Someone help me understand this...?

Robert Watson rwatson at freebsd.org
Thu Aug 28 06:40:00 PDT 2003


On Wed, 27 Aug 2003, Joe Greco wrote:

> The specific OS below is 5.1-RELEASE but apparently this happens on 4.8
> as well. 

Could you confim this happens with 4.8?  The access control checks there
are substantially different, and I wouldn't expect the behavior you're
seeing on 4.8...

<...>
> Well, the sending and receiving processes both clearly have equal uid/euid.
> 
> We're not running in a jail, so I don't expect any issues there.
<...>
> 
> The parent process did actually start as root and then shed privilege with
> 
>         struct passwd *pw = getpwnam("news");
>         struct group *gr = getgrnam("news");
>         gid_t gid;
> 
>         if (pw == NULL) {
>             perror("getpwnam('news')");
>             exit(1);
>         }
>         if (gr == NULL) {
>             perror("getgrnam('news')");
>             exit(1);
>         }
>         gid = gr->gr_gid;
>         setgroups(1, &gid);
>         setgid(gr->gr_gid);
>         setuid(pw->pw_uid);
> 
> so that looks all well and fine...  so why can't it kill its own children,
> and why can't I kill one of its children from a shell with equivalent 
> uid/euid?
> 
> I know there's been some paranoia about signal delivery and all that, but
> my searching hasn't turned up anything that would explain this.  Certainly
> the manual page ought to be updated if this is a new expected behaviour or
> something...  at least some clue as to why it might fail would be helpful.

The man page definitely needs to be updated, but I think it's worth having
a conversation about whether the current behavior is too conservative
first...

These changes come in response to a class of application vulnerabilities
relating to the delivery of "unexpected signals".  The reason the process
in question is being treated as special from an access control perspective
is that it has undergone a credential change, resulting in the setting of
the process P_SUGID bit.  This bit remains set even if the remaining
credentials of the process appear "normal" -- i.e., even if ruid==euid,
rgid==egid, and can only be reset by calling execve() on a "normal" 
binary, which is considered sufficient to flush the state of the process. 

These processes are given special protection properties because they
almost always have cached access to memory or resources acquired using the
original credential.  For example, the process accesses the password file
while holding root privilege, which means that the process may well have
password hashes in memory from its reading the shadow password file -- in
fact, it likely even have a file descriptor to the shadow password file
still open.  The same P_SUGID flag is used to prevent against unprivileged
debugging of applications that have changed credentials and now appear
"normal".  P_SUGID is also used to determine the results of the
issetugid() system call, which is used by many libraries to see if they
are running with (or have run with)  privilege and need to behave in a
more conservative manner. 

I don't remember the details, but there have been at least a couple of
demonstrated exploits of vulnerable applications using signals in which
setuid applications rely on certain signals (such as SIGALRM, SIGIO,
SIGURG) only being delivered as a result of system calls that set up
timers, IO, etc. I seem to recall it might have involved a setuid
application such as sendmail on OpenBSD, but I'll have to do some googling
and get back to you.  These protections probably fall into the same class
of conservative behavior as our preventing setuid programs from being
started with closed stdin/stdout/stderr descriptors.

Giving up privilege without performing an exec() is very difficult in
UNIX, unfortunately, since the trappings of privilege may be maintained by
libraries, etc, without the knowledge of application writers.  Right now,
signal delivery in 5.x is pretty conservative if a process has changed
credentials, to protect against tampering with a class of applications
that has, historically, been vulnerable to a broad variety of exploits. 
I've attached an (untested) patch that makes this behavior run-time
configuration using a sysctl -- when the sysctl is disabled, special-case
handling for P_SUGID processes is disabled.  I believe that this will
cause the problem you're experiencing in 5.x to go away -- please let me
know.

Clearly, unbreaking applications like Diablo by default is desirable.  At
least OpenBSD has similar protections to these turned on by default, and
possibly other systems as well.  As 5.x sees more broad use, we may well
bump into other cases where applications have similar behavior: they rely
on no special protections once they've given up privilege.  I wonder if
Diablo can run unmodified on OpenBSD; it could be they don't include
SIGALRM on the list of "protect against" signals, or it could be that they
modify Diablo for their environment to use an alternative signaling
mechanism.  Another alternative to this patch would simply be to add
SIGARLM to the list of acceptable signals to deliver in the
privilege-change case.

BTW, it's worth noting that the mechanism Diablo is using to give up
privilege actually does retain some "privileges" -- it doesn't, for
example, synchronize its resource limits with those of the user it is
switching to, so it retains the starting resource limits (likely those of
the root account).  A preferred structuring of privilege separation
attempts to avoid this scenario by containing privilege in a process that
is as independent as possible from the unprivileged processes, and uses
file descriptor passing to get a bound port to the unprivileged processes,
rather than credential manipulation which is fairly failure-prone.  Also,
regardless of whether the outcome seems likely, it would be a good idea
for Diablo to check the return values of the setuid/setgid/... calls and
make sure they succeed.

Robert N M Watson             FreeBSD Core Team, TrustedBSD Projects
robert at fledge.watson.org      Network Associates Laboratories


Index: kern_prot.c
===================================================================
RCS file: /home/ncvs/src/sys/kern/kern_prot.c,v
retrieving revision 1.175
diff -u -r1.175 kern_prot.c
--- kern_prot.c	13 Jul 2003 01:22:20 -0000	1.175
+++ kern_prot.c	28 Aug 2003 13:30:29 -0000
@@ -1367,6 +1367,20 @@
 	return (cr_cansee(td->td_ucred, p->p_ucred));
 }
 
+/*
+ * 'conservative_signals' prevents the delivery of a broad class of
+ * signals by unprivileged processes to processes that have changed their
+ * credentials since the last invocation of execve().  This can prevent
+ * the leakage of cached information or retained privileges as a result
+ * of a common class of signal-related vulnerabilities.  However, this
+ * may interfere with some applications that expect to be able to
+ * deliver these signals to peer processes after having given up
+ * privilege.
+ */
+static int	conservative_signals = 1;
+SYSCTL_INT(_security_bsd, OID_AUTO, conservative_signals, CTLFLAG_RW,
+    &conservative_signals, 0, "Unprivileged processes prevented from "
+    "sending certain signals to processes whose credentials have changed");
 /*-
  * Determine whether cred may deliver the specified signal to proc.
  * Returns: 0 for permitted, an errno value otherwise.
@@ -1399,7 +1413,7 @@
 	 * bit on the target process.  If the bit is set, then additional
 	 * restrictions are placed on the set of available signals.
 	 */
-	if (proc->p_flag & P_SUGID) {
+	if (conservative_signals && (proc->p_flag & P_SUGID)) {
 		switch (signum) {
 		case 0:
 		case SIGKILL:



More information about the freebsd-current mailing list