Re: [patch 2.1.97] more capabilities support

Alexander Kjeldaas (astor@guardian.no)
Sun, 19 Apr 1998 17:38:29 +0200


On Sun, Apr 19, 1998 at 03:17:54AM -0700, Andrew Morgan wrote:

> + 1.1.2 File capabilities
> +
> + WARNING: there is currently no support for file capabilities
> +
> + Each file has three sets of capabilities, when the file is exec()'d
> + these three capabilities combine with those of the exec()'ing process
> + to establish the newly exec()'d process' capabilities.
> +
> + 1.1.2.1 Inheritable
> +
> + This is a mask against which the Inheritable capabilities of the
> + exec()'ing process are filtered. Only capabilities in this set may be
> + inherited by the exec()'d process.
> +

This is the "allowed" set. [I use this term since it is much clearer
in order to understand what is going on].

> + Note, on filesystems that do not support capabilities, all executables
> + are assumed to have { ~0 } Inheritable capabilities.

No, this is not correct. On file systems without capability support,
files are assumed to have a { 0 } inheritable (allowed) set. We
_could_ do the above, maybe we should, but we don't _do_ this
currently. Some reasons:

* The draft says so :-)
* You get a kind of inverse protection. A file system without
capabilities will grant an executable more capabilities than the
same executable executed from a different file system. This is a
"threat" that will always be there since we live in a world with
lots of other file systems than ext2. I think this collides
somewhat with the principle of least surprise.
* Since we have a CAP_SETPCAP capability (set process' capabilities)
we can implement file-system capability support using a daemon.
* It would arguably be better to have it as a per file system mount
option.

> +
> + 1.1.2.2 Effective
> +
> + This set must have either all capabilities raised or all capabilities
> + lowered. It is used to indicate whether the program knows about
> + capabilities. If this set has capabilities raised, the program will
> + start with all of its Permitted capabilities in its Effective set. If
> + this set is empty, the program will start with an empty Effective set,
> + and will have to explicitly raise the capabilities it needs.
> +

This is a single bit saying whether the executable knows about
capabilities (internally it's represented as a bit mask however).

> + 1.1.2.3 Permitted
> +
> + This set is the set of capabilities required by the executable in
> + order to do its job. These will appear in the Permitted set of the
> + process after exec()ing this executable.

This is the "forced" set. It is also read as { 0 } on a file system
without capabilities support.

> +
> + 1.2 Capability inheritance
> +
> + Following a sys_exec() call, the process' capability sets are modified
> + in the following way:
> +
> + pI' = pI
> + pP' = fP | (fI & pI)
> + pE' = pP' & fE [NB. fE is 0 or ~0]
> +
> + Key: I=Inheritable, P=Permitted, E=Effective // p=process, f=file
> + ' indicates post-exec().
> +
> + Note, the Inheritable set is fully preserved across a sys_exec()
> + call.
> +
> + Process capabilities are _not_ modified on fork (clone).
> +
> + 1.3 Compatibility mode
> +
> + The historical situation, of the superuser being omnipotent, is
> + partially preserved following a system call to sys_exec(). This

..and on set*uid calls.

> + backward compatibility is preserved only in the case that
> + (issecure(SECURE_NOROOT) == 0).
> +
> + 1.3.1 Details
> +
> + Backward compatibility takes the following form:
> +
> + 1 setuid-root binaries, automatically have their Effective
> + and Permitted capabilities raised to equal the Inheritable
> + capabilities of the process that calls sys_exec().
> +

Yes.

> + 2 setuid-NON-root binaries, automatically have their Effective
> + capabilities cleared. If the UID of the process calling
> + sys_exec() is 0, the Permitted capabilities are raised to
> + equal the Inheritable capabilities of the process that called
> + sys_exec(). Such processes become "capability smart".
> +

Yes.

> + 3 non-setuid binaries directly inherit the capabilities of the
> + process that sys_exec()s them.
> +

No! What happens is the following:

1. When the effective uid or real uid of the resulting process is 0
[ execing a suid-root binary, normal exec by root, or
exec by root who has done a setreuid operation ]
then the allowed set is set to { ~0 }.

2. When the effective uid of the resulting process is 0
[ exec of a suid-root binary or normal exec by root ]
then the forced set is set to { ~0 }.

After the above operations, the capability laws described in 1.2 are
used. This means that as long as the allowed set is read as { 0 } from
the file system, we don't inherit any capabilities at all except for
the inheritable ones. We don't need to in order to be backward
compatible.

A very important part of backward compatibility that should be
mentioned is dealing with the set*uid functions. Current software
relies on losing privileges on a setuid or setreuid operation. This
compatibility is in effect if issecure(SECURE_NO_SETUID_FIXUP) ==
0. It works by letting the effective set be a reflection of the
current effective uid. The permitted set is a reflection of the
current effective/real/saved uid. The following rules are used.

1. When set*uiding _from_ one of {r,e,s}uid == 0 _to_ all of
{r,e,s}uid != 0, the permitted and effective capabilities are
cleared.

2. When set*uiding _from_ euid == 0 _to_ euid != 0, the effective
capabilities of the process are cleared.

3. When set*uiding _from_ euid != 0 _to_ euid == 0, the effective
capabilities are set to the permitted capabilities.

4. When setfsuiding _from_ fsuid == 0 _to_ fsuid != 0, the effective
capabilities of the process are filtered for fs-specific capabilities

5. When setfsuiding _from_ fsuid != 0 _to_ fsuid == 0, the fs-specific
parts of the effective capabilities of the process are copied from
the permitted capabilities.

> + 1.3.2 Discussion
> +
> + Firstly, we note that this backward compatibility has only one effect
> + on systems that are not capability aware:
> +
> + Point 2, of section 1.3.1, implies that such binaries
> + inherit the EUID of root, but none of root's kernel level
> + privileges.
> +

No this is not correct. When root runs a non-root setuid program, the
effective uid of the resulting process is _not_ 0. However, the real
uid is 0. Letting the permitted set of the resulting program be set
but not the effective makes perfect sense.

> + Secondly, on a system that runs binaries which are capability aware,
> + without setting SECURE_NOROOT mode, it is possible to further restrict
> + the root account:
> +
> + Point 1, of section 1.3.1, implies that a parent process
> + can reduce its Inheritable capabilities and thus prevent any
> + of its children from _ever_ acquiring all/any of root's kernel
> + level privileges. An immediate consequence of this is that it
> + becomes possible to implement escape proof sys_chroot() cells.
> +

This does not depend on the SECURE_NOROOT setting. The only way you
can increase your effective capabilities beyond your inheritable ones
is if you somehow, through exec or other means, get "forced"
capabilities. "Forced" capabilities can either come from a
capability-aware file system during exec (which we don't have
currently), or from another process having the SETPCAP capability.

[ patch.. ]
> +/*
> + * The restrictions on setting capabilities are specified by POSIX:
> + *
> + * I: any raised capabilities must be a subset of the (old) Permitted
> + * P: permitted capabilities can only be removed and never added.
> + * E: must be set to a subset of (new) Permitted
> + */

I'd like to see an additional restriction. Many programs do privilege
checking at the beginning and don't correctly check all return values
on system calls. On a capability-system this can probably be exploited
in ways not possible on a normal Linux system by using the inheritable
set. If a normally suid program needs several capabilities to perform
a task, you can engineer an inheritable set which will make a system
call fail that would succeed on a normal system and possibly exploit a
buffer overrun condition. On some systems it is therefore desirable to
be unable to _clear_ capabilities in the inheritable set unless you
are privileged. This also has the effect that you cannot selectively
delegate capabilities to your own child processes unless you are
privileged. When raising capabilities in the inheritable set, this
argument doesn't apply since the process would already be "trusted" by
having some forced capabilities.

The above rules should be disregarded for a process having the
CAP_SETPCAP capability. This makes it possible for a
"capability-daemon" to do some interesting things such as raising a
capability only in the effective capability set of a process. This
makes it possible for the daemon to grant a _non-transferable_
capability, something which is not possible with the POSIX rules
(unless you limit setting capabilities in the inheritable set as
well).

Another point is that the system calls in your patch doesn't support
setting capabilities on other processes. This is needed to implement
the "capability-daemon".

astor

-- 
 Alexander Kjeldaas, Guardian Networks AS, Trondheim, Norway
 http://www.guardian.no/

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu