From owner-freebsd-current@FreeBSD.ORG Thu Aug 28 06:40:00 2003 Return-Path: Delivered-To: freebsd-current@freebsd.org Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id 6DDFE16A4BF for ; Thu, 28 Aug 2003 06:40:00 -0700 (PDT) Received: from fledge.watson.org (fledge.watson.org [204.156.12.50]) by mx1.FreeBSD.org (Postfix) with ESMTP id 2950E44001 for ; Thu, 28 Aug 2003 06:39:59 -0700 (PDT) (envelope-from robert@fledge.watson.org) Received: from fledge.watson.org (localhost [127.0.0.1]) by fledge.watson.org (8.12.9/8.12.9) with ESMTP id h7SDdWrO034795; Thu, 28 Aug 2003 09:39:33 -0400 (EDT) (envelope-from robert@fledge.watson.org) Received: from localhost (robert@localhost)h7SDdWFt034792; Thu, 28 Aug 2003 09:39:32 -0400 (EDT) Date: Thu, 28 Aug 2003 09:39:32 -0400 (EDT) From: Robert Watson X-Sender: robert@fledge.watson.org To: Joe Greco In-Reply-To: <200308271408.h7RE8ghW073779@aurora.sol.net> Message-ID: MIME-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII cc: freebsd-current@freebsd.org Subject: Re: Someone help me understand this...? X-BeenThere: freebsd-current@freebsd.org X-Mailman-Version: 2.1.1 Precedence: list List-Id: Discussions about the use of FreeBSD-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 28 Aug 2003 13:40:00 -0000 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@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: