[Apparmor-general] Moving To A Pure Capability Model?

Crispin Cowan crispin at novell.com
Fri Jan 12 01:08:11 MST 2007


Replying to my own post, as this discussion needs to be revived.

The current revalidation code takes file descriptors as they "arrive" in
a security context (profiled process) and recovers the path for that
file, then checks whether the profile has access to that pathname. The
nice thing about this behavior is that it recovers the *current* name of
the file, even if it has been renamed since it was opened. This provides
an intuitive view of whether the confined process gets to access the
file, e.g. renaming the file out of range actually blocks access.

The problem with the current behavior is this dynamic lookup: if the
file has been deleted, its parent directories have been deleted, it is
residing on a file system that has been lazily unmounted, etc. then you
can get a garbage name recovered for the file descriptor. It also
involves some ugly kludges that will be unappealing for an LKML upstream
submission. So this code, at least, must go.

Our options going forward are:

    * Completely remove all revalidation, producing the pure capability
      model discussed previously in this thread.
    * Switch to a static revalidation model, where a file descriptor is
      essentially tagged with the pathname it had when it was opened,
      and revalidation of passed-in FDs is with respect to the file's
      original name, and does not notice any renaming that might happen.

For purposes of upstream submission, we are going to go forward with
first removing the existing revalidation, producing an alpha release of
AppArmor as a pure capability model. Based on experimentation with that
release, we hope to get a more informed opinion on whether the static
revalidation approach is called for, or if the pure capability model is
sufficient.

There remains a security risk from totally uncontrolled sharing of FDs,
which is that they have been known to leak unintentionally across
exec()s. To mitigate this threat, we will:

     * Close all open FDs on exec by default
     * Provide an extended capability (smells just like a POSIX.1e
capability, but named "pass_fd_on_exec" or such like) that if enabled
restores the classic behavior
     * similar capability for closing the special stdio FDs

Naturally, discussion and user feedback is encouraged.

Crispin

Crispin Cowan wrote:
> Mark Seaborn wrote:
>   
>> Crispin Cowan <crispin at novell.com> wrote:
>>   
>>     
>>> And we would have these costs:
>>>     
>>>     * Seth found at least 7 vulnerability disclosures in the last 2
>>>       years pertaining to Linux in which file descriptors were
>>>       unintentionally leaked from one process to another. AppArmor
>>>       profiles currently have a chance of stopping this kind of
>>>       vulnerability, but under the pure capability model they would not.
>>>     
>>>       
>> I'd be interested to see the list of those vulnerabilities.  Do the
>> programs involved consistently leak file descriptors, or does it only
>> happen occasionally?
>>   
>>     
> My impression is that the bugs were largely failure to set the "close on
> exec" bit when the file was opened, and so when the privileged program
> exec'd a non-privileged child, the FDs were still open. There was much
> public fuss about this on IIRC Bugtraq many years ago, resulting in
> stdio FDs 0, 1, and 2 being closed on exec by default. But other FDs
> remain problematic, because applications can and do get it wrong.
>
>   
>> A possible solution could be to make the close-on-exec flag "on" by
>> default, on a per-profile basis.  This would then be set to "on"
>> before a program is allowed to use setuid().  The small number of
>> programs that both use setuid() and pass FDs across execve() would
>> need to be changed to carefully unset the close-on-exec flag on the
>> relevant FDs.
>>   
>>     
> We discussed that at our internal in-person meeting:
>
>     * Changing the default globally is likely to break lots of programs;
>       unacceptable without huge community buy-in.
>     * We could change the default for confined programs, and then add a
>       new "capability" to manage the default behavior. This is workable,
>       but has a problem: it is very difficult to "learn":
>           o Many programs in learning mode will do an exec leaving some
>             FDs open. Some are intended, some are not.
>           o How to detect the intentional open FDs? You have to
>             instrument the child processes and all their descendants so
>             that if they ever use the FD, the original opener profile is
>             marked as "allowed to exec with open FDs".
>           o You also have to mark the intervening processes as allowed
>             to exec with open FDs.
>
> This is doable, but very complex to implement. So the sub-question is
> that if we proceed to make FDs into first class capabilities WRT
> AppArmor, do we also implement this complex mechanism to close the 7 or
> so vulnerabilities of leaking FDs? Or just shrug it off?
>
>   
>> For debugging purposes there could be a third close-on-exec state,
>> error-on-exec.  If any FDs with this state are open when execve() is
>> called, it would give an error (and maybe terminate the process), and
>> log the filenames of the relevant FDs.  (Since these filenames are
>> only used for debugging and not for making security decisions it
>> doesn't matter that you can't uniquely determine the FD's original
>> filename.)
>>   
>>     
> We could do that, but what would it buy us? I'm not claiming it doesn't
> have advantages, I just don't see them.
>
>   
>>>     * change_hat would become particularly porous. Any file that is open
>>>       at the time you call change_hat would remain accessible inside the
>>>       hat, even though the hat grants no access to them. This condition
>>>       is different than the exec case, because hats are interleaved with
>>>       execution of the outer profile, where as all the other "passing"
>>>       conditions are one-way transitions.
>>>     
>>>       
>> This may be impractical, but could each hat have its own file
>> descriptor table?  There could be a way of copying FDs between tables
>> upon a change_hat call.
>>
>> By the way, how does change_hat interact with threads?  Are "hats"
>> per-thread?
>>   
>>     
> change_hat is called that to be evocative of what's going on. It is a
> state that the process can be in at a given time; the process is
> "wearing" one hat or another, or none at all, and thus governed by the
> outer profile.
>
> However, there is no memory safety in change_hat, so it is only useful
> in cases where other mechanisms provide memory safety. change_hat is
> used with Apache and mod_apparmor to apply a different policy to
> scripted programs interpreted by mod_perl and mod_php. Because it is
> difficult for script code to access apache's memory directly, this is
> relatively safe. But for truly concurrent threads that have full access
> to each other's memory, change_hat is pretty useless.
>
> Crispin
>
>   

-- 
Crispin Cowan, Ph.D.                      http://crispincowan.com/~crispin/
Director of Software Engineering, Novell  http://novell.com
     Hacking is exploiting the gap between "intent" and "implementation"






More information about the Apparmor-general mailing list