Re: Bug in how capability inheritance is handled in "fs/exec.c", 2.3.99

From: Theodore Y. Ts'o (tytso@MIT.EDU)
Date: Mon May 29 2000 - 17:32:50 EST


   Date: Mon, 29 May 2000 12:47:40 -0700
   From: Linda Walsh <law@sgi.com>

           Correct. By the existing model we have:
   pP = some vector, say "0001", then PIE=(1,1,1) (by code in suid).
   Now when I execute file backup that has PIE=(0000, 1111, 0010), we
   have:

   Example1: File inherited sets and Effective sets allowed to be > Permitted

           I_1 = I_0 ;I_1=0001
           P_1 = (P_f && X) | (I_F && I_0) ;P_1=0000 | 1111 & 0001
                                           ;=> P_1 = 0001
           E_1 = E_F && P_1 ;E_1= 0010 & 0001
                                           ;E_1 = 0000 (broken,wrong -- you've
                                           ; lost all permissions)

Um, what's broken is the file permissions. If the executable wanted
0010 the capability, it should have been in the fP capset. And if it
wanted the 0001 capability to be set by default when it started, it
should have been i the fE capset, at least according to the Posix.1e
Draft 17 model. So the file PIE should have been (0010, 1111, 1111),
given your (implied) desired results.

I think the formulae in Draft 17 *can* do what is needed. It's just a
matter of setting the bits correctly. If they're different from what's
in Irix, then I'd be interested to see how Trusted Solaris and Trusted
HPUX did things. I'd also like to put this questions to those who were
on the Posix.1e committee, to get some "legislative history" on this
one. You seem to be arguing that they were stupid and didn't know what
they were doing when they produced Draft 17. I don't think that's a
wise assumption to make, especially since Draft 17 was almost ready to
be balloted through ISO when POSIX.1e was killed. Specifically, I don't
think the formulas for calculating capsets post-exec in section 3.1.2.2
are a mistake, as you are claiming.

           The way I envision is the way Casey designed it into IRIX. He,
   I and other team members had considerable discussion on this point a few
   weeks ago -- and over the meaning of a file having no capability set.
   Out of that we came up with files with no set, have '0,0,0'.

I've discussed capabilities years ago with Casey, and I wouldn't mind
having a chat with him myself on this issue. (In fact, I think I owe
him a beer; we need to get together in a bar sometime. :-) I agree that
files that don't have any thing set should have 0,0,0. That makes
perfect sense. However...

   Thus files in the Trusted Computing Base (TCB) can be marked with
   0,1111,0 unless they need privilege -- like, say, tar. So
   'user=backup' runs with (01,all,01), tar has (10,all,10) (or
   10,all,10), doesn't matter.

I don't think so. Given Draft 17, here's how I think things should
work:

* Files that don't have any "forced" (to use a now obsoleted
  term) privileges, but which are allowed to inherit all privileges
  (which I don't think I would normally allow, but we're talking TCB
  program here) would either have:
        - a PIE of (0, all, 0) if they are "capability smart" (i.e.,
                they understand how to raise effective privileges
                themselves, only when they are needed --- see "Principal
                of Least Privilege" in the Rationale section, B.25.1,
                page 310, line 138 for more discussion)
        - a PIE of (0, all, all) if they are "capability dumb" (i.e.,
                they want all permitted privileges raised when they
                start because they don't know how to deal with
                capabilities themselves. See Rationale, B.25.2.4, page
                315, lines 336-346)
* Programs that run with raised privileges, and who want child processes
        to inherit these raised privileges post-exec must explicitly do
        a "pI = pP" (i.e., set their inheritable capset equal to their
        permitted capset, or some subset of their permitted capset)
* Users that receive on login some set of privileges (i.e., your example
        of user=backup getting capability CAP_READ_SEARCH == 01) will
        have this set of privileges set to all bitmasks. pI does *not*
        get set to ~0.

This is, I believe, consistent with what Draft 17 does with its
calculations. As you can see, however, it's quite different from the
assumptions which you're working under. However, I believe that these
set of assumptions plus Draft 17 is self-consistent.

Part of the problem here is that the details of exactly how the bitmasks
work in Posix.1e have been changing over time. At one point, for
example, (back when Posix.1e was Posix.6, before the "great renaming")
only had the "forced" bitmask and the "allowed" bitmask, where
"allowed", and the algorthm used at that time was:

Permitted = (Inheritable_original & Allowed_executable) | Forced_executable

As another example, some older versions of Posix.6 (now Posix.1e) had
four process capsets: saved, effective, permitted, inheritable.

Obviously, as the algorithms have changed over time, even minor changes
in the forumlas for how the capsets are calculated post-exec can make
very big differences on how things work. I remember (way back when)
having major arguments with some folks about how things worked, and it
turned out that it was because I was using Draft 13, and they were using
some later draft of .1e, and there had been some major changes between
draft versions. So it wouldn't surprise me if what trusted Irix
implemented wasn't exactly Draft 17. In fact, looking at the change
bars in B.25.2.3 and in 3.1.2.2 in draft 17, it's seems pretty clear
that these changes were made in the last draft.

However, that doesn't mean (as you are arguing) that Draft 17 is broken.
Some of the assumptions you have made that were valid for, say, Draft 15
or Draft 16, mays imply have since changed since the last version of
.1e, draft 17, was released.

Now, if you want to make arguments based on such as "draft 17 requires
changes to more programs to get the desired results", that's fair. But
at the same time, I think it's also fair to say that draft 17 requires
explicit capability management because you get more security that way.
Is the tradeoff worth it? That's a good question, and worthy of
debate.

But please, let's be really explicit about the assumptions that we're
making when we make our arguments; unfortunately, Posix.1e has changed
over its various edits, and I think you have been using assumptions from
an earlier draft when you try to tear apart Draft 17.

                                                        - Ted

[ At this point most people will probably want to stop reading; if they
  haven't stopped reading already. :-) Below is a blow-by-blow
  commentary of the rest of Linda's reponses. They're probably not
  interesting to most people, which is why I've moved them last. ]

           NO -- backup only needs CAP_RAW_IO to write to its tapedrive
   device......

Sigh, you're playing with different rules than I was. First of all, my
example was only exampled one bit --- CAP_DAC_READ_SEARCH --- and
ignored all others --- to simplify the discussion. Also, I was assuming
that tar was going to be writing directly to tape (which is why it
needed CAP_DAC_READ_SEARCH), and that backup was a restricted program
which would only run as root. You changed the basic assumptions, and
raised issues based on the contradictions caused by the changed
assumptions which you made.

           login, running with PIE=(all,all,all) now wants to "log in"
   user 'backup'. It forks and sets PIE=(01,all,01) (using 01 for
   DAC_READ_SEARCH). This works. So pPIE=(01,all,01) (note that
   Inheritable set is not a sub set of the Permitted set -- this is
   inconsistent). Now tar only wants to be able to access raw tape
   devices but wants to allow other bits like "DAC_READ_SEARCH) to be
   inherited in from the previous process fPIE(tar)=(10,1111,10), so
   it's inherited mask is also set to 'not a subset' of the Permitted or
   Effective sets).

   So we have example 4:

           I_1 = I_0 ;I_1 = 1111
           P_1 = (P_f && X) | (I_F && I_0) ;P_1 = 0010 | (1111 & 1111)
                                           ;P_1 = 1111
           E_1 = E_F && P_1 ;E_1 = 0010 & 1111
                                           ;E_1 = 0010

   So now you have a Permitted set of "1111" (BAD -- another bug), and E=0010.
   Again -- not what was desired which was 0011.

Again, what's broken is that you're assuming certain things about how
file CAPSETS should be set that agreed, don't work with Draft 17. The
question is whether Draft 17 is broken, or whether your assumption about
how to set the file CAPSETS are broken.

First of all, the login should set PIE of the shell process to
(01,01,01). (Why should the process Inheritable set be set to all?
This doesn't make any sense.) Secondly, the fPIE(tar) should be
(0010,0011,0011). That is, it is automatically given CAP_RAW_IO (here
assumed to be 0010), but it can inherit CAP_READ_SEARCH (here 0001) and
it is given CAP_RAW_IO and CAP_READ_SEARCH (if available) immediately
upon execution, since it's in fE.

> Your proposed inheritance scheme (repeated above) does make a certain
> amount of sense. Note, though, that if a program explicitly sets its
> Inhertiable capmask to be equal to its Permitted capmask, then the Draft
> 17 rules becomes almost identical to what you've asked for.
   ---
   Your terminology is unclear -- by "program explicity sets", do you mean
   the current process, the new process, or the PIE vector stored on disk for
   the file? The first choice has insufficient information to compute the
   new PIE vectors. The 2nd choice doesn't affect inheritance because it
   is done after inheritance was applied by exec, so assuming the third
   case:

No, that's not what I meant. What I meant is that current process
wishes the the exec'ed to inherit its capabilities, it should set its pI
capmask. I.e. the program would

        1) optionally fork()
        2) pI = pP (allow exec'ed process to have all my caps)
        3) exec program

Applying pI = pP, and then doing an exec, means that the newly exec'ed
process will have:

           pI' = pI = pP
           pP' = fP | (fI && pP) = fP | (fI && pP && pI)
           pE' = fE && pP'

If you compare this to your desired formula:

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

You'll see that they really aren't that different. The big difference
is that using the Draft 17, the process has to explicitly give permisson
for its exec'ed child to inherit its permissions, by setting pI = pP
before doing the exec.

Is this bad? Is this good? It depends on your point of view. It does
mean that you may have to change a few more programs in order to
explicitly allow privilege inheritance to happen. This can be
considered either a bug or a feature. It's a feature in that you don't
have to worry about an application accidentally handing down privileges
it didn't anticipate handing down to a child process. This can enhance
security. As you point out, it means that you may have to modify more
programs to be capability aware. Life is full of tradeoffs.

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Wed May 31 2000 - 21:00:22 EST