[cap-talk] More Heresey: ACLs not inherently bad
David Wagner
daw at cs.berkeley.edu
Thu Oct 9 12:27:26 CDT 2008
Marcus Brinkmann wrote:
>At Thu, 2 Oct 2008 17:09:19 +0000, "Karp, Alan H" <alan.karp at hp.com> wrote:
>> The problem is that the compiler needs to exercise rights from two
>> sources, the user's rights to read the input file and write the
>> output file, and the compiler's right to update the billing file.
>> If the compiler uses setuid to run as the user, it can't update
>> billing file. If it runs as itself, it will clobber the billing
>> file with the output.
>
>These are false alternatives. Unix is more powerful, and I am sure
>you know how this can be implemented correctly in Unix using file
>descriptors.
Sure. File descriptors are capabilities.
A capability-like way to fix the confused compiler is for the
user to provide the compiler with file capabilities rather than
file names. One way to do that in Unix is to pass a file descriptor.
More importantly, the claim is not that it is *impossible* to implement
this securely in Unix; the claim is that this is an easy mistake to make,
while capabilities help you avoid this mistake.
>1. A setuid process can change its effective user ID at any time with
>seteuid(). This way, the compiler can run most operations under the
>user ID of the caller and only update the billing file with the user
>ID of the compiler administrator.
The confused deputy risk is that, if you forget to (or fail to)
change uids at every point where it is necessary, then you will
be subject to a confused deputy vulnerability. It's easy to make
this mistake. Not for nothing is it said that writing secure
setuid programs is incredibly challenging.
P.S. In case remembering to change uids at every point where that's
necessary sounds easy to you, let me mention a few subtleties to
suggest that this is not necessarily as simple as it sounds:
* If a signal handler executes while you're in the middle of
a code block where the euid has been temporarily changed, the
signal handler may execute with unexpected privilege.
(Remember the tractorbeaming attack?)
* An early return or longjmp in the middle of such a code block
can introduce bugs. e.g.,
...
seteuid(compiler_id);
fd = open("billingfile", O_RDWR);
if (fd < 0)
return -1; <--- BUG!
seteuid(getuid());
* The Unix set*uid() system calls are notoriously unportable,
confusing, non-uniform, and error-prone. See, e.g.,
http://www.cs.berkeley.edu/~daw/papers/setuid-usenix02.pdf
>2. There is a special function, access(), which checks access to
>resources using the real user ID, not the effective user ID. This can
>be used to verify access to the billing file before writing.
Yikes! Wash your mouth out with soap! access() is insecure
and should never be used. (TOCTTOU/race conditions)
>3. More secure is to open the billing file under the compiler
>administrator user (using seteuid), keep the file descriptor open and
>then use setreuid() to drop the compiler administrator privilege.
>This is of course closer to the capability model, as the file
>descriptor now essentially is the compiler administrator capability.
Right.
>Alternatively, you can get even closer to the capability model by
>dropping the *user* privilege after opening input and output files,
>and only run under the file user ID. This is possible in Linux, but
>it's not necessarily possible in every POSIX system.
I'm not sure what you meant here; I don't know of any reasonable
way to drop all privilege. (If you're referring to the fsuid, in
the scenario you're talking about, the fsuid will be the same as
the uid of the invoking user, once you drop the privileges of
the compiler administrator.)
>This demonstrates that Unix systems already make use of ACL and
>capability system functionality.
>
>The ACL method using different user IDs can be difficult to get right,
>and it has a lot of hair attached to it. This is particularly true if
>you consider multi-threaded applications and various extensions, like
>per-thread user IDs. But, it is there, and it can be used to get
>correct behaviour, contrary to what you said.
I agree with all of this except for the "contrary to what you said";
I don't think it contradicts any of the standard claims about confused
deputies. Confused deputies are a hazard; but they're not inevitable.
>As for carol's copy service:
>
>> Here's an example I've been using. Alice wishes to use Bob's backup
>> service, which is implemented by using Carol's copy service.
>>
>> Alice: bob.backup(foo)
>> Bob: carol.copy(foo,bar)
>> Carol: copy(a,b) { b.write(a.read()); }
>>
>> Show me how to do that with setuid without violating least privilege.
>
>As this is an abstract example, it can't provoke more than abstract
>interest in me. There are relatively simple solutions on any Unix
>system:
>
>1. Carol's copy service can run with root privileges. Then it can
>assume any user ID, including that of Alice or Bob.
This of course violates least privilege.
>2. As is well known, capabilities can be emulated with an inflation of
>ACL IDs: A group carol-and-alice can be used to give Carol access to
>Alice's file, and a group carol-and-bob for the mediation of Carol and
>Bob.
This is an imperfect emulation. Capabilities are specific not just to
the identity but to the *instance* of the object. For instance, Alice
might run 100 different programs; in Unix, they all receive all of Alice's
privileges, but in a capability system, each instance of each program can
receive a different set of privileges. That cannot be emulated with a
"carol-and-alice" group.
But the biggest problem with this approach is probably that it cannot be
used by ordinary users; it requires special cooperation from the sysadmin.
Alice and Carol cannot, on their own, create a "carol-and-alice"
group upon demand -- they have to ask the sysadmin to do it for them.
Capabilities try to allow the users to manage their own privileges without
relying upon cooperation from a system administrator. This is crucial
for least privilege: if you put barriers in the way ("you have to ask
the sysadmin to help"), adoption is going to be significantly weakened.
>Don't knock the first two options too quickly. To pull your example
>back into the real world: three-way negotiations are very complicated
>and tricky business. Until you go into more detail, I am going to
>assume that the negotiations between Alice's, Bob's and Carol's
>lawyers and the resulting contracts and business models are going to
>cost many times more than a dedicated machine for Carol on which the
>copy service is implemented, or a contract between Carol and root for
>running the copy service or at least for creation of the necessary
>user IDs.
I assumed that Alice, Bob, and Carol refer to 3 chunks of code -- not
necessarily 3 different people, let alone 3 different organizations.
I don't know if that's what Alan intended or not.
(Keep in mind the common convention among computer scientists to give
human names and ascribe intentionality to a chunk of code. This practice
can sometimes be confusing and misleading; other times, it can aid the
intuition. If a computer scientist starts talking about Alice and Bob,
often a good question to ask is whether we're talking about two people,
those two people's computers, or two code modules that are interacting
with each other.)
More information about the cap-talk
mailing list