On Thu, Apr 23, 2015 at 11:48 AM, Linus Torvalds
<[email protected]> wrote:
> On Thu, Apr 23, 2015 at 10:57 AM, Linus Torvalds
> <[email protected]> wrote:
>>
>> Same goes for uid etc - if you are implementing a service daemon, the
>> uid of the requester sure as hell makes a ton of difference in what
>> you might want to expose. Things like "does this user have access
>> rights to the printer?" are very natural questions to ask.
>
> Hmm. Looking at the code, it strikes me that not only does
> kdbus_meta_proc_collect() collect too much, but some of what it
> collects it just seems to do *wrong*.
>
> So I agree with collecting user and credential information (obviously
> unlike some people ;), but I think the code that does it is just
> wrong.
Can I try again, this time without reference to the rest of this
thread from hell?
Let's say I have a shiny kdbus system. I'm generally okay with my
printer daemon finding out that the Angry Penguins app I'm printing
from is uid 1000, that it has the admin's blessing to print, and even
that it's in such-and-such cgroup. That's not why I dislike this.
Objection 1: This thing is omnidirectional. I'm much less convinced
that it's okay for Angry Penguins or its associated ad network to find
out that the printer daemon is uid 38, that it's in cgroup
such-and-such, or that the printer daemon has the admin's permission
to feed the walruses.
Objection 2: There's a difference between the printer daemon knowing
that Angry Penguins has general permission to print and an explicit
assertion by Angry Penguins of its permission to print. Suppose that
printing is implemented by having Angry Penguins call the method Write
on some kdbus thing. Suppose further that changing root's password is
implemented by having the caller call the method Write on some other
kdbus thing. Before changing the password, the password daemon makes
sure that the caller (or the caller's kdbus conn or whatever) has
password-changing permissions. Before printing, the printer daemon
makes sure that the caller has printing permissions.
In kdbus, this is IMO a big problem. See, I can try to find some
setuid root program that takes a printer object as input (however
kdbus might do this -- presumably it would be an object name) and
calls Write to print some diagnostic thing. Now I just feed it the
password-changing thingy as input and I can get it to "print" to the
root password. Oops.
With sensible APIs, this problem goes away. If the actual API were:
call_a_method(object, method name, args, privileges to assert);
then it's a big red flag when a program calls:
call_a_method(untrusted_object, "Write", untrusted_data, all_my_privileges);
This is just like how open(untrusted_path) is bad news and everyone
knows it, whereas write(untrusted_fd, untrusted_data, len) is supposed
to be fine (see, for example, stdout in every setuid program ever).
In the model where system services get to know the identity of the
callers (ok so far) and where every single method call allows the
service to know every possible credential of the caller (kdbus with
sd-bus) and where those credentials are used for authentication
(probably every kdbus daemon ever), it follows that every method call
asserts every possible permission, and we're screwed.
As you pointed out with cmdline, it's not so easy to disentangle
knowledge of some attribute of a caller with use of that attribute as
authorization. So, if we give all the system daemons (and Angry
Pigeons!) the ability to know the credentials of their callers on all
method calls, then they'll use it for authorization, and now all
privileges are potentially dangerous merely to possess for all method
calls.
IMO we need an API where assertion of a privilege is Totally Obviously
Explicit And Clearly A Thing You Only Do With Careful Thought And Is
Expressed As Such In A Method Call (). SCM_CREDENTIALS gets this
wrong, and so does kdbus (and especially sd-bus).
(Alternatively, in a capability-based security model, access to the
password setting object *is* the permission to set passwords, and
there are no implicit/ambient privileges at all, so this is all moot.)
--Andy
On Thu, Apr 23, 2015 at 12:41 PM, Andy Lutomirski <[email protected]> wrote:
>
> Objection 1: This thing is omnidirectional. I'm much less convinced
> that it's okay for Angry Penguins or its associated ad network to find
> out that the printer daemon is uid 38, that it's in cgroup
> such-and-such, or that the printer daemon has the admin's permission
> to feed the walruses.
I think that is valid. It's one thing to send a request to somebody
else and say "I am so-and-so". I think that when you connect to
somebody else, that somebody else should be able to know who the
requester is.
But the service provider has no such onus on it. I agree.
> Objection 2: There's a difference between the printer daemon knowing
> that Angry Penguins has general permission to print and an explicit
> assertion by Angry Penguins of its permission to print.
Now, this I don't necessarily agree with. Simply because what you
propose seems to require everybody to do something extra, and that
adds complexity.
When you open a file, you don't object to the fact that the VFS layer
uses your privileges to check whether you can do that. It would be
insane to say that the open system call should have an explicit
argument saying that the vfs layer should take your privileges into
account.
(Which is not to say that that _can_ make sense - if you implement a
server in user space, you might want an interface that says "open
using these credentials". So such a model is not insane per se, but it
*does* imply a lot more complexity, and there's a reason we don't do
that. We *have* done user-level file servers in Linux, and the way it
was done was that the open system call uses the current thread
privileges implicitly, and the server that wants to do the odd and
complex unusual thing has to explicitly modify its privileges around
the open call!).
So I think your second argument is weak. It doesn't actually match
what we do normally in Unix. Yes, the mindset _can_ make sense, but
it's different from normal behavior, and it's not at all clear that it
is a good thing, because it introduces new issues and complexities
along the lines of "how do I assert that I want you to check my
credentials for *this* particular use (printing) but not for *that*
particular use (passwd).
IOW, your model seems to imply that you have to surround everything
with yet another layer of enumeration of all the possible things you
might want to do. And that seems just a bad idea to me. Why would
system daemon permissions be so magically different from all the
*native* permission handling we do?
It's not like you open /etc/passwd with some special flag that says "I
want to edit my password". No. You open /etc/passwd with the normal
unix credentials.
Linus
On Thu, Apr 23, 2015 at 2:05 PM, Linus Torvalds
<[email protected]> wrote:
> On Thu, Apr 23, 2015 at 12:41 PM, Andy Lutomirski <[email protected]> wrote:
>> Objection 2: There's a difference between the printer daemon knowing
>> that Angry Penguins has general permission to print and an explicit
>> assertion by Angry Penguins of its permission to print.
>
> Now, this I don't necessarily agree with. Simply because what you
> propose seems to require everybody to do something extra, and that
> adds complexity.
>
> When you open a file, you don't object to the fact that the VFS layer
> uses your privileges to check whether you can do that. It would be
> insane to say that the open system call should have an explicit
> argument saying that the vfs layer should take your privileges into
> account.
>
> (Which is not to say that that _can_ make sense - if you implement a
> server in user space, you might want an interface that says "open
> using these credentials". So such a model is not insane per se, but it
> *does* imply a lot more complexity, and there's a reason we don't do
> that. We *have* done user-level file servers in Linux, and the way it
> was done was that the open system call uses the current thread
> privileges implicitly, and the server that wants to do the odd and
> complex unusual thing has to explicitly modify its privileges around
> the open call!).
>
> So I think your second argument is weak. It doesn't actually match
> what we do normally in Unix. Yes, the mindset _can_ make sense, but
> it's different from normal behavior, and it's not at all clear that it
> is a good thing, because it introduces new issues and complexities
> along the lines of "how do I assert that I want you to check my
> credentials for *this* particular use (printing) but not for *that*
> particular use (passwd).
>
> IOW, your model seems to imply that you have to surround everything
> with yet another layer of enumeration of all the possible things you
> might want to do. And that seems just a bad idea to me. Why would
> system daemon permissions be so magically different from all the
> *native* permission handling we do?
>
> It's not like you open /etc/passwd with some special flag that says "I
> want to edit my password". No. You open /etc/passwd with the normal
> unix credentials.
In the case of open, though, users know, or at least should know, that
open(2) checks fsuid, fsgid, LSM labels, and effective caps. If you
want to open with less than your full privileges, you need to change
fsuid (setresuid or setfsuid) and fsgid. If you're not aware of
capabilities, that's enough -- we have very careful code that makes
sure of this, because the alternative would break things. (This is
all the setuid fixup mess in commoncap.c.) We *need* that code
because there is a large installed code base from the non-Linux or
non-caps world that things that setresuid + open is safe.
If you're caps-aware, then you do a prctl to tell the system that and
you mess with effective caps. Still good.
With LSMs, it gets murky, but we trust the writers of policies to get
this right by magic or by patching every daemon on their system [1].
As a result of all this, we really can't get away with adding a new
per-task bit "can feed walruses" that's checked on open because
setresuid won't temporary clear it and vsftpd might start
inadvertently feeding walruses. We can, however, safely add such a
bit and check it on a brand-new call feed_walruses(2) or in a
hypothetical open("/walrus/mouth", O_ASSERT_FEED_WALRUS_PRIVILEGE)
call. [2]
Now let's consider write(2). write(2) doesn't check privileges, per
POSIX. We've screwed that up multiple times by having write(2) check
privileges, generally resulting in root holes.
IOW, userspace programmers who type open(...) are supposed to know
that they're asserting their euid and egid, and those programmers are
supposed to know that when they type write(...) they aren't asserting
caps at all.
Enter kdbus. We now have an unbounded number of possible kdbus calls,
and somehow users are supposed to keep track of which privileges the
hold affect which kdbus calls. Either each method should document
which privileges it looks at (and then carefully never change it or,
if they do, carefully fix up the holes a la the setuid fixup) [3], or
the library should just suck it up and make users assert privileges
explicitly.
I strongly favor the latter.
[1] I don't believe that this actually works in practice.
Fortunately, good LSM users design their systems so that a complete
failure of LSM policy still leaves it secure.
[2] In the capability-based-security model, which I'm increasingly a
fan of, this looks like openat(walrus_privilege_fd, "mouth", O_RDWR).
Isn't that nifty?
[3] Of course multiple people will mess this up.
--Andy
Linus wrote:
> It would be insane to say that the open system call should have an
> explicit argument saying that the vfs layer should take your privileges
> into account.
On the contrary, it would be a big improvement on the current interface.
To be clearer, it would be great if the open system call took an explicit
argument saying *which* privileges it should take into account.
All that screwing around with uid, euid and fsuid and crap would be
a lot simpler if it was explicit in the open() call which permissions
were desired.
In my "If I had a time machine and could go back and talk to Ken & Dennis"
fantasy, there would be no open(), only openat(), and the permissions
would be associated with the dirfd.
In addition to the now-current standard three fds, there would be
additional ones for root and cwd. And, in a setuid program,
a separate set for effective uids.
So openat(fd, path, flags) would use the real or effective permissions
depending on which fd was in use. A process could drop permissions
by closing the associated fd. Etc.
(And a program which was written without setuid awareness would only
use the real-uid dirfds, and the setuidness would do nothing.)
Hi
On Fri, Apr 24, 2015 at 12:08 AM, Andy Lutomirski <[email protected]> wrote:
> Enter kdbus. We now have an unbounded number of possible kdbus calls,
> and somehow users are supposed to keep track of which privileges the
> hold affect which kdbus calls. Either each method should document
> which privileges it looks at (and then carefully never change it or,
> if they do, carefully fix up the holes a la the setuid fixup) [3], or
> the library should just suck it up and make users assert privileges
> explicitly.
But this is not how authorization with polkit works (or anything
similar to polkit). The authorization-framework is totally separated
from the client that accesses a service. The client asks a service
provider to perform an action. The service provider then asks the
authorization-framework, whether the client is authorized to run the
action. If yes, the action is performed. The client does not have to
know which "privileges" the service provider looks at. It's of no use
to them, they expect the call to succeed, otherwise they wouldn't call
it.
The authorization-framework is explicitly separated from
credential-passing. It has a separate configuration that is neither
controlled by the client nor the service-provider (the default is
usually provided by the latter, though). Therefore, credentials that
are passed are not associated with an action, but rather with the
identity of the client. If a client does not want to run an operation
as its current identity, it better does not call it.
Note that we use those implicit credentials for authorization _only_.
If an operation needs credentials for more than authorization (eg.,
creating a file as a given user), you better encode that in the
object-path (or a method parameter) and validate that the caller is
privileged to do this.
This is quite similar to LSM in general: a seclabel describes the
identity of a process/object, and the identity only. It does not tell
you whether you're allowed to perform an action. Instead, a separate
database maps your identity to your privileges. It's a single (or,
combined) label you pass along. You cannot split it nor choose to send
something else (or not send it at all). You always perform the action
as the identity you are.
Without LSM, we don't have such a unique identifier. Therefore, we
send the UIDs+GIDs+CAPs+NAMEs combination. Those we pass on to the
authorization framework, to decide on whether the peer is privileged.
And we believe those should be mandatory, not optional, just like the
seclabel we send if an LSM is active.
Thanks
David
On Thu, Apr 23, 2015 at 12:41:18PM -0700, Andy Lutomirski wrote:
> On Thu, Apr 23, 2015 at 11:48 AM, Linus Torvalds
> <[email protected]> wrote:
> > On Thu, Apr 23, 2015 at 10:57 AM, Linus Torvalds
[...]
> Objection 2: There's a difference between the printer daemon knowing
> that Angry Penguins has general permission to print and an explicit
> assertion by Angry Penguins of its permission to print. Suppose that
> printing is implemented by having Angry Penguins call the method Write
> on some kdbus thing. Suppose further that changing root's password is
> implemented by having the caller call the method Write on some other
> kdbus thing. Before changing the password, the password daemon makes
> sure that the caller (or the caller's kdbus conn or whatever) has
> password-changing permissions. Before printing, the printer daemon
> makes sure that the caller has printing permissions.
>
> In kdbus, this is IMO a big problem. See, I can try to find some
> setuid root program that takes a printer object as input (however
> kdbus might do this -- presumably it would be an object name) and
> calls Write to print some diagnostic thing. Now I just feed it the
> password-changing thingy as input and I can get it to "print" to the
> root password. Oops.
This will not only introduce complexity, it is formulated for a
"supposed" problem, and even if this problem exists, the fix _should_
be simple, no reason to add the extra complexity.
A suid root program that takes objects from input without assuming that
the unprivileged user has provided this, is a bogus program and the fix
should be at this layer, _not_ introduce extra layers...
The same thing can be applied on every other part of the kernel, what if
a suid program takes some input, constructs objects/structs based on
that, and makes a direct syscall or one through a library into another
part of the kernel ? I don't see why it is a problem for kdbus since
this supposed problem can affect every major part of the kernel. If there
is something to fix here, then sure it is not at this level.
Thanks!
--
Djalal Harouni
http://opendz.org
> But this is not how authorization with polkit works (or anything
> similar to polkit). The authorization-framework is totally separated
Thats a detail which is changeable
> from the client that accesses a service. The client asks a service
> provider to perform an action. The service provider then asks the
> authorization-framework, whether the client is authorized to run the
> action.
This is not good design IMHO. The client should always be indicating it
intends to pass on the credentials it has. That stops privileges leaking
or programs being tricked into things.
> The authorization-framework is explicitly separated from
> credential-passing. It has a separate configuration that is neither
> controlled by the client nor the service-provider (the default is
> usually provided by the latter, though). Therefore, credentials that
> are passed are not associated with an action, but rather with the
> identity of the client. If a client does not want to run an operation
> as its current identity, it better does not call it.
You still want such a usage to involve a client sending a message flag
which says "and this message is an authority to use the following
credential". Given the daemon the other end already has the rights to
perform the action the daemon can presumably be trusted to remember to
check.
> Without LSM, we don't have such a unique identifier. Therefore, we
> send the UIDs+GIDs+CAPs+NAMEs combination. Those we pass on to the
> authorization framework, to decide on whether the peer is privileged.
> And we believe those should be mandatory, not optional, just like the
> seclabel we send if an LSM is active.
The mashed up caps and names really ought to be replaced by something
better. Especially the names. Would it make sense to put some kind of
security label on the executable and pass that instead ? So instead of
all the caps and names crap you label the executable itself as having
"kbus:awesomerebootpower" or whatever so the kernel can see that cleanly
as a label that's basically a kbus namespace capability ?
Alan
Hi
On Mon, Apr 27, 2015 at 4:57 PM, One Thousand Gnomes
<[email protected]> wrote:
>> But this is not how authorization with polkit works (or anything
>> similar to polkit). The authorization-framework is totally separated
>
> Thats a detail which is changeable
It's not a detail, it's a design choice. But see at the end..
>> from the client that accesses a service. The client asks a service
>> provider to perform an action. The service provider then asks the
>> authorization-framework, whether the client is authorized to run the
>> action.
>
> This is not good design IMHO. The client should always be indicating it
> intends to pass on the credentials it has. That stops privileges leaking
> or programs being tricked into things.
If all you pass along is your identity, you cannot "leak a privilege".
If you run a program as someone privileged and you don't want the
program to run as such a user, well, then you better not run it as
such a user..
We don't do setuid or setcap. A caller always has full control who to
run a program as. Choose that wisely, don't make the program protect
itself against the user. On the contrary, if a program is called with
elevated privileges, then this is an explicit decision of the caller
we must respect.
>> The authorization-framework is explicitly separated from
>> credential-passing. It has a separate configuration that is neither
>> controlled by the client nor the service-provider (the default is
>> usually provided by the latter, though). Therefore, credentials that
>> are passed are not associated with an action, but rather with the
>> identity of the client. If a client does not want to run an operation
>> as its current identity, it better does not call it.
>
> You still want such a usage to involve a client sending a message flag
> which says "and this message is an authority to use the following
> credential". [...]
No, I really don't.
>> Without LSM, we don't have such a unique identifier. Therefore, we
>> send the UIDs+GIDs+CAPs+NAMEs combination. Those we pass on to the
>> authorization framework, to decide on whether the peer is privileged.
>> And we believe those should be mandatory, not optional, just like the
>> seclabel we send if an LSM is active.
>
> The mashed up caps and names really ought to be replaced by something
> better. Especially the names. Would it make sense to put some kind of
> security label on the executable and pass that instead ? So instead of
> all the caps and names crap you label the executable itself as having
> "kbus:awesomerebootpower" or whatever so the kernel can see that cleanly
> as a label that's basically a kbus namespace capability ?
(it would be lovely if you did not call my code 'crap')
But this is the essential difference in our design. We don't want code
to be aware of their privileges. We want privileges to be attached
externally to an identity. Thus, a program should always assume it is
privileged to do whatever its purpose is. If its purpose does not fit
you, you better not call it. In all other cases, a "privilege leak"
would just result in the program running as expected.
Thanks
David
On Mon, Apr 27, 2015 at 8:50 AM, David Herrmann <[email protected]> wrote:
> Hi
>
> On Mon, Apr 27, 2015 at 4:57 PM, One Thousand Gnomes
> <[email protected]> wrote:
>>> But this is not how authorization with polkit works (or anything
>>> similar to polkit). The authorization-framework is totally separated
>>
>> Thats a detail which is changeable
>
> It's not a detail, it's a design choice. But see at the end..
>
It's also completely irrelevant to the present discussion. It makes
no difference what code or policy is embedded in what framework.
(It's also actively problematic. As it stands, I, as a logged in
user, have no idea how to waive my permission to reboot the machine on
a regular Fedora install. Dbus and polkit have obfuscated it to the
point that it's already quite difficult to undertsand. This bit me a
couple days ago.)
>>> from the client that accesses a service. The client asks a service
>>> provider to perform an action. The service provider then asks the
>>> authorization-framework, whether the client is authorized to run the
>>> action.
>>
>> This is not good design IMHO. The client should always be indicating it
>> intends to pass on the credentials it has. That stops privileges leaking
>> or programs being tricked into things.
>
> If all you pass along is your identity, you cannot "leak a privilege".
> If you run a program as someone privileged and you don't want the
> program to run as such a user, well, then you better not run it as
> such a user..
>
> We don't do setuid or setcap. A caller always has full control who to
> run a program as. Choose that wisely, don't make the program protect
> itself against the user. On the contrary, if a program is called with
> elevated privileges, then this is an explicit decision of the caller
> we must respect.
Who is "you"? In the future universe where kdbus takes over as the
main way of doing privileged things, people most certainly will run
kdbus clients setuid or setcapped.
>>> Without LSM, we don't have such a unique identifier. Therefore, we
>>> send the UIDs+GIDs+CAPs+NAMEs combination. Those we pass on to the
>>> authorization framework, to decide on whether the peer is privileged.
>>> And we believe those should be mandatory, not optional, just like the
>>> seclabel we send if an LSM is active.
>>
>> The mashed up caps and names really ought to be replaced by something
>> better. Especially the names. Would it make sense to put some kind of
>> security label on the executable and pass that instead ? So instead of
>> all the caps and names crap you label the executable itself as having
>> "kbus:awesomerebootpower" or whatever so the kernel can see that cleanly
>> as a label that's basically a kbus namespace capability ?
>
> (it would be lovely if you did not call my code 'crap')
>
> But this is the essential difference in our design. We don't want code
> to be aware of their privileges. We want privileges to be attached
> externally to an identity. Thus, a program should always assume it is
> privileged to do whatever its purpose is. If its purpose does not fit
> you, you better not call it. In all other cases, a "privilege leak"
> would just result in the program running as expected.
>
Two answers:
1. This is severely inconsistent with other things I've heard recently
from the kdbus camp. Lennart wants clients to use caps and adjust
them. This would involve those clients being very much aware of their
privileges, and their privileges would not "be attached externally to
an identity". Please make sure you actually understand your security
model before you try to justify it.
2. This is a nice thought, but it doesn't work in practice. Sorry.
I can give you a big pile of CVEs from last year if you like, or I can
try explaining again.
The issue boils down to what type of privileges you want to assert and
over what object you want to assert them. Suppose I have a method
"write". When I call it, I do Write(destination, text). In your
model, it's basically never safe to do:
Write(client_controlled_destination, "Log message: client asked me to do xyz");
because the client cause xyz to be "--\nDELETE TABLE people;" or
similar and the server *can't* Write without that Write exercising the
full authority of the server's identity.
Again, we have had Real Exploitable Bugs (tm) in the kernel in
implementations of the write(2) syscall because of this issue. When I
raised those issues (with exploits!) people made very similar
arguments to yours along the lines of "no, sudo is buggy because it
writes to stderr". Sorry, but you're missing the point here. Even
highly privileged programs need to be able to perform unprivileged
operations.
--Andy
Hi
On Mon, Apr 27, 2015 at 6:13 PM, Andy Lutomirski <[email protected]> wrote:
>>>> from the client that accesses a service. The client asks a service
>>>> provider to perform an action. The service provider then asks the
>>>> authorization-framework, whether the client is authorized to run the
>>>> action.
>>>
>>> This is not good design IMHO. The client should always be indicating it
>>> intends to pass on the credentials it has. That stops privileges leaking
>>> or programs being tricked into things.
>>
>> If all you pass along is your identity, you cannot "leak a privilege".
>> If you run a program as someone privileged and you don't want the
>> program to run as such a user, well, then you better not run it as
>> such a user..
>>
>> We don't do setuid or setcap. A caller always has full control who to
>> run a program as. Choose that wisely, don't make the program protect
>> itself against the user. On the contrary, if a program is called with
>> elevated privileges, then this is an explicit decision of the caller
>> we must respect.
>
> Who is "you"? In the future universe where kdbus takes over as the
> main way of doing privileged things, people most certainly will run
> kdbus clients setuid or setcapped.
They're free to do that. But they need to deal with the consequences.
We encourage people not to do that.
>>>> Without LSM, we don't have such a unique identifier. Therefore, we
>>>> send the UIDs+GIDs+CAPs+NAMEs combination. Those we pass on to the
>>>> authorization framework, to decide on whether the peer is privileged.
>>>> And we believe those should be mandatory, not optional, just like the
>>>> seclabel we send if an LSM is active.
>>>
>>> The mashed up caps and names really ought to be replaced by something
>>> better. Especially the names. Would it make sense to put some kind of
>>> security label on the executable and pass that instead ? So instead of
>>> all the caps and names crap you label the executable itself as having
>>> "kbus:awesomerebootpower" or whatever so the kernel can see that cleanly
>>> as a label that's basically a kbus namespace capability ?
>>
>> (it would be lovely if you did not call my code 'crap')
>>
>> But this is the essential difference in our design. We don't want code
>> to be aware of their privileges. We want privileges to be attached
>> externally to an identity. Thus, a program should always assume it is
>> privileged to do whatever its purpose is. If its purpose does not fit
>> you, you better not call it. In all other cases, a "privilege leak"
>> would just result in the program running as expected.
>>
>
> Two answers:
>
> 1. This is severely inconsistent with other things I've heard recently
> from the kdbus camp. Lennart wants clients to use caps and adjust
> them. This would involve those clients being very much aware of their
> privileges, and their privileges would not "be attached externally to
> an identity". Please make sure you actually understand your security
> model before you try to justify it.
Granting an application a capability is not equivalent to the
application being aware of that capability. In fact, I'd assert the
application doesn't care at all whether it gains the privilege due to
a capability or polkit. It's not hard-coded in the application, but
the unit/polkit configurations.
Exception obviously being if we directly access syscalls.
> 2. This is a nice thought, but it doesn't work in practice. Sorry.
> I can give you a big pile of CVEs from last year if you like, or I can
> try explaining again.
>
> The issue boils down to what type of privileges you want to assert and
> over what object you want to assert them. Suppose I have a method
> "write". When I call it, I do Write(destination, text). In your
> model, it's basically never safe to do:
You're correct. So don't create such APIs.
In fact, never ever accept FDs or file-paths from a less privileged
caller. It might be FUSE backed and under their full control.
Thanks
David
On Mon, Apr 27, 2015 at 9:33 AM, David Herrmann <[email protected]> wrote:
> On Mon, Apr 27, 2015 at 6:13 PM, Andy Lutomirski <[email protected]> wrote:
>> 2. This is a nice thought, but it doesn't work in practice. Sorry.
>> I can give you a big pile of CVEs from last year if you like, or I can
>> try explaining again.
>>
>> The issue boils down to what type of privileges you want to assert and
>> over what object you want to assert them. Suppose I have a method
>> "write". When I call it, I do Write(destination, text). In your
>> model, it's basically never safe to do:
>
> You're correct. So don't create such APIs.
> In fact, never ever accept FDs or file-paths from a less privileged
> caller. It might be FUSE backed and under their full control.
Really?
So writing to stdout is not okay? So what if it's on FUSE? At worst
it should be a DoS, unless you screw up the security model (as the
kernel did) and it's privilege escalation or system state corruption.
How about privileged services that mediate interactions between
client-provided objects and other client-inaccessible things?
FWIW, all these threads seem to have spawned tons on comparisons
between dbus, COM, Binder, etc. I don't know too much about all of
these technologies, but one difference springs to mind. COM and
Binder both have very clear concepts of capability-passing. COM calls
it "marshalling an interface". Binder calls it "IBinder". Quoting
from the reference [1]:
The data sent through transact() is a Parcel, a generic buffer of data
that also maintains some meta-data about its contents. The meta data
is used to manage IBinder object references in the buffer, so that
those references can be maintained as the buffer moves across
processes.
Wikipedia suggests CORBA has "objects by reference". I think that
Wayland's "new_id" type is effectively an object reference. I have no
clue what Mac OS X and iOS do.
AFAICT D-Bus is completely missing any corresponding concept. I'm
rather confused how Samsung thinks they can cleanly layer Binder over
kdbus unless they use kdbus as a completely dumb transport and
implement object references in userspace. If they do that, then I
don't see the point of using kdbus at all -- sockets would be fine.
One can certainly debate the merits of capability-based security, but
I've (so far) never heard anyone claim that passing object references
around is a bad idea.
Havoc, am I missing something here? If I'm right about this aspect of
D-Bus, then I'm a bit surprised.
[1] http://developer.android.com/reference/android/os/IBinder.html
--Andy
On Fri, May 1, 2015 at 9:48 PM, Andy Lutomirski <[email protected]> wrote:
> Havoc, am I missing something here? If I'm right about this aspect of
> D-Bus, then I'm a bit surprised.
>
I'm not well-informed about Binder, though from reading about it, it
seems to be modeled on and comparable to COM.
>From what I can tell Binder cannot be implemented in userspace, it
depends on a kernel piece for its semantics, so that's one reason we
wouldn't have made some of its design decisions. Should we make some
of them now if kernel is on the table - I don't know enough about
binder and kdbus to say, but interesting question.
When we did dbus, Android didn't exist so Binder was (I guess) just a
weird BeOS thing, I don't think I had ever looked at it.
COM however was top-of-mind. Mozilla had XPCOM, and GNOME's ORBit and
Bonobo stuff was COM-inspired.
The original idea of COM as far as I know wasn't IPC but more solving
a C++ ABI problem. C++ objects didn't have a fixed ABI (dependent on
compiler, which wasn't stable then) and C++ objects don't define
refcounting, introspection, and stuff like that. So COM provided a
more stable and complete C++ object model, and this evolved to (for
example) allow a COM interface to be used from multiple languages such
as Visual Basic. It makes C++ objects more like java.lang.Object or
GObject or other languages with more complete runtimes.
A COM object can be implemented by an in-proc dll or by a separate
process: http://blogs.msdn.com/b/larryosterman/archive/2004/10/12/241420.aspx
When you instantiate a COM object it's analogous to a C++ "new", it
isn't analogous to a dbus "service activation" - COM doesn't help you
with having a race-free singleton provider of a service, according to
http://stackoverflow.com/questions/6252334/singleton-com-object-required
anyway (and the one time I did win32 programming I remember getting a
singleton by creating a hidden window with a certain class name on it
or some hack like that, while on Linux for the same app we just used
dbus).
I don't think COM addresses the "multicast" thing either.
Anyway, if COM starts from "fancy C++ objects" and then happens to
have out-of-process mode, dbus starts from "singleton service
directory and multicasting" and then happens to have some
convention-only ways to map that onto native language objects.
dbus here was like X11 and KDE's DCOP, and moved away from GNOME's
ORBit and Mozilla's XPCOM.
To fill the COM-like role, GNOME ended up just extending GObject (such
as by adding full introspection).
I'm trying to remember the full rationale here, but some things I remember:
* each language or framework already had the COM-like thing for
in-process: java.lang.Object, GObject, python object, etc. These
define refcounting or GC, introspection, and all that stuff already.
App developers generally want to work with their "native" object
runtime.
* the abstraction where objects could be either in or out of process
wasn't that useful; in practice each object was always one or the
other, and had to be treated differently depending on which.
Philosophically, "network transparent objects" as promised by CORBA
were not working for us.
* multicast and singleton services were really useful, while
random-object-happens-to-be-out-of-process really wasn't. The usual
reason to go out of process is because there's some sort of shared
resource (like "the user's task bar" or "the wifi connection"), so
singleton is the default case. There are two common singleton
"scopes," per-machine (system bus) and per-session (user bus).
* cross-process reference counting made a big mess; without the
hub-and-spoke as in dbus, it's either unreliable or wastes a lot of
file descriptors as every process tracks every other process via an
open connection. (binder solves this presumably via the kernel, but we
were looking at userspace-only options.)
* recursion never had a satisfactory solution; you'd call some
innocuous-looking method on what turned out to be, or use, a remote
object and suddenly code from some completely unrelated part of your
app might end up running. so you call foo_ref() and while that ref is
going to another process you get an incoming unref on another object
you were using... needless to say these bugs were always heisenbugs...
essentially it was multithreaded programming but without locks. The
idea with dbus is to make explicitly asynchronous remote calls kind of
the default, or at least well-supported, though language bindings can
always choose otherwise. In dbus if you make a blocking remote call,
traditionally it is truly blocking (though it's up to the language
binding). The thread making the blocking call will not process any
incoming recursive calls. If you get a deadlock, you need to make your
code nonblocking, which you probably should have done in the first
place.
http://lists.freedesktop.org/archives/dbus/2007-March/007381.html and
http://lists.freedesktop.org/archives/dbus/2006-March/004362.html are
some sample discussions of this topic if anyone is super curious.
* from the X11 experience we felt the most important performance
issue was to avoid blocking round trips, so this is another reason to
write nonblocking code always - you can fire off a lot of requests
without waiting to get back each reply as you're going along.
* because dbus uses the same system for service location/activation,
unicast messages, and multicast, it can keep ordering between
lifecycle events on the bus, multicast events, and unicast method
calls.
Anyhow... so the big picture is this different design emphasis. dbus
is solving multicast/singleton/multi-process first, and then making it
look sort of OO-ish in the language binding library. COM is solving
"C++ has an impoverished runtime with a fragile ABI" first, and then
bolting on "hey we could make these fancied-up objects be out of
process sometimes."
I don't know how to characterize Binder here, I guess it is COM-like
but I don't know if it has in-process mode, or what they do about
singletons or locating/activating services.
dbus is exposing more implementation detail than COM - it explicitly
is always about processes (ok, technically bus connections, but the
point is: not objects). You can track the processes themselves (via
the bus names). dbus encourages writing async code, again exposing
that we're dealing with remote objects. dbus encourages you to just
use your language's native runtime for in-proc objects.
dbus has an "objects-with-methods mapping" but it's pretty much just
convention. Object paths are free-form strings. Either language
bindings or apps can decide what to do with these paths in order to
route method calls within the process.
For example a spreadsheet could own the name
"org.freedesktop.Spreadsheet" and then it could decide to have objects
like "/documents/foo/cells/E5" - the "object" could be completely
virtual, maybe there really isn't an object instance allocated in the
spreadsheet process for every cell (could mean millions of objects),
instead it just parses the path and makes it look like there is. But
another program could have object paths that are just a number, and
keep a hash table from the numbers to object instances. It's up to the
app, or the binding library, to decide how it wants to do it. Apps can
also choose to implement reference counting if they want (and they can
auto-release references by tracking the lifetime of the peer process).
So dbus is punting a lot more to the bindings than COM does; it
doesn't require that an object even exists as a distinct allocation,
let alone require anything about its vtable layout.
On the language binding level, though, you can make COM-like
decisions; you could choose to allow incoming recursion during
apparently-blocking calls, you could choose to export Ref and Unref
methods on all your objects, stuff like that. You could even make a
COM binding to dbus I suppose.
I guess you're really asking how all this relates to capabilities and
I don't know - dbus only looks at processes, not objects, for security
purposes, as far as I know. Objects are just a convention, the dbus
daemon doesn't actually care how a process interprets the object path
(or interface or method name, for that matter).
Havoc