Hacker News new | ask | show | jobs
by sarciszewski 3879 days ago
I would love to see all the other operating systems adopt pledge(), but as is often the case with OpenBSD's security mitigations, it will be years before we see it happen (if at all).
2 comments

This is kind-of a different case than most mitigations, though. It's right now only possible because OpenBSD takes a whole-system approach to development; how the different syscall groups work can change, and right now only follows "this seems like it aligns with how we usually do things in OpenBSD." But yes, I would like to see a similar mechanism appear in other operating systems.
OpenBSD is not unique here; Solaris has a "whole-system approach" to development as well, and has had the ability for programs and admins to easily do privilege drop or provide privilege separation for many years now.

Not only that, Solaris allows you to wrap programs without any source modifications easily using ppriv to drop or add privileges as required.

Almost every slide in the presentation that talks about how you would use the proposed pledge() interface applies to Solaris' privileges model as well.

Some relevant examples:

http://www.kernelthread.com/publications/security/solaris.ht... http://docs.oracle.com/cd/E23823_01/html/816-4863/ch3priv-25...

Solaris' role-based access control and advanced privileges model even lets you implement things like only allowing someone to become 'root' if both them and another person logs in at the same time. Think of the "two-keys required to unlock this door" sort of approach to security:

https://blogs.oracle.com/gbrunett/entry/enforcing_a_two_man_...

My point about OpenBSD having a "whole-system approach" was that the interface isn't necessarily general (yet); it just needs to meet the needs of the OpenBSD team as they exist today. When they realize it has a limitation, they can change it, no fuss, because they can commit to the whole system.

That said, Solaris' facilities seem useful, but from the documentation you linked, seems much more complicated than pledge(). They look similar conceptually, but Solaris' seems to be much more complicated to actually use.

The examples provided are some of the more complex cases that let you do advanced things.

You can shrink the amount of code required if you limit it to more simple cases as those shown in the slides.

For example, as derived from the OpenBSD presentation:

  if (pledge("stdio fattr", NULL) == -1):
     err(1, "pledge");
A similar (not completely equivalent, since OpenBSD chose some "interesting" definitions for their privileges, and is admittedly untested) example for Solaris might be:

  priv_set_t *tmp = priv_str_to_set(
     "PRIV_FILE_READ PRIV_FILE_WRITE PRIV_FILE_CHOWN_SELF",
     " ", NULL);

  /* Assert required privileges. */
  if (setppriv(PRIV_ON, PRIV_PERMITTED, tmp) == -1)
    err(1, "setppriv permitted");
  if (setppriv(PRIV_ON, PRIV_EFFECTIVE, tmp) == -1)
    err(1, "setppriv effective");

  priv_inverse(tmp);

  /* Drop all privileges not required. */
  (void) setppriv(PRIV_OFF, PRIV_PERMITTED, tmp);
The big difference, I think, between the Solaris interfaces and the OpenBSD ones are that Solaris allows the process to temporarily drop privileges and then add them back, or permanently drop them. From the proposed OpenBSD interfaces, it looks they only allow the permanent drop model.

There are a few convenience wrappers that might simplify the above further, but the real point is not to compare efficiency of interfaces, but capability.

Also, Solaris offers the ability to restrict privileges of programs without source code modification (imagine a program you don't quite trust and don't have the source code to). I didn't see that in the OpenBSD presentation.

In their defense, they're also clearly still working on these interfaces, so there can't yet be a fair comparison. Solaris has had privilege interfaces for over a decade, so the model presented is a bit more mature obviously.

The only thing I'd mention is that Solaris tries to provide a default set of privileges that represent things closer to administrative boundaries, rather then implementation-specific ones, as implementation can change, but the basic high-level operations do not.

For example, Solaris has a file read/write privilege, but doesn't bother letting you restrict the ability to set file timestamps separately because that doesn't seem like a useful thing to do. It does however, provide separate privilege(s) for manipulating ownership of files, since that's clearly a different category of operations. OpenBSD currently seems to be focused on the implementation instead of the administrative-level operations being performed.

Thanks for the reply! I've got several responses, that are mostly unrelated to eachother.

1. "interesting definitions": As you note, "OpenBSD chose some 'interesting' definitions for their privileges". This was the core of my original thesis: because of the whole-system approach, they were free to choose "interesting" definitions that closely matched their current-implementation-specific usage patterns, making the whole thing more convenient, but less general. That's all my original post was trying to say.

2. code size: I see that conceptually these 2 code samples work similarly, but one has 5x more LOC.

3. dropping and picking up isn't really dropping: If I drop privileges, but can pick them back up, then if I decide to misbehave, picking them back up is just something I do first. Dropping them with the possibility of picking them up is security theater, not actual security.

4. sandboxing is addressed in other presentations: Earlier presentations explain why having tools to restrict the privileges of programs without source modification is inadequate; a common pattern for programs is that they require some privileges during start up, but then don't need them for the rest of execution; a source-unaware mechanism would have to allow the start-up privileges for the entire execution. Otherwise, it's not too different than existing priv-sep tools.

5. pledge() isn't about administrative privileges, it's about code vulnerability mitigation. Security tools around administrative boundaries are important, sure. But that is fundamentally not the problem that pledge() is trying to solve; pledge is /mitigating/ against vulnerabilities in the /implementation/ of the program. It's saying "for the remainder of my execution, I should only do these types of operations; if I try to do anything else, I have cracked & compromised."

1. "interesting definitions"...

Understood.

2. code size: I see that conceptually these 2 code samples work similarly, but one has 5x more LOC.

I think that's nit-picking a bit, especially since, as I said, there are some other convenience functions that could be used depending on the situation. But regardless, it's hardly onerous. We're talking about 7 lines of actual code vs. 2 lines. That's not even worth arguing about, especially as most applications do this once if they're permanently dropping privileges.

3. dropping and picking up isn't really dropping: If I drop privileges, but can pick them back up, then if I decide to misbehave, picking them back up is just something I do first. Dropping them with the possibility of picking them up is security theater, not actual security.

No, it's actually not security theatre at all. I think you misunderstand the threat model that's trying to be addressed. The kernel is enforcing the restrictions.

Note that I also specifically said Solaris allows both -- the developer can temporarily drop privileges or can permanently drop them or use a combination thereof. Each style of interface is appropriate for a different situation.

For example, consider a case where your program allows the execution of a user script to retrieve a password required to unlock an SSL Private Key file (Apache does this). For the duration of that operation, you can set your effective privileges to be very limited, and thus any programs you fork/exec can also inherit those very limited privileges and you have additional mitigations against certain attacks.

Or consider any other scenario, where during that particular period of execution, you may have to accept untrusted user input. By limiting your effective privileges during that operation, you can significantly mitigate potential attacks against your application.

Privilege bracketing (as this is called) allows you to carefully control sensitive information to ensure that it is not compromised. It's important because sometimes programs do need a higher level of privilege, but only for short durations of program execution. Privilege bracketing is not always the correct answer for an application, but sometimes it is best and only practical choice.

The key benefit of privilege bracketing is to narrow the window of a program's vulnerability so that it is as small as possible, reducing the damage any exploit can do. For example, in order for a process to be able to write to a file, it is only necessary for that file to be opened for write access. In other words, we would assert privileges only for the open() call (relinquishing them immediately after); they should not be asserted for the write() call.

4. sandboxing is addressed in other presentations: Earlier presentations explain why having tools to restrict the privileges of programs without source modification is inadequate; a common pattern for programs is that they require some privileges during start up, but then don't need them for the rest of execution; a source-unaware mechanism would have to allow the start-up privileges for the entire execution. Otherwise, it's not too different than existing priv-sep tools.

Yes, sandboxing is not a complete answer, but if you don't have the source code to an application (which happens often in an enterprise environment), it is one of the best ways to ensure that an application behaves as expected.

As for handling applications that only need certain privileges for startup, Solaris provides a mitigation for that via SMF (Service Management Facility). In particular, it's possible to have SMF allow an extended set of privileges to start the service, and after the service is online, then modify the effective set for the program. That may not work for all programs, but it's still important.

As a historical example, you could modify Apache to start with a greater set of privileges than needed after startup:

http://www.c0t0d0s0.org/archives/4075-Less-known-Solaris-fea...

5. pledge() isn't about administrative privileges, it's about code vulnerability mitigation. Security tools around administrative boundaries are important, sure. But that is fundamentally not the problem that pledge() is trying to solve; pledge is /mitigating/ against vulnerabilities in the /implementation/ of the program. It's saying "for the remainder of my execution, I should only do these types of operations; if I try to do anything else, I have cracked & compromised."

Fundamentally, operations a program can perform is an administrative consideration - not just an implementation consideration. For example, consider a libc function that historically used a specific syscall, but is later optimised to no longer require a certain syscall (as the OpenBSD folks themselves point out as examples of things they are changing). If you represent the privilege in terms of the syscall, then when the implementation changes, either the program breaks or now the program can perform an operation you didn't previously want to allow.

Not only that, a privilege capability model needs to be represented in terms not only appropriate for a developer, but for an administrator as well. Especially since administrators will often require the ability to restrict the capabilities of programs for which they have no source access.

Solaris attempts to strike a balance between the two as you can see in the list of privileges here:

https://docs.oracle.com/cd/E53394_01/html/E54776/privileges-...

Also, this is precisely why both the ability to permanently and temporarily drop privileges is important. As an example, a program at startup could drop everything but the ability to read and write files permanently. Later, it could then further use privilege bracketing to temporarily drop the ability to read or write files (as appropriate) during certain parts of program execution.

In short, I think it's important to not limit perspective of privilege capability to a model where you treat the entire program as hostile -- privileges can be used not only by the administrator to ensure a program behaves as expected, but by the application itself to insulate itself from other bad actors.

Why do you feel this way when even OpenBSD hasn't fully worked out all the details (see the slides) and proven it will work well in practice.
Because I think whitelisting the syscalls at init and only being able to drop syscalls is a great idea.