Yet *another* horrible problem with setuid is what it did to the simple Unix userid/groupid system. Rather than processes simply having a userid and a group id, as in the early days, or a user id, a group id, and a list of supplementary group ids, it has a real user id, an effective user id, a saved user ID, a morass of inconsistent functions to switch between them, some of which sometimes let you switch back and sometimes don't, with some of which have magic special rules to switch to/from root and some of which don't... the transition diagram is numbing in its complexity. I'm amazed more security holes don't arise from this nightmare.
d-bus services (or other started-as-root daemon-invoked processes) have *none* of this security-critical quasi-portable crap visible (obviously it is visible briefly inside d-bus as it forks and switches uid, but that need not be visible to the service author.)
Ghosts of Unix past, part 4: High-maintenance designs
Posted Nov 23, 2010 22:31 UTC (Tue) by foom (subscriber, #14868)
[Link]
Well, even without a suid bit, you still want to be able to switch effective UIDs while preserving the ability to switch back, so that you can do work on behalf of a user using that user's credentials. E.g. accessing files on the filesystem.
Ghosts of Unix past, part 4: High-maintenance designs
Posted Nov 24, 2010 8:16 UTC (Wed) by nix (subscriber, #2304)
[Link]
Yes, but if you were designing a new system from scratch, would you choose to do that the way Unix has? I know I wouldn't.
(And you mean 'while *optionally* preserving the ability to switch back': a lot of programs really don't want to keep that ability.)
Ghosts of Unix past, part 4: High-maintenance designs
Posted Nov 26, 2010 16:45 UTC (Fri) by nevyn (subscriber, #33129)
[Link]
> d-bus services (or other started-as-root daemon-invoked processes) have *none* of
> this security-critical quasi-portable crap visible
They don't have _those_ issues, no. But there are a number of open issues wrt. how d-bus services break the link between the user and the service. The most obvious is that loginuid is lost. I've also yet to see any kind of analysis on DOSing D-Bus. These are all security related problems, they are just less well understood.
Ghosts of Unix past, part 4: High-maintenance designs
Posted Dec 2, 2010 22:27 UTC (Thu) by oak (guest, #2786)
[Link]
> I've also yet to see any kind of analysis on DOSing D-Bus.
DOSing D-BUS is trivial, just register services and send messages.
D-BUS daemon needs an FD per client connection so you can DOS it by creating new connections to it until it doesn't anymore accept connections (it runs out of FDs before you do as it already has several clients).
D-BUS doesn't seem to have limits on its memory usage. For example if you send messages to (e.g. your own) service and it doesn't read them, D-BUS doesn't block either sender or receiver, it just buffers all the messages until the system runs to swap and D-BUS goes OOM.
The D-BUS memory management code seems also a bit horrible, both inefficient (doesn't free memory to system, just fragments its heap) and complicated. It's also a bit strange that a thing that mostly is supposed to push bits from one socket to another is CPU, not IO bound (at least when it has many clients, like you have on Maemo).
Ghosts of Unix past, part 4: High-maintenance designs
Posted Nov 30, 2010 16:53 UTC (Tue) by pbonzini (subscriber, #60935)
[Link]
Since you mention fork... never thought what kind of awful code this particular high-maintenance design causes?
As a first example, think about the ugly hack that vfork is. It still survives because it does perform better than fork. Luckily posix_spawn hides this ugliness and chooses fork over vfork. On the other hand, it means that popen can be _much_ more expensive than system just because one will use fork and the other will use vfork.
And one thing I realized today: you can hardly retrieve the errno of a failed exec system call in a forked process. Everything you will do is going to be racy, except possibly using ptrace on the child.
(FWIW, the most clever way I thought about it is to use a FD_CLOEXEC pipe and write the return code of exec there; if the parent cannot read anything, exec succeeded... or the forked child died of a signal after exec returned but before it wrote to a pipe... and if you wanna use waitpid, you get it wrong in case the execed process was signaled before the parent started waitpid...).
Ghosts of Unix past, part 4: High-maintenance designs
Posted Dec 4, 2010 18:00 UTC (Sat) by nix (subscriber, #2304)
[Link]
Yeah, that's quite nasty. But spawn*() has all the same problems and a bunch of extra ones, prominent among them the fact that it's exactly the same as fork()/exec() with the code between the fork() and exec() hardwired. So you end up with dozens of spawn*() calls and no benefit over fork()/exec() at all (except on tiny non-MMU systems, which can theoretically implement spawn*() but not fork()/exec() --- but usually implement both, because most code uses fork()/exec() and not spawn*().)
I suspect that a combination of waitpid() (to catch signals and read-from-pipe to catch errno might work: if you played games with self-signalling you could possibly encode errnos as rare signals and drop the pipe, at the cost of losing the ability to detect those rare signals.
A bit of extra effort (sending something down the pipe right before exec() as well as right after a failed one, and opening the pipe end O_CLOEXEC) enables you to distinguish between a signal hitting before exec() and a signal hitting after a successful exec().
But, yes, this is all pointlessly complex. If C had proper Lisp-style macros we could wrap this up in a library without the result becoming as inflexible as spawn(). (Something involving function pointers could get halfway there, perhaps. But it wouldn't be as neat.)
Ghosts of Unix past, part 4: High-maintenance designs
Posted Dec 5, 2010 20:40 UTC (Sun) by quotemstr (subscriber, #45331)
[Link]
except on tiny non-MMU systems, which can theoretically implement spawn*() but not fork()/exec()
Cygwin also falls into this category. fork() works there, but it's painfully slow because it copies the entire address space. spawn() is far more efficient.
Ghosts of Unix past, part 4: High-maintenance designs
Posted Dec 6, 2010 10:58 UTC (Mon) by pbonzini (subscriber, #60935)
[Link]
no benefit over fork()/exec() at all (except on tiny non-MMU systems, which can theoretically implement spawn*() but not fork()/exec()
Actually, if the parent has a large RSS it is quite common to see major performance improvements with vfork() over fork(). And given how hacky vfork() is, I'd really be happy to pay the price of spawn()'s inflexibility.
fork() should be treated like a relic of when parallelism was achieved using processes rather than threads, IMO.
Ghosts of Unix past, part 4: High-maintenance designs
Posted Dec 6, 2010 11:31 UTC (Mon) by dlang (✭ supporter ✭, #313)
[Link]
does this vfork advantage still exist (i.e., is it measurable) when the host OS does Copy On Write for the fork instead of actually copying all ram?
yes, the page tables still get modified twice, but is this measurable on modern hardware?
Ghosts of Unix past, part 4: High-maintenance designs
Posted Dec 6, 2010 11:58 UTC (Mon) by pbonzini (subscriber, #60935)
[Link]
Yes, I've seen forking take 60% of CPU (that was forking 4 child processes per second from a +2 GB process). Using fork to vfork, or equivalently switching to posix_spawn, brought it down to 3-4%.