2002-04-12 18:33:59

by Marek Zelem

[permalink] [raw]
Subject: [PATCH] + story;) on POSIX capabilities and SUID bit


Hello

The POSIX capabilities support defines the file and process capabilities,
and rules to be obeyed at particular events, such as exec() and so.
Because of historic reasons, there is also a SUID bit, which is also
POSIX, and has its own set of rules. You can choose either to use
SUID, or to use POSIX capabilities. If you want to be compatible with
both of them, you have a problem.

Linux internally uses POSIX capabilities to verify permissions. On
the other hand, it uses a filesystem, which doesn't hold the capability
information at each file, so it must use SUID bit, which is available
on all UNIX-like filesystems. This is called the SUID emulation, and
if everything goes well, it might disappear some day and will be replaced
by proper implementation of capabilities in the filesystem.

The rules for computing new process capabilities at exec() involve
the previous set of process capabilities, and the capabilities got
from the file to be executed. (I,P and E means inherited, permitted
and effective capabilities, p means process caps and f means file caps).

The old formula, which is used during exec:
* pI' = pI
* (***) pP' = (fP & X) | (fI & pI)
* pE' = pP' & fE [NB. fE is 0 or ~0]
is not compatible with the SUID-bit semantics. Because of this, there is
an ugly piece of code in prepare_binprm(), which (after several lines
of code) results in setting either FULL or NONE capabilities to the
process. If we want to have the POSIX capabilities working, we
DON'T want this behaviour. We want to have new capabilities reflecting
the old ones and, at the same time, honour the SUID bit.

Our new formula:
* (***) pP' = (fP & X) | (fI & pI)
* pI' = pP'
* pE' = ((pP' & pE) | fP) & X & fE
respects the ideas of POSIX capabilities, and preserves the SUID bit emulation
at the same time. In this case, SUID bit just alters the file capabilities.

The new formula is not exactly the same as in the POSIX standard. That's o.k.
The old version, with SUID bit emulation turned on, was in conflict with
POSIX too. The new one honours the SUID bit just like the previous one,
and ENABLES use of the file capabilities. Thus, the new implementation
* is ready for the POSIX caps-enabled filesystems and
* enables use of POSIX capabilities even in the system, which uses
the SUID files.

Marek Zelem

Here follows the patch for 2.5. For those who are interrested, there
is a 2.4 patch available at
http://www.terminus.sk/~marek/kernel/capabilities-2.4.18.diff.

--- linux-2.5.7.orig/fs/exec.c Mon Mar 18 21:37:12 2002
+++ linux-2.5.7/fs/exec.c Fri Apr 12 19:29:53 2002
@@ -20,6 +20,10 @@
* table to check for several different types of binary formats. We keep
* trying until we recognize the file or we run out of supported binary
* formats.
+ *
+ * Modified formula for evolving capabilities to allow nice SUID emulation
+ * which work together with (future) VFS capabilities implementation.
+ * Feb 2002 by Marek Zelem <[email protected]>
*/

#include <linux/config.h>
@@ -651,11 +655,23 @@
bprm->e_uid = current->euid;
bprm->e_gid = current->egid;

+ /* We don't have VFS support for capabilities yet */
+ cap_clear(bprm->cap_permitted);
+ cap_set_full(bprm->cap_inheritable);
+ cap_set_full(bprm->cap_effective);
+
if(!(bprm->file->f_vfsmnt->mnt_flags & MNT_NOSUID)) {
/* Set-uid? */
- if (mode & S_ISUID)
+ if (mode & S_ISUID) {
bprm->e_uid = inode->i_uid;
-
+ if (bprm->e_uid == 0) {
+ if (!issecure(SECURE_NOROOT))
+ cap_set_full(bprm->cap_permitted);
+ }
+ else {
+ cap_clear(bprm->cap_effective);
+ }
+ }
/* Set-gid? */
/*
* If setgid is set but no group execute bit then this
@@ -666,28 +682,6 @@
bprm->e_gid = inode->i_gid;
}

- /* We don't have VFS support for capabilities yet */
- cap_clear(bprm->cap_inheritable);
- cap_clear(bprm->cap_permitted);
- cap_clear(bprm->cap_effective);
-
- /* To support inheritance of root-permissions and suid-root
- * executables under compatibility mode, we raise all three
- * capability sets for the file.
- *
- * If only the real uid is 0, we only raise the inheritable
- * and permitted sets of the executable file.
- */
-
- if (!issecure(SECURE_NOROOT)) {
- if (bprm->e_uid == 0 || current->uid == 0) {
- cap_set_full(bprm->cap_inheritable);
- cap_set_full(bprm->cap_permitted);
- }
- if (bprm->e_uid == 0)
- cap_set_full(bprm->cap_effective);
- }
-
memset(bprm->buf,0,BINPRM_BUF_SIZE);
return kernel_read(bprm->file,0,bprm->buf,BINPRM_BUF_SIZE);
}
@@ -698,6 +692,11 @@
*
* The formula used for evolving capabilities is:
*
+ * (***) pP' = (fP & X) | (fI & pI)
+ * pI' = pP'
+ * pE' = ((pP' & pE) | fP) & X & fE
+ *
+ * original was:
* pI' = pI
* (***) pP' = (fP & X) | (fI & pI)
* pE' = pP' & fE [NB. fE is 0 or ~0]
@@ -744,6 +743,14 @@
* capability rules */
if (current->pid != 1) {
current->cap_permitted = new_permitted;
+/* This is to allow good SUID emulation (Marek Zelem <[email protected]>) */
+ current->cap_inheritable = new_permitted;
+ working =
+ cap_intersect(new_permitted,current->cap_effective);
+ new_permitted =
+ cap_combine(working,bprm->cap_permitted);
+ new_permitted = cap_intersect(new_permitted, cap_bset);
+/* END of good SUID emulation (Marek Zelem <[email protected]>) */
current->cap_effective =
cap_intersect(new_permitted, bprm->cap_effective);
}
--- linux-2.5.7.orig/include/linux/capability.h Mon Mar 18 21:37:09 2002
+++ linux-2.5.7/include/linux/capability.h Fri Apr 12 19:29:53 2002
@@ -302,8 +302,9 @@

#define CAP_EMPTY_SET to_cap_t(0)
#define CAP_FULL_SET to_cap_t(~0)
-#define CAP_INIT_EFF_SET to_cap_t(~0 & ~CAP_TO_MASK(CAP_SETPCAP))
-#define CAP_INIT_INH_SET to_cap_t(0)
+//#define CAP_INIT_EFF_SET to_cap_t(~0 & ~CAP_TO_MASK(CAP_SETPCAP))
+#define CAP_INIT_EFF_SET to_cap_t(~0)
+#define CAP_INIT_INH_SET to_cap_t(~0)

#define CAP_TO_MASK(x) (1 << (x))
#define cap_raise(c, flag) (cap_t(c) |= CAP_TO_MASK(flag))
--- linux-2.5.7.orig/kernel/sys.c Mon Mar 18 21:37:05 2002
+++ linux-2.5.7/kernel/sys.c Fri Apr 12 19:29:53 2002
@@ -482,6 +482,7 @@
!current->keep_capabilities) {
cap_clear(current->cap_permitted);
cap_clear(current->cap_effective);
+ cap_clear(current->cap_inheritable);
}
if (old_euid == 0 && current->euid != 0) {
cap_clear(current->cap_effective);
--- linux-2.5.7.orig/kernel/kmod.c Mon Mar 18 21:37:10 2002
+++ linux-2.5.7/kernel/kmod.c Fri Apr 12 19:29:53 2002
@@ -134,6 +134,8 @@
curtask->euid = curtask->fsuid = 0;
curtask->egid = curtask->fsgid = 0;
cap_set_full(curtask->cap_effective);
+ cap_set_full(curtask->cap_inheritable);
+ cap_set_full(curtask->cap_permitted);

/* Allow execve args to be in kernel space. */
set_fs(KERNEL_DS);


--
e-mail: [email protected]
web: http://www.terminus.sk/~marek/
pgp key: http://www.terminus.sk/~marek/gpg.txt



2002-04-16 02:24:16

by daw

[permalink] [raw]
Subject: Re: [PATCH] + story;) on POSIX capabilities and SUID bit

Marek Zelem wrote:
>Our new formula:
> * (***) pP' = (fP & X) | (fI & pI)
> * pI' = pP'
> * pE' = ((pP' & pE) | fP) & X & fE

Can you say anything about why this is safe and doesn't introduce
vulnerabilities? (The capabilities misfeature that caused sendmail
8.10.1 to leak root privilege really drove home for me the subtlety of
this stuff.)

Also, the meaning of fE and fP seem backwards from what I would have
expected. Maybe this reflects a lack in my understanding in capabilities,
but I thought 'effective' refers to capabilities you're allowed to invoke
at the moment, whereas 'permitted' refers to an upper bound on what
capabilities you're allowed enable in 'effective', consequently I would
have swapped the treatment of fE and fP. Can you clear up my confusion?

Finally, what's the story behind the changes to CAP_INIT_EFF_SET and
CAP_INIT_INH_SET, and the business with CAP_SETPCAP? If I understand
correctly, one side-effect of this change is that you've changed cap_bset
(X, the global bound on capabilities above) to add CAP_SETPCAP to it.
Is this safe? What motivated this change? Did I understand correctly?

2002-04-17 16:02:08

by Marek Zelem

[permalink] [raw]
Subject: Re: [PATCH] + story;) on POSIX capabilities and SUID bit


On 16 Apr 2002, David Wagner wrote:

> Marek Zelem wrote:
> >Our new formula:
> > * (***) pP' = (fP & X) | (fI & pI)
> > * pI' = pP'
> > * pE' = ((pP' & pE) | fP) & X & fE
>
> Can you say anything about why this is safe and doesn't introduce
> vulnerabilities? (The capabilities misfeature that caused sendmail
> 8.10.1 to leak root privilege really drove home for me the subtlety of
> this stuff.)

New 'permitted' capabilities for process are computed by the same formula as
the original because there is the primary aspiration to preserve the original
POSIX formula.
Most significat difference is in computation of 'inheritable' capabilities.
Original formula just keeps process 'inheritable' capabilities untouched.
(This fact was major problem of consolidate POSIX capabilities and SUID bit.)
New formula set the 'inheritable' capabilities to correspond with the new
'permitted'.
There can occur four situations:
1) capability was in pI and will be in pI' - this correspond with
the original formula
2) capability wasn't in pI and will not be in pI' - this also
correspond with the original formula
3) capability was in pI and will not be in pI' - (so is not set
in new pP) - meaning: proces is NOT able to raise self pP by
executing "nosuid" binary. In this case is new formula
more restrictive then original.
4) capability wasn't in pI and will be in pI' - this can occure
only if this capability is in fP. In this case this
capability is also in pP'. And if is in 'permitted' caps
then process is allowed to add this capability into
'inheritable' caps. So we do nothin wrong if we set this
capability into pI'.
Finally, difference in computation of pE' only aspirate to reflect pE.
Major principle was preserved: pE' = pP' & fE. In new formula was pP'
replaced by more complex term. But it's always true that
(((pP' & pE) | fP) & X & fE) IS SUBSET of (pP' & fE).
So I think this is safe.

>
> Also, the meaning of fE and fP seem backwards from what I would have
> expected. Maybe this reflects a lack in my understanding in capabilities,
> but I thought 'effective' refers to capabilities you're allowed to invoke
> at the moment, whereas 'permitted' refers to an upper bound on what
> capabilities you're allowed enable in 'effective', consequently I would
> have swapped the treatment of fE and fP. Can you clear up my confusion?

File capabilities are little different from process capabilities. Meaninig
of file capabilities is:
fP - capafilities which are "forced" to process by exec().
(sometimes called 'forced' capabilities)
fI - capabilities which is "allowed" to remain in process 'permited'.
(sometimes called 'allowed' capabilities)
fE - capabilities which select which of 'permited' will be
initially also 'effective'. (afrer exec() of course.)

>
> Finally, what's the story behind the changes to CAP_INIT_EFF_SET and
> CAP_INIT_INH_SET, and the business with CAP_SETPCAP? If I understand
> correctly, one side-effect of this change is that you've changed cap_bset
> (X, the global bound on capabilities above) to add CAP_SETPCAP to it.
> Is this safe? What motivated this change? Did I understand correctly?

Yes, you understand correctly. Enabling CAP_SETPCAP is safe exactly as
enabling the CAP_SYS_MODULE, which is enabled by default. Capability
CAP_SYS_MODULE allows process to modify the cap_bset. Maybe I am wrong,
but my opinion is not to do inconsequential restrictions. So, this is
reason why I decide to change CAP_INIT_EFF_SET.

Marek Zelem
--
e-mail: [email protected]
web: http://www.terminus.sk/~marek/
pgp key: http://www.terminus.sk/~marek/gpg.txt