Dan Yefimov wrote: > > > The signal in question in the given situation is issued by PRIVILEGED process, > > > no matter how. > > > > And that's the bug, > > The case we consider is of course a bug. But generally privileged process > sending a signal to another privileged process is of course not a bug. > Yes, the user toggles a signal that privileged process sends to another one, > but how many ways to trigger sending a signal to a process spawned by that user > are there? User can always press Ctrl-C, Ctrl-Q, Ctrl-R at the terminal, he can > hang up the terminal, he can open some socket/pipe/FIFO for non-blocking I/O > and issue fcntl() call to activate sending either a SIGIO or some other signal > when some new data become available in it, he can issue alarm(), and probably > many, many, many other ways. All of these are known, documented behaviours. The signals involved can be blocked or ignored, and the mechanisms which send those signals can be disabled (tcsetattr() can disable Ctrl-C etc, unused descriptors can be closed, timers can be reset, etc). Setuid programs will normally take such steps at startup if they have critical sections which should avoid being interrupted (e.g. to prevent stale lock files). However, the bug in question allows sending signals which cannot be blocked or ignored (SIGKILL, SIGSTOP). Moreover, the cause (PDEATHSIG) cannot be disabled, and will be unknown to to programmers who aren't familiar with the Linux-specific prctl() call (and even programmers who know about it won't be allowing for the fact that it could be triggered by an unprivileged process due to this bug). > Yes, all they can be turned off, but that would > require a large artificial intelligence and excessive knowledge about > everything occurred in the system before startup from the program being > started. Such is life for anyone writing a setuid executable. However, the issues are known and documented. At least, most of them are; but not the PDEATHSIG issue. > It's much simpler for that program to just block, ignore or install > proper signal handler for every possible signal in the system whose default > action is terminating a process. SIGKILL and SIGSTOP cannot be blocked, handled or ignored. Signals which don't terminate the process may still have undesirable consequences, e.g. use of SIGUSR1 as a secure signalling mechanism (at least, it's supposed to be secure). > > because it's an unprivileged process which > > *causes* the signal to be issued. If the process tried to send the > > signal directly with kill(), it would fail. This vulnerability is a > > form of privilege escalation; it can cause the sending of signal which > > would normally be prohibited on security grounds. > > The matter here is that you cannot abuse this bug to send arbitrary signal to > arbitrary process in the system. You can only send it to your children. Including setuid children. That isn't supposed to be possible. > And > this is in general not exploitable. For example, what scary things can happen > when you send SIGKILL, SIGQUIT, SIGBUS, SIGSTOP or SIGSEGV to /bin/su in > condition that the latter doesn't allow arbitrary code execution on receiving a > signal? Killing a process can leave stale lock files resulting in a denial of service. Sending SIGSTOP may behave likewise, only moreso: the creator will still exist, so the lock files may not be considered stale, fcntl() locks will still be held, etc. There's more risk if a program uses signals (e.g. SIGUSR1) for remote control. If there wasn't *any* risk, there wouldn't be any restrictions on sending signals to privileged processes. > > > Well written program must not depend on anything that is out of > > > it's control. > > > > That's neither possible > > Really? Let's consider the following scenario. You write an analogue of > /bin/passwd. Here you make a temporary copy of /etc/shadow, hard link > /etc/shadow to /etc/shadow- pre-removing existing /etc/shadow- if that exists, That interferes with any existing passwd invocation. > > Programs have to rely upon the > > OS to guarantee certain behaviours. The problem here is that there is > > a mechanism which causes a guarantee to be violated. > > > Yes, and I said this is a bug, but it is in general not exploitable. It's roughly as exploitable as any other bug which allows signals to be sent to privileged processes, i.e. it's mostly a DoS issue. > > Just in case it hasn't sunk in yet, the inability to trust signals is > > a consequence of this bug. Ordinarily, it should be possible to rely > > upon the fact that an asynchronous signal cannot be sent to a suid > > process by an unprivileged user. > > I disagree with you in that. Any hard guarantee can be given only by God. > I repeat, signals are in general not a reliable information source since they > can be generated in a couple of ways, even by an unkind superuser :-) . You cannot protect against the superuser, nor should you even try. Programs which attempt to evade control by the owner of the hardware are normally termed "malware". > > > In fact, PDEATHSIG should be reset for every binary, not just suid/sgid, since > > > it emits signal that exec()ed program may not expect. > > > > Are you talking about the parent exec()ing or the child? > > > No matter. > > > If you're talking about the child, that would almost entirely defeat > > the purpose of having PDEATHSIG. > > > The only useful exploitation of PDEATHSIG I can imagine is signalling to a > server subprocess that it's superprocess died for some reason and the > subprocess should exit as far as possible in order to avoid some harm. That > model doesn't include execing. Sure it does. Server subprocesses frequently start with exec(), whether of a different binary or the same binary. The latter is sometimes done to reset memory usage. But the more general case is simply where a process wants all of its children to die with it. If the parent is killed, the children are typically of no further use, and may at least tie up resources and possibly prevent a new invocation of the parent from operating. > > > But in any case, every program shouldn't trust any signal in the > > > system. That is a good tone rule. > > > > In which case, what's the point of having signals in the first place? > > > > Processes are supposed to respond to signals. Security is achieved > > placing controls on who can signal who, and this bug circumvents that > > mechanism. > > But the process in general is in no way obliged to respond to every signal > unless explicitly wanted. It's obliged to "repsond" to SIGKILL and SIGSTOP, and may choose to respond to other signals under the assumption that there are restrictions on who can send them. > > > I still don't see why this bug should be considered as a security issue but not > > > as an ordinary bug. > > > > Because it's a form of privilege escalation. Non-root processes can't > > normally send signals to processes which are owned by another UID (and > > most modern operating systems prevent non-root processes from sending > > signals to any process where suid/sgid is involved regardless of the > > current UID or EUID). > > I repeat, this bug cannot be abused to send arbitrary signal to arbitrary > process in the system. Only direct successors (children) are affected, and this > is in general not exploitable. Sending asynchronous signals to setuid/setgid children is supposed to be impossible, and that restriction is considered a security mechanism. > > > > Moreover, I would suggest that exec()ing a suid/sgid binary should > > > > reset *everything* which is not explicitly specified as being > > > > preserved. > > > > > > Specified with what? > > > > POSIX, XPG, SUS. > > I got you. I just thought you want to reset everything successively including > sigmasks and open files :-) . No. I'm suggesting that it should be possible for a setuid/setgid program to *exhaustively* sanitise (or at least validate) its operating environment. -- Glynn Clements <glynn@xxxxxxxxxxxxxxxxxx>