<<< Date Index >>>     <<< Thread Index >>>

Re: COSEINC Linux Advisory #1: Linux Kernel Parent Process Death Signal Vulnerability



Dan Yefimov wrote:

> > > If setuid program just 
> > > trusts the environment in that it doesn't properly handle or block 
> > > signals 
> > > whose default action is terminating the process and doesn't perform it's
> > > actions in a fail-safe manner, it is certainly broken. Setuid program 
> > > must 
> > > always be careful in signal handling and data processing.
> > 
> > Ordinarily, a process can assume that certain signals (those which can
> > only be generated by kill()) can only be received as a result of an
> > action by a sufficiently privileged process.
> 
> The signal in question in the given situation is issued by PRIVILEGED 
> process, 
> no matter how.

And that's the bug, 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.

> Well written program must not depend on anything that is out of 
> it's control.

That's neither possible nor sensible. 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.

> > Also, other signals which could be triggered by the predecessor (e.g. 
> > SIGALRM triggered due to alarm() followed by exec()) can normally be
> > prevented by specific means (e.g. resetting any outstanding timers). 
> > This bug means that such steps are insufficient.
> > 
> > A consequence of this bug is that no signal can be trusted.
> 
> Sure.

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.

> > Also, if it's possible to set the signal to one which cannot be
> > blocked (SIGKILL, SIGSTOP), there's not much that the callee can do
> > about it.
> 
> Yes, and well written program must operate in a fail safe way, that is if it 
> is 
> killed, for example, by sadly known OOM killer, all data it operated on must 
> remain in a consistent state.

However nice failsafe may be, it is often either impossible or
impractical. In general, a process shouldn't have to protect itself
against the superuser, and in some respects shouldn't even try to.

> > > From another hand, 
> > > PDEATHSIG should be always reset on exec() like signal handlers are (I'm 
> > > not 
> > > sure though if that is directly specified by any standard). Please 
> > > correct me
> > > if I'm wrong.
> > 
> > prctl() isn't specified by any standard; it's Linux-specific.
> > 
> > That's a significant part of the problem: code which isn't
> > specifically written for Linux isn't going to take steps to mitigate
> > this issue (e.g. reset the parent death signal).
> > 
> > But the suggestion that this should be reset on exec() (at least for a
> > suid/sgid binary) is sound, IMHO.
> 
> 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?

If you're talking about the parent, it isn't the parent's "successor"
(the exec()d program which inherits the parent's PID) which gets the
signal.

If you're talking about the child, that would almost entirely defeat
the purpose of having PDEATHSIG.

A parent uses this feature to cause a signal to be delivered to its
children when it terminates. The children will usually be created by
fork()+exec(), so resetting it upon exec() would mean that it was only
useful for the fork()-without-exec() case, which is quite uncommon.

> 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.

> 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).

> > 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.

> Do open files fall into this category?

Descriptors are preserved across exec() unless the FD_CLOEXEC flag is
set.

http://www.opengroup.org/onlinepubs/009695399/functions/execve.html

    File descriptors open in the calling process image shall remain
    open in the new process image, except for those whose close-on-
    exec flag FD_CLOEXEC is set. For those file descriptors that
    remain open, all attributes of the open file description remain
    unchanged. For any file descriptor that is closed for this reason,
    file locks are removed as a result of the close as described in
    close(). Locks that are not removed by closing of file descriptors
    remain unchanged.

> Does blocked signal bitmap fall into it?

Yes.

>From the above link:

    The initial thread of the new process shall inherit at least the
    following attributes from the calling thread:

    * Signal mask (see sigprocmask() and pthread_sigmask())
    * Pending signals (see sigpending())

> What exactly are you going to reset?

Everything which is not specified as being preserved. Which means
every non-standard extension that programmers won't have heard of and
won't be expecting to have to manually reset.

-- 
Glynn Clements <glynn@xxxxxxxxxxxxxxxxxx>