No subject


Fri Feb 24 02:21:12 GMT 2006


to get, so here is the tale from the perspective of the humbled
implementer.

The Linux kernel at this time has no support for filesystem
capabilities. So, the implementation of capabilities is a little
crippled.

The equations for evolving capabilities through 'exec' are these:

p=process, f=file ; P=permitted,I=inheritable,E=effective ; B is a
system-wide bounding set.

1) pI' = pI
2) pP' = (fP & B) | (fI & pI)
3) pE' = fE & pP'

Since there is no support for file-capabilities, there is no way to set
fP, fI and fE on regular files and the values of these capabilities are
naturally fP=fI=fE=0. This naturally leads to capability free processes
that emerge from exec: (pE'=pP=0).

The legacy support for the concept of a superuser, is implemented by
recognizing setuid-0 binaries and euid=0 users and faking the
capabilities for the file being exec()d.

In the old (exploitable) system, the scheme for this faking was:

  if the exec()d program will have euid=0: fE=~0.
  if the exec()d program will have euid=0 or uid=0: fI=~0.
  in all cases fP=0.

Note, in this old (bug-susceptible) system since there was no
possibility of getting fP!=0, the actual rule (2) was (effectively):

2) pP' = (fI & pI)

And B was implemented as a dynamic system-wide bounding set on pE that
one could manipulate asynchronously with respect to the execution of a
program, _and_ have its bounding effect alter the behavior of the whole
system asynchronously. This was not the model that POSIX was pursuing.

The key here is that all access to capabilities was leveraged off pI
which for init[1] was almost pI=~0, and via rule (1) was propagated down
the chain of process execution and picked up whenever fI was seen to be
non-zero. The intentional, but unfortunate, feature was that one could
firewall the potential power of superusers by clipping pI at login and
with custom wrappers etc. . Cute no? No.

In the new Linux 2.2.16 (resilient) system, the scheme for this
(filesystem capability) faking is different:

  if the exec()d program will have euid=0: fE=~0.
  if the exec()d program will have euid=0 or uid=0: fP=fI=~0.
  in all cases fP=0.

And 'B' is just enforced at exec() time.

The distinction between the before and after models is that since it is
an unprivileged operation to lower the pI set, 'before' it was
effectively an unprivileged operation to lower the pP=pE set of legacy
euid=0 programs. 'After', because we can pick up capabilities from both
pI _and_ B (since fP is not always 0), it has become a privileged
operation to turn off capabilities in euid=0 processes. That is, to do
it you need to be able to manipulate B.

> I'm not sure which of these techniques the authors of POSIX.1e had in
> mind, and to what extent that influences the possibility of vulnerability
> to this bug.  In any case, it represents an interesting situation in which
> attempting to decompose a central concentration of privilege by
> introducing a new model for authorization results in a weakness.  It also
> reminds us that having well-defined syntax and semantics for
> capability-management calls would be a good idea :-).

Indeed. At the heart of this problem is the ambiguity in the correct
functionality of setuid(). For privileged users it does one thing, and
for unprivileged users it does something else. For legacy applications
in a capability framework, there is no clear way for the legacy
application to know if (and by how much) it is privileged or not.
Instead, by the way it was written an application effectively makes an
assumption that if the setuid() call returns success it must have done
what the application author intended it to do. In the case of sendmail,
and to their credit, they are now able to recognise a broken Linux
kernel and refuse to work with it, but the bug was really the kernel's
undocumented and unexpected deviation from legacy behavior (which is now
fixed).

The lesson to me seems to be acknowledging what fP represents (and I
shall quote Linda Walsh from a recent discussion):

> "fP" is the minimum Privilege vector that a file needs for normal
> execution over the life of the program.

Namely, there _is_ a minimum - due to strange dual-purpose system calls
like setuid() - and great care should be taken to honor this minimum.
Especially with respect to legacy applications.

Cheers

Andrew
To Unsubscribe: send mail to majordomo at cyrus.watson.org
with "unsubscribe posix1e" in the body of the message



More information about the posix1e mailing list