lkml.org 
[lkml]   [1998]   [Apr]   [29]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
Date
From
SubjectRe: [patch 2.1.97] more capabilities support
Alexander Kjeldaas wrote:
>
> On Mon, Apr 27, 1998 at 02:26:22PM +0200, Andrej Presern wrote:
> > Theodore Y. Ts'o wrote:
> > >
> > > Huh? That's hardly a pure-capability system. In a pure capaibility
> > > system, you have something that is effectively like a "key", which can
> > > get passed around between processes, and a privileged program is merely
> > > a program that has several keys that confer more abilities than the
> > > standard default capability.
> >
> > Don't you think that the two syscall capabilities are essentially two
> > 'keys' that 'open different syscalls'? I designed the syscall capability
> > the way I did exactly to demonstrate that you don't neccessarily have to
> > pass the capability like a fd when you invoke it, keeping existing
> > syscall interfaces unchanged.
> >
> > I stated in the mail that for starters I don't want a process to be able
> > to pass the syscall capability around to other processes (which it could
> > nonetheless if we don't tie the memory containing the copy of the
> > sys_call_table to the process that requested it).
> >
> > You don't need a special authority granting daemon to have pure
> > capabilities because every object can have its own set that it can give
> > away. In the case of a syscall capability that I described, the kernel
> > gives a single syscall capability to all processes by default, and if
> > the process wants it can create its own subset of the system calls that
> > it received. As I already stated, if we don't tie this syscall
> > capability to the process (that is if we don't free the memory that it
> > occupies when the process exits), a process could as well communicate it
> > to other processes (the reference is just 4 bytes) if, of course, it
> > includes the authority to call system calls that can be used to
> > communicate with other processes.
> >
>
> What you are suggesting is simply a less useful version of POSIX
> capabilities. They are
>
> 1) less expressive
>
> I mentioned a few things you couldn't express with your model in
> another post, but let me just mention that you can express
> _everything_ you can do with _your_ model using POSIX capabilities.
> I have already described this in an earlier mail when someone
> wanted a CAP_NETWORK capability to be able to restrict access to
> the network to a process. This requires the following changes to
> the kernel which I didn't want to do to avoid complexity at this
> time.
> 1.1) You have to read some capabilities as "set" in the allowed
> set when reading them from the file system.
> 1.2) You have to add credentials checks to the kernel that
> currently don't exist.
> The reason you don't have CAP_EXEC and CAP_NETWORK isn't that it is
> inherently difficult to do, but that we want to take one step at a
> time. Partitioning the root privilege is a well-defined step.

install_sys_call_table( without_socket );
(But this is not the issue.)

If we talk about pure capabilities, we talk about expressivity. A pure
capability system will perform an action for you if you are able to
express the request. In contrast, a list based system will let you
express the request and then verify if the request should be performed
for you (ie if you are authorized).

In a drawing, the difference would be something like this:

Lists:
+----+ +-------+
|obj1| ---request----|-->obj2|
+----+ +-------+
After the border of authority has already been passed, obj2 checks if
obj1 is authorized for the action, and if not, the action is denied and
the execution returns to obj1.


Capabilities:
+----+ +----+
|obj1|---request---->|obj2|
+----+ +----+
The border of authority is not passed, for if it were, obj2 will simply
perform the action without any checking. If obj1 is not authorized to
request that an action be performed for it by obj2, obj1 cannot even
pass the border of authority. This means that obj2 doesn't have to
execute not even a single instruction if obj1 doesn't have the
authorization to invoke a capability on it.

Because a capability can have an arbitrary amount of authority, it can
have an arbitrary amount of expressiveness. But as you can see, the
issue is not about how much you can express in a request but rather _if_
you can express the request, because the latter is what makes the
difference between lists and capabilities.
You stated in your mail (as quoted) that you didn't want to do
CAP_NETWORK yet because it would introduce more complexity to the
system. The always increasing complexity is exactly one of the
disadvantages of a lists design! With every additional authority you
need an additional check/branch in the program that will handle it, for
if you don't you have a security hole. This means that an additional
amount of code is needed to accomodate the check, which then reflects in
increased complexity, increased storage use and worse performance.

With pure capabilities on the other hand, time and storage required to
perform a check is always constant: 0 (zero), because the check is done
implicitly at the object border, so the object does not have to waste
any resources to perform it explicitly.

> 2) not pure
>
> I can emulate your sys_call_table with a 183 bit long integer
> with each bit corresponding to being allowed to invoke a system
> call. How can you argue that POSIX capabilities are inherently
> broken when the similarities between POSIX and your suggestion are
> so obvious?

You only emulate what you already have: a lists design. That is also
exactly the reason why the proposed extension may seem as not very
efficient: it is indeed an emulation of a lists design, which is needed
however to support backward compatibility.

While the proposed extension suffers from increased use of memory, it
still sports good performance, which is not so if we used a 183-bit list
as you proposed. Consider the following:

sys_call_table
->fork
->read
->write

If you want to apply a lists design to verify that an application is
allowed to perform a system call (say, write()), you can nullify the
pointer to write and then at syscall time check if the value of the
pointer is NULL, or create a different list using a bit-array and check
if the corresponding bit is zero:

sys_call_table
->fork
->read
->NULL

Such an approach involves a check in the code, such as
if ( ! is_syscall_valid(num) ) return;
do_syscall( num );
This can be modified to use some capability ideas. Instead of storing a
NULL pointer in the sys_call_table, we store a pointer to a dummy
function that returns from the syscall, effectively revoking the write()
capability from the application:

sys_call_table
->fork
->read
->dummy_exit

Now if the application requests write(), the operation will
automatically fail because dummy_exit() will be called instead of
write(), no checks performed.

The code would now be something like:
do_syscall( num );

To give a capability to write(), the kernel only needs to replace the
pointer to dummy_exit() with a pointer to write(). If the kernel wants
to perform some action when a syscall is attempted that is not
permitted, kernel only needs to change the pointer to dummy_exit() to a
pointer to log_breakin_attempt() or kill_offending_caller(). The kernel
might even want to play games with the offending application, so it
might store a pointer to fake_write() instead of write() to see what the
application is up to and the application will never know that it is
being monitored. Please note that the complexity of the system call
mechanism has still not changed even though we're now already doing an
analysis of the breakin attempt.

> 3) they require 60 times the memory, and
> 4) are harder to administer, and
> 5) are probably slower

As you can see, the above mechanism can hardly be slower than what you
propose, and I'm sure you will also agree that it's also easier to
administer. As for memory, the reason for increased memory is because
we're emulating a lists design, where the whole list needs to be
initialized if it should work. If the syscall mechanism wasn't
fundamentally broken in the first place, the emulation would not be so
inefficient memory-wise.

> I think simply the cache effects of having different
> system_call_tables makes them slower than looking up a bit in a
> table. However, if this isn't true for some reason, there is
> nothing stopping us from implementing for instance CAP_SYS_RAWIO
> by removing iopl and ioperm from the system call table of the
> process.

... and adding an additional entry in the list of capabilities, making
things even less efficient.

> [...]
> > The other way to abuse the program is that the attacker installs the
> > syscall capability that contains execve() by itself before calling
> > execve(). But this means that the attacker must have such a capability.
> > And because it can't just produce one by itself, and because it can't
> > get one from the system (it can only get a copy of the currently
> > installed one) the only way to get it is by stealing it from the
> > attacked process (and to do that it must know exactly where the attacked
> > process holds it), which complicates things even more.
>
> If you want it to be difficult to steal the capability from the
> running process, you will have to _design_ it to be difficult. Until I
> see some evidence suggesting it is difficult, I'll assume it is as
> easy as stealing POSIX capabilities.

Can you please explain to me how you can steal a file descriptor in
Linux?

Andrej

--
Andrej Presern, andrejp@luz.fe.uni-lj.si


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

\
 
 \ /
  Last update: 2005-03-22 13:42    [from the cache]
©2003-2014 Jasper Spaans. Advertise on this site