Description: In this lecture, Professor Zeldovich continues the topic of privilege separation, discussing the usage of capabilities with regards to privilege.
Instructor: Nickolai Zeldovich
The following content is provided under a Creative Commons license. Your support will help MIT OpenCourseWare continue to offer high quality, educational resources for free. To make a donation, or to view additional materials from hundreds of MIT courses, visit MIT OpenCourseWare at ocw.mit.edu.
PROFESSOR: All right. Let's get started. So today we're going to talk about capabilities, continue our discussion of how to do privilege separation. And remember last week we talked about how Unix provides some mechanisms for applications to use if they want to privilege separate the application's internal structure.
And today we're going to talk about capabilities, which is a very different way of thinking about privileges that an application might have. And this is why we have actually these two somewhat distinct readings for today, one of which is this confused deputy problem and how to make your privileges much more explicit when you're writing software so that you don't accidentally use the wrong privileges.
And then the second paper is about the system called Capsicum, which is all about sandboxing and running some piece of code with fewer privileges so that it, very much like [INAUDIBLE], if it's compromised, the damage isn't that great.
Now it turns out that the authors of both of these readings really think capabilities are the answer, because they let you manipulate privileges in a rather different way from how Unix, let's say, thinks about privileges.
So to get started, maybe let's look at this confused deputy problem and try to understand what is this problem that Norman Hardy ran into and was so perplexed by. So the paper is written-- well, it was written quite a while ago, and it uses syntax for file names that's a bit surprising. But we can try to at least transcribe his problem into more familiar syntax with Unix-style path names, et cetera.
So as far as I can tell, what is going on in their system is that they had a Fortran compiler, which sort of dates their design at some level, too. But their Fortran compiler lived in /sysx/fort, and they wanted to change this Fortran compiler, so they would keep statistics about what was compiled, what parts of a compiler were particularly expensive presumably, et cetera.
So he wanted to make sure this Fortran compiler would somehow end up writing to this file /sysx/stat, that it would record information about various invocations of the compiler. And the way they did this is, in their operating system, they had something kind of like the setuid that we talked about in Unix. Except there, they called it the home files license. And what it means is that if you ran /sysx/fort, and this program had this so-called home files license, then this process that you just ran would have extra privileges on being able to write everything in /sysx.
So it would have these extra privileges on everything in /sysx/, basically, star. It could access all those files in addition to anything that it could access because the user ran it, for example. So the particular problem they ran into is that some clever user was able to do this. So they would run the Fortran compiler, and the Fortran compiler would take arguments very much like GCC takes arguments. And they would compile something like foo.f. Here is my Fortran source code. And they'd say, well, put that output -o into /sysx/stat.
Or more damagingly in their case, there was another file in /sysx that was the billing file for all the customers on the system. So you could similarly ask the Fortran compiler to compile the source file and put the output into some special file in /sysx.
And in their case, this actually worked. Even though the user themselves didn't have access to write to this file or directory, because the compiler had this extra privilege-- this home files license, in their case-- it was able to override these files despite that not being really the developer's intention. This make sense? This is the rough problem they ran into?
So who do they blame? What do they think went wrong? Or how would you design it differently to avoid running into such problems? So the thing they sort of think about here, or they talk about in this write up, is that they believe this Fortran compiler should be very careful when it's using its privileges.
Because, at some level, the Fortran compiler has two types of privileges. It has one stemming from the fact the user invoked it, so the user should be able to access the source file, like foo.f. And if it was some other user, maybe it wouldn't be able to access the user source code.
And in other sorts of privileges is from those home files license thing that allows us to write to these special files. And internally, in the source code of the compiler, when they open a file, the compiler should have been very explicit about which of these privileges it wants to exercise when opening a file or performing some privileged operation.
But their compiler was not written in this way. It was just called open, read, write, like any other program would do. And it would implicitly use all the privileges that it has, which combines-- well, in their system design, it was sort of the union of the user privileges and these home files license privileges. That make sense?
So these guys were really interested in fixing this problem. And they were sort of calling this compiler this confused deputy, because it needs to disambiguate these multiple privileges that it has and carefully use them in the right instance.
So I guess one thing we could try to look at is how would we design such a compiler in Unix? So in their system, they had this whole files license thing. Other mechanisms, then they introduced capabilities. We'll talk about them shortly. But could we solve this in a Unix system? Suppose you had to write this Fortran compiler in Unix and write to a special file and avoid this confused deputy problem. What would you do? Any ideas?
I guess you could just declare this a bad plan. Like don't keep statistics. Yeah?
PROFESSOR: Sure. That could be, right? Well, yeah. So you could not support flags like -o. On the other hand, you might want to allow specifying which source code you want to compile so that maybe you could read the billing file or read the statistics file, which maybe should be secret. Or maybe the source code has-- maybe you can support a the source code on standard, but it has include statements, so it needs to include other pieces of source code. So that's a little tricky.
AUDIENCE: You could split up the application [INAUDIBLE].
PROFESSOR: Yes. So another potentially good design is to split it up, right? And realize that this fort compiler really doesn't need all these two privileges at the same time. So maybe we should have our Unix world /bin/fortcc or something, the compiler, and then this guy is just a regular program with no extra privileges. And then we'll also maybe have a /bin/fortlog, which is going to be a special program with some extra privileges and it'll log some statistics about what's going on in the compiler. And fortcc is going to invoke this guy.
So how do we give this guy extra privileges? Yeah?
AUDIENCE: Well, maybe if you use something like setuid or something, like fortlog, then presumably any other user could also log arbitrary data through it.
PROFESSOR: Yeah. So this is not so great. Because on fortlog, presumably the only way to give extra privileges in Unix is to in fact make it owned by, I don't know, maybe the fort UID, and that's also setuid. So every time you run it, it switches to this Fortran UID. And maybe there's some special stats file. But then in fact anyone can invoke this fortlog thingy. Which is maybe not great. Now anyone can write to the stats file.
But maybe this example is not the biggest security concern about someone corrupting your statistics. But suppose this was a billing file. Then maybe the same problems would be slightly more acute. Yeah?
AUDIENCE: But you can always make your [INAUDIBLE] stats you want, right? Instead of [INAUDIBLE].
PROFESSOR: So in some sense, yeah. If you're willing to live with arbitrary stuff in your statistics or logging file, then maybe that's true.
AUDIENCE: Even if you [INAUDIBLE], you can already make your C code have whatever statistics that you'd want to be recorded.
PROFESSOR: You could. Yeah. Yeah. So it might be that in this case, it doesn't really matter that you can log arbitrary stuff. So that's true. Yeah. So if you cared about who can invoke this fortlog thing, could you really do something about it in Unix, or not so much? Yeah?
AUDIENCE: [INAUDIBLE]. It would make both of them setuid. Now the fortcc would read that source files. It would switch back to the saved UID, just the user UID. Remote fortlog in a setuid, which has permissions to execute fortlog. And that fortlog would setuid again [INAUDIBLE].
PROFESSOR: Right. Yeah. So there is this rather elaborate mechanism in Unix that we skipped on last Monday's lecture, that actually allows an application to switch between multiple UIDs. if it was setuid to some user ID, then it could say, well, now I want to run with this user ID. Now I want to run with this other user ID. And it could sort of carefully alternate between these. It's a little tricky to do it right, but it's probably doable. So that's one potential design.
I guess another hack you could maybe try to do is make this fortlog binary only executable to a particular group and make fortcc a setgid binary to that group. It's not great, because it obliterates whatever group list the user had initially. But who knows? Maybe that's better than nothing.
Anyway, so it's a fairly tricky problem to solve in an entirely satisfactory fashion with these Unix mechanisms. Although, maybe you should rethink your problem and not worry about your statistics file as much in the first place. But how do we think about what's going wrong in the design?
Well, there's two things we could try to learn from this, or basically, what went wrong. And one interpretation that one party wants us to take away is this notion that he calls ambient authority. So what is ambient authority? Can anyone figure out what they meant? They've never exactly defined this. Yeah?
AUDIENCE: It means you have the authority given to you by the environment. So as if [INAUDIBLE] user with no limitations.
PROFESSOR: Yeah. So you're making an operation, and you can specify what operation you want. But the decision of whether that operation is going to succeed comes from some extra implicit parameters in your process, for example. And in Unix, you can figure out what this ambient authority check might look like.
So if you make a system call, then you probably supplied some sort of a name to a system call. And inside of the kernel, this gets mapped to some sort of an object. And the object presumably has some kind of an access control list on it, like the permissions on a file, et cetera. So there are some permissions that you can get from the object. And that should decide whether an operation is going to be allowed on this name of the application supplied. This is sort of what the application gets to see.
Inside of the kernel, there's also the current user ID of the process making the calls. So this is the current prox UID. And this thing goes into the decision of whether to allow a particular operation or not. So it's the current process user ID that's this ambient privilege. Whatever operation you're going to try to do, the kernel will actually try, in some sense, as hard as possible to allow it by using your current UID, and your current GID and whatever other extra privileges you might have.
And as long as there's some set of privileges that allow you to do it, it'll let you do it. Which is maybe not the best thing to do if you aren't fully aware of what all these problems are. Maybe you don't want to use all of them to open a particular file or make some other operation.
Does this make sense, roughly what ambient privilege is? In the case of an operating system, it basically ends up being the fact that a process has some sort of a user ID. Are there non-OS examples of ambient privilege you guys can think of? Like when you're making an operation, something about the identity of the caller, the terms of whether they'll succeed or not.
Like one example is probably firewalls, as well. So this is just an OS example. And in privilege, another is the firewalls on the network. Because any operation you do from a machine inside of a firewall is going to be allowed because, well, you just have that IP address, or you're on that side of a network. And if you're outside, the same operation will be disallowed. So it's also a solar problem.
Say you visit some website, and the website includes a link to some different server, well, maybe you don't want to use the privileges that you have or the inside of your network to access that link. Because maybe it'll access your internal printer and exploit it in some way. And really, the guy that provided you the link shouldn't have been able to reach the printer in the first place, because they were on the outside. Or a firewall that your browser, maybe by visiting uplink, will be tricked into doing this.
It's sort of a moral equivalent of this confused problem on the network models. Yeah?
AUDIENCE: [INAUDIBLE] permission are directly affected also.
AUDIENCE: Because it's essentially DAC, potentially, in the Capsicum.
PROFESSOR: Yeah. So this is pretty much what the Capsicum guys think of as discretionary access control. And the fact that it's discretionary, well, this is not quite what discretionary access control means. But what discretionary access control means is that the user, or the owner of an object, can decide what security policy will look like to an object. Which seems very natural in a Unix setting. it's my files, I can decide what I want. I can give them to you, or I can keep them private, great.
So almost all DAC systems do look like this, because they want to have some sort of permissions that a user could modify to control the security policy for their files. The flip side is mandatory access control. We'll talk about it in a little while, but at some level, they have this very philosophically different view of the world. They think, well, you're the user. But someone else will set the security policy for how you use this computer.
And this sort of came out of the military in the '70s or '80s, when they really wanted to have classified computer systems where, well, you're working on some stuff and it's marked secret. I'm working on some stuff that's marked top secret. So my stuff just can't go to you. It's not up to me whether to set the permissions on a file, et cetera. It's just not allowed by some guy in charge.
So mandatory access control is really trying to enforce these different kinds of policies in the first place, where there's the user and the application developer. And then there's some guy separate from the user and the developer that sets the policy.
And, as you can sort of guess, it doesn't always work out. Well, we'll talk about it in a bit. But that's what discretionary versus mandatory means at this control.
All right. So there's many other examples that you could imagine where we have ambient authority. And it's not inherently bad, law but it's just something that you have to be very careful about. If you have a system with ambient authority, you should probably be very careful if you're performing privileged operations. You should make sure that you're really using the right authority and not accidentally being tricked very much like this Fortran compiler 20 years ago. 25 now.
All right. So this is one interpretation of what goes wrong. And it's not necessarily the only way to think about what goes wrong, right? Another possibility is that, well, wouldn't it be nice if it was easy for an application to tell whether it should access a file on behalf of some principle?
So maybe another problem is that the access control checks are complicated. So in some sense, when the Fortran compiler is running, and it's opening a file on behalf of a user, it basically needs to replicate the same exact logic we see drawn out here, except that the Fortran compiler needs to plug-in something else here. Instead of using its current privileges, and all of them, it should just replicate this check and try to make it with a different set of privileges.
So in Unix, this turns out to be fairly tricky to do, because there's many places where these security checks happen. if you have symbolic links, then the symbolic link gets looked up, and that path name also gets evaluated with someone's privileges, et cetera.
But it might be that, in some system, you could simplify this access control check, where you could do it yourself in an application. Does that seem like a reasonable plan to you guys? Would you go with that? Any dangers of replicating these checks? Yeah?
AUDIENCE: Well, if you do the checks in the application, you could just not do the checks.
PROFESSOR: Yeah. So you could easily miss the checks. That's absolutely right. So in some sense, what the Fortran compiler did here, well, they didn't even bother trying to do the checks, now that they screwed them up.
Another possibility, in addition to missing the checks, is maybe the kernel will change over time, and it will have slightly different checks. It will introduce some extra security measure, and the application will be left behind. And it will implement old style checks. And probably not a great plan.
So recall, one good idea in security is to have economy of mechanisms. So there's only a small number of places that are enforcing your security policies. You probably don't want to replicate the same functionality in applications in the kernel, et cetera. You really want to boil it down to one place. That roughly makes sense? OK.
So what is this capability, I guess, idea where thinking might solve this authority problem? Well, there's some formal definition for the thing. But really, you can get very close by thinking of Unix file descriptors as a capability.
So I guess the alternative to this picture, in capability world, is that instead of having the application supply name, and you look up an object, you get a permission, you decide whether to allow it based on some ambient authority, instead, the capability is the picture looks very simple. You have a capability, and if you have a capability, it points to an object. And maybe the capability has some small number of restrictions of what you can do with an object.
But basically, if you have the capability to an object, you can access the object. It's actually very simple. So there's no ambient authority that decides whether an operation on a capability is going to be allowed. The only thing is that maybe the capability has a couple of extra bits, or this mass that they described in the paper, which says, well, you have a capability for this file, as it's restricted to read operations only. Or it's restricted to write or append operations only.
And then your security decisions are all of a sudden very easy. Because if you have a capability, you can do something. If you don't, you can't. Make sense?
So I guess one important property of capability is that they should actually be unforgeable, as the papers talk about. So what does it mean to be unforgeable, or why do we want this in this capability world?
Well, I guess this actually may be almost too obvious here. Well, if you can make up any capability you want-- I can make up a capability for any of your guys' files and go access it. So if I can make it up, and I'll access it, and there's nothing else in the security design, that stops me from accessing an object once I can manufacture a capability.
So it's important that these capabilities can't be made up out of thin air by the application or by whatever's running. How is this getting forced, if we think of file descriptors as capabilities? So many of you guys actually submitted this as the big question about Capsicum. What do you think? What prevents an application from synthesizing a capability in this file descriptor world?
Could you synthesize a capability? Yeah?
AUDIENCE: Well, it was probably like a structure and a construct that says that they have a capability for certain file descriptors.
PROFESSOR: Yeah. So it's actually fairly easy to see what goes on once you look at what exactly is a file descriptor, right? So a file descriptor is basically just some sort of an integer. And this integer-- like in Unix, you have file descriptor 0, which refers to your input, file descriptor 1 which refers to your output. Rockwell
But really, these are just integers in user space. And this is what the application can presumably do, and it can choose any integer it wants. But whenever you try to do something to a file descriptor, which is one of these integers, the kernel will always interpret the integer according to your current process's file descriptor table.
So for every PID-- let's say, well, this is PID, I don't know, 57. So I'm process running. It has an open file table, and each integer from supply from user space, refers to some entry in this table. And of course, the kernel should check that the integer is in bounds in this stable. It isn't negative. It doesn't go past the end of the table. Otherwise, it will have the usual buffer overflow problems, et cetera.
But if you carefully check that the integer is in bounds in the kernel implementation, then the only possible things that the application can refer to by a file descriptor are entries in this table. So presumably, the kernel will somehow make sure that you legitimately guard a particular capability.
So when you, for example, open a file outside of this capability model in Unix, well, the kernel, after the open call succeeds, it's going to change that file descriptor table entry to point to a particular open file, like maybe open/etc/pwd.
And now, the entry at this slot on the table points to an open file. Some of them might actually be null. Maybe you don't have an open file with a particular index in this table. And as a result, what does it mean to forge a capability? The only thing you can do in user space is make up an integer. And the only integers that would make sense to make up would be entries that point to non-null entries in this table. And those guys are exactly the capabilities that you have.
So does that make sense why it's difficult, in this file descriptor world, to actually forge capabilities in the first place? So it's kind of cool, right? Like the only files that you have opened are exactly the things you can operate on. And there's nothing else that you can potentially touch and effect. Make sense? Any questions?
All right. OK. So I guess, how would capabilities help solve the ambient authority problem that Norman Hardy is excited about with his Fortran compiler? So what would be the file descriptor moral equivalent solution to this sysx/fort thing? Do they actually solve the problem? Yeah?
AUDIENCE: Well, they just use the appropriate capabilities whenever they're needed. So when you have to access the output file, in the statistics, you use the capability [INAUDIBLE] file. But when you're accessing the file you're about to read, you don't use that capability.
PROFESSOR: Yeah. So I guess really what it boils down to is that somehow the Fortran compiler should just already have a file descriptor open for that /sysx/stat file. So they don't really describe, in their short paper, about how we don't get that capability.
But it basically means you shouldn't really pass file names around. You shouldn't set past file descriptors. So you could actually come up with a perhaps much more elegant design for our Unix replacement on the Fortran compiler using capabilities. So maybe the plan is we should just have a Fortran compiler front end that doesn't have any extra privileges, and it takes all these arguments you give it, and converts all the path names you supply to it into open file descriptors.
So the alternative design I am thinking of here is that maybe we'd have a program fort1, which is the front end. And it would take some sort of a file, foo.f, and all the other arguments, -o, whatever. And it doesn't actually implement any of the compiler logic, anything else. All it looks for is path names in its arguments, and it's going to open them and establish file descriptors for them.
And the cool thing is that, because it has no extra privileges, if the user can't have access to some file name, then it will fail. Those are great. And then once this front end has opened all these file descriptors, it can execute some privileged extra component, like the actual setuid Fortran compiler. So maybe then it'll run fort. This guy's maybe setuid to some special user ID that has access to the stats file. But it doesn't actually accept any path names as input. All it's going to do is accept file descriptors.
And, in that case, the file descriptor is already prove that the caller had access to open them. Does the property make sense? So it of course doesn't solve every issue. I'm just sort of sketching out how capabilities might help. But that's roughly the plan, is that you should demonstrate the fact that you have access to a particular name by just opening it and passing a capability, instead of saying, why didn't you try to open this file and maybe accidentally use some extra privileges. Yes.
AUDIENCE: So does this generalize to having one process per capability?
PROFESSOR: Does this generalize? Well, of course you can have as many processes as you want. You can have multiple processes per capability, but I'm not sure--
PROFESSOR: I'm still not sure what you mean by one property.
AUDIENCE: So we have [INAUDIBLE] capabilities the user has.
PROFESSOR: That's right.
AUDIENCE: And then we have the fort.s access to this past file.
PROFESSOR: That's right. Yeah. So the way to think of it is, you don't necessarily need a separate process for every capability. Because here, the fort1 thing might open many files and might pass many capabilities to the privileged fort component. The problem here-- the reason that this might seem like you want a separate process for every capability is that we're sort of dealing with this weird interface between capabilities and ambient privileges.
Because fort1 sort of does have ambient privilege. And what we're doing is basically we're converting this ambient privilege into capabilities in this fort1 process. So if you have multiple different kinds of ambient privilege, or multiple different privileges that you want to carefully use, then maybe what you want is a separate process holding that privilege. And whenever you want to use a particular set of privileges, you'll ask the corresponding process to please perform a separation. And if it succeeds, give me back the capability.
So that's maybe one way to think of this. There's been actually some operating system designs that are entirely capability-based, there are no ambient privileges whatsoever. And it's kind of cool. Unfortunately, it's more of sort of an interesting reading experience. Like oh, yeah, you can do it. That's pretty cool. But it's probably not really practical to use in a real system, unfortunately. It turns out that you really do want not so much ambient privilege but being able to name an object and tell someone about an object without conveying necessarily the rights to that object.
So maybe I don't know what privileges you might have over some shared document, but I do want to tell you, hey, well, there's a shared document. If you can read it, read it. If you write it, great, write it. But I don't want to necessarily convey any rights. I just want to tell you, hey, there's this thing, go try it. So it's a bit of a bummer in a capability world that it really forces you to never talk about objects without conveying rights to that object.
So it's an important idea to know about, and to use it in some parts of a system, but probably not the be all end all solution to security, much like almost anything else [INAUDIBLE] about here. Make sense? Yeah?
AUDIENCE: So if the process has capabilities given to it by some other process, and it happens to already have the capability to that object, that's greater. Can it compare them to make sure that they're about the same object? Or will it just use the one that's greater?
PROFESSOR: So the thing is that a process doesn't implicitly use the capabilities. So that's the cool thing about capabilities. You have to explicitly name which one you're using. So think of it in terms of file descriptors. Suppose that I give you an open file descriptor for some file, and it's read only. And then someone else gives you another capability for some other-- maybe the same filem maybe a different file, and it's read/write.
It's not all of a sudden that if you're trying to write to the first file descriptor you had that was read only, all of a sudden those will start succeeding because you have this extra writeable file descriptor open. So that's sort of the cool thing. You don't want this ambient privilege. Because if you think of these capabilities as a bunch of privileges that just keep accumulating in your process, then you'll actually just end up with ambient privilege again. You just have all these magic capabilities, and people have actually built such libraries. Basically, well, they manage your capabilities for you. They sort of collect them. And when you try to perform an operation, they look for the capabilities and find the one that'll make it work.
That exactly brings you back to this ambient authority that you were trying to avoid. So the cool thing about capabilities is that it's almost like a programming construct, where it makes it easy for you-- which is a rare thing in security-- it makes it easier for you to write code that specifies exactly what privileges you want to do from a security standpoint. And it's actually a fairly natural code to write.
So if you get into that mindset of always carrying around this privilege with the object you're accessing, it seems like a cool thing to do. It doesn't always make sense, but sometimes it does. Any other questions? OK.
So that's more on the ambient authority that we've look at here. It turns out that capabilities are also great for other problems, as well. And in particular, the problem of managing privileges often shows up when you want to run some untrustworthy code. Because you want to really control which privileges you give it, because you think it will misuse any privileges you give it at all.
And this is the slightly different point of view from which the authors of the Capsicum paper are coming at capabilities. So they're of course clearly aware of this ambient authority problem, but it's sort of a different problem that you might or might not care about solving. But the particular thing they really care about is they have a really large privileged application, and they worry that there's going to be bugs in different parts of that application source code. So they would like to reduce the privileges of different components of that application.
So in that sense, the story is very similar to OKWS. So you have-- for sandboxing, you have some large application, you break it up into components, and you will limit what privileges each component has. So where does this make sense? Like OKWS is clearly one example. What are other situations where you might care about prileged separation? Well, I guess in the paper they describe the examples I actually got to run.
So things like tcpdump and other applications that parse network data. So why do they worry so much about applications that parse network inputs? What goes wrong in tcpdump? Why are they so paranoid?
AUDIENCE: Well, an attacker can control what's being sent and what's being called.
PROFESSOR: Yeah. I think what they really worry about is, very much like with OKWS, they worry about that attack surface and how much can an attacker really control the inputs? And with these network parsing programs, there's a lot of control that that factor has. They have the exact packet.
And the reason that this was so problematic is that if you're writing code in C that has to parse data structures, you're presumably going to do lots of pointer manipulations, copying bites into arrays, allocating memory. And as you are now experts, this is super fragile. And you can easily have memory management errors that lead to pretty disastrous consequences. So this is the reason why they're very excited about sandboxing various network protocol, parsing things, et cetera.
Another probably real world instance where you really care about this is in your browser. You probably want to sandbox your Flash plug-in, or your Java extension, or whatnot. Because they're pretty large attack surfaces as well that have gotten exploited pretty aggressively.
So it seems like a reasonable plan. Like if you're writing some piece of software, you want to sandbox different components of it. What about more generally, if you download something from the internet, and you want to run it with fewer privileges? Is this sort of Capsicum style isolation a good plan for that? I could download some random screensaver or some game from the internet. And I want to run it on my computer, and I want to make sure it doesn't screw up whatever I have laying around.
Would you use Capsicum? Would this be a good plan? Yeah?
AUDIENCE: You could write a sandboxing program, which you'd use Capsicum to sandbox [INAUDIBLE].
PROFESSOR: Right. You could try to use Capsicum. So how would you use Capsicum? Well, you'd just enter into the sandbox mode with cap_enter. And then you run the program. Would you expect it to work? I guess the problem is that if the program wasn't really expecting to be sandboxed with Capsicum, then all of a sudden the program will try to open any simplified-- it'll open a shared library, and it can't open the shared library, because it can't open/liv/ something else. That's not allowed in capability mode.
So it's a bit of a problem. So typically, these sandboxing techniques that we're going to look at here-- capabilities, style, stuff, and so on-- really are best used when the developer is sort of building the application aware that the code is going to run in this mode. There's probably other kinds of sandboxing techniques that could be used for unmodified code, but then the focus, or the requirements, change a bit.
So in Capsicum, they don't really worry about backwards compatibility. Well, we have to open files differently? Sure, we'll open them differently. Whereas, if you want to write existing code, you probably want something more like maybe a full virtual machine. So you could open a VM and run it there. And it's very compatible, and there's no question that it'll just run, and probably not--
Well, it's actually a good thought exercise. Should we use virtual machines to sandbox instead of Capsicum?
PROFESSOR: Yeah. The overheads are probably quite significant. So the memory overhead is pretty bad. It could be. But what if we don't care about memory overhead? So maybe virtual machines gets really good, and they don't actually use that much memory. Is it still a bad plan?
PROFESSOR: Yeah. So it's kind of hard to control what happens on the network, because either you give the virtual machine no access to the network at all, or you connect to a network through NAT mode or something in Preview or VMware. And then it can access the whole internet. So you have to much more explicitly control network by maybe setting up firewall rules for the virtual machine, et cetera. That's maybe not so great.
What if you don't care about network? What if you're some simple video or tcpdump parser. You just spin up a VM. It's going to parse your tcpdump packets and spit you back after your presentation that tcpdump wants to burn to the user. So there's no real network I/O. Maybe you're, for some reason [INAUDIBLE] still?
AUDIENCE: Because the initialization overhead is still large.
PROFESSOR: Yeah. So it's maybe like an initial overhead of starting a VM. So that's true. There's some performance stuff. Yeah.
AUDIENCE: Well, you might want to have database rights and things like that.
PROFESSOR: Yeah. But even more generally, what you're getting at is what if there's a real data that you care about here? And it's really hard to share. So VMs are really a much more sort of separation mechanism, where you can't really share stuff across VMs very easily. So it's good for situations where you have a very isolated program you want to run, you basically don't want to share any files with any directories, any processes, any pipes even. And you just let it run separately.
So it's great. It's probably, in some ways, stronger isolation than what Capsicum provides, because there's probably fewer ways for things to go wrong. And, you know, all these problems we talked about so far. But it's also not applicable in many of the situations where you might want to use Capsicum, because in Capsicum, you can actually share files that have very fine granularity between sandbox [INAUDIBLE] by just giving it capability to [INAUDIBLE] file. This is something that's very easy to do in Capsicum, and would require quite a bit of machinery in a virtual machine setting. That makes sense? Questions? All right.
So does that seem like a useful primitives to have to maybe sandbox stuff. So I guess we're going to talk about different ways to try to sandbox something. And Capsicum in particular is the new thing here that uses capabilities. But just by comparison, I guess, you can do some sandboxing in Unix, as we saw with OKWS. Right? It's just not great from several standpoints.
So let's maybe take the example of tcpdump and see why tcpdump is difficult to sandbox with Unix mechanism. So remember, in the Capsicum paper, these guys took tcpdump. And the way tcpdump works is that it opens some special sockets and then runs basically parsing logic on network packets. And it proceeds and prints them out to the users' terminal.
So what would it take to sandbox tcpdump with Unix primitives? Have you restricted privileges? So I guess the one problem with Unix is that you basically have to-- well, the only way to really change privileges is to change the inputs into the decision function that decides whether you can actually access some object or not. And the only things you can really change are, well, you can change the privilges of the process, which means it sends UID to something else.
Or you could change the permissions on various objects that are laying around in your system. Or probably both, in fact, right? If you wanted to sandbox tcpdump, you'd probably have to pick some extra user ID and switch to that while you're running. Probably not an ideal plan, because you probably don't mean for multiple instances of tcpdump to run as the same user ID.
So if I compromise one instance of tcpdump, it doesn't really mean I want to allow that factor to now control the other instances of tcpdump running on my machine. So that's potentially a bad part of using user IDs here.
Another problem is that, in Unix, you actually have to be root in order to change the user ID of the process or something else, or user privileges or switch them to something else. That's not great either. And another problem is that, regardless of what your user ID is, there could be files that allow access to them. So there could be world writable or world readable files in your file system. Like your etc password file. Regardless of what your UID is, the process will still be able to read that password. So that's not so nice.
So the result, in order to sandbox a unit, you probably have to do both-- some UID changing and maybe careful look at the permissions of all the objects to convince yourself that there's no world writeable file that's really sensitive. Or there's no world readable file that you don't want that hacker to get access to.
And I guess [INAUDIBLE] true that you get another mechanism unit that you can use. But it all starts to add up. If you see it through, then it might be hard to share files or share directories and so on. So does that make sense? Just in terms of contrast for what Capsicum is trying to solve? Any questions about Unix stuff? All right.
So let's look at how Capsicum tries to solve this problem. So in Capsicum, as we keep alluding to, the plan is very much that once you enter the sandboxing mode, everything is going to be accessed only through capability. So if you don't have a capability, you simply cannot access any objects.
So these guys, in the paper, make a huge deal about global namespaces. So what's this thing about a global namespace, and why are they so worried about it? What's an example of a global namespace these guys worry about?
PROFESSOR: Yeah. So a file system from them is sort of the prime example of a global namespace. You can start a slash, and you can basically enumerate any file you could, right? Like go to someone's home directory-- /home/nickolai/ something, something. Why is this bad? Why are they against global namespaces in Capsicum? What do you think? Yeah?
AUDIENCE: Well, if you have the wrong permissions, then use authorities, and then you can get in trouble.
PROFESSOR: Yeah. So the problem is that this is Unix after all. So there are still regular permissions on file. So maybe you really want to sandbox some process and can't read anything at all in the system and can't write to anything. But if you can name a file starting from scratch, you'll find some stupid user that has a world writable file in their home directory. And that would be not so great for the sandboxing client.
And I guess more generally, the way they're thinking of it is that, with capabilities, you could, in principle, enumerate exactly all the objects that a process has. Because you could just enumerate all the capabilities in the file descriptor table, or whatever it is that's storing capabilities for you. And those are the only things that the process could ever touch.
And if you ever have access to our global namespace, and this was potentially unbounded. Because you could-- even if you have some limited set of capabilities, maybe you'll start from slash again and find some new file, and you'll never really know what is the set of operations or objects that a process could access.
So this is the reason they're so worried about global namespaces because it goes against their goal of precisely controlling all the things that a sandbox process should have access to. Make sense? All right.
So they tried to eliminate global namespaces with a bunch of kernel changes to the FreeBSD, in their case, kernel to make sure that all the operations go through some kind of capability, which is, in their case, a file descriptor.
So just to double check, do we really need kernel changes? What if we just do this in a library? So we implement Capsicum, which they already have a library. And all we do is we change all these functions, like open, read, and write, to all very exclusive use capabilities. So all operations will go through some capability, and look it up in the file table, et cetera. Does that work? Yeah?
AUDIENCE: You could always make a sys call.
PROFESSOR: Yeah. So the problem is that there was this existing set of systems calls the kernel will accept. And even if you implement a nice library, it doesn't prevent a bad process or a compromised process from making the sys call directly. And then you have to have the kernel enforce something or other. Yeah?
PROFESSOR: Yeah. So I think it's a question of-- I guess what is your threat model? Exactly. So for the compiler, the threat model is that the programmer is maybe not paying attention a whole lot, but it's not really a compromised compiler process, not an arbitrary code. So if we just help the well-meaning developer do the right thing, then a library will probably suffice.
On the other hand, if we're talking about a process that could be our executing arbitrary code and could be trying to bypass our mechanisms in any possible way, then we have to have a strong enforcement boundary. And a library doesn't provide any kind of strong enforcement guarantees. Whereas a kernel, in our case, would do that.
OK. So what do they actually make in terms of changes to the kernel? So I guess the first thing is this system call that they call cap_enter. And what happens once you run cap_enter? Once you've [INAUDIBLE] cap_enter from your process?
So as far as I can tell, what happens is that the kernel will stop accepting any system calls that refer to global namespaces. And the only thing you'll be able to do is refer to existing file descriptors that you have open in your process. So cap_enter will put your process in a special mode where you cannot use the regular system called open, and instead you have to do things like openat.
So there's this new sort of family of systems called, in Unix like operating systems, where instead of having open take a single path name, you can actually you openat, where you pass it a first argument which is a file descriptor for a directory and the second is some sort of a name. And the open at system call will open this name relative to whatever directory the file descriptor points to.
So this is a much more capability-like version of open, where you can still have file descriptors pointing to directories, but you can-- well, sorry. You can still direct your operation. But in order to do this, you have to have a capability to the directory in the form of an open file descriptor for that [INAUDIBLE]. Make sense? OK.
So do they need any other kernel changes? Is there anything else they worry about? So I guess there's another-- yeah?
PROFESSOR: Yeah. So what do they do about network access, right? So what happens in capability mode?
AUDIENCE: I guess they have capabilities for security packets [INAUDIBLE].
PROFESSOR: Yes. So I think the way they basically do it is that they treat the network as a global namespace, very much like a file system. So I think once you enter capability mode, you cannot create a new socket. Or you cannot create a new socket and connect to some arbitrary machine, or to some arbitrary address or fort number. You have to basically create all the connections you want ahead of time and fill them in as capabilities. Or maybe you'd have to get them from someone that will pass you a file descriptor.
But basically, once you're in capability mode, the set of file descriptors you have open completely enumerates all the machines you'll ever talk to. So you can find open connections. Maybe you're listening on a forge. That's OK. But you cannot connect to an address specified by an absolute name, kind of like a global namespace would allow you to do it. That make sense?
So it's access through the networking namespace, as well. What do they do for processes? So another global namespace, I guess, in Unix, is the the PIDs themselves. So the example of a system call that operates in this name space is "kill." So I could kill PID 25. And I could-- well, presumably I'll put a single number in there, too.
But I could actually kill a process by its PID number. How do they fix this in Capsicum? What's their plan? Yeah?
AUDIENCE: File descriptors with processes.
PROFESSOR: Yeah. It's actually kind of cool. It's like, I wish Unix had this all along. Which is that, instead of having these different kinds of numbers or PIDs, instead, when you fork off a process, actually having new variant of fork called pdfork, or Process Descriptor Fork. And what it does is when it creates a child process, it actually sticks a reference to that child process into your file descriptor table somewhere.
And this is your new process. And you can operate on a child process by specifying the file descriptor number. Well, it would be pretty cool, because you can now pass your child process to someone else and say, well, if you can go and kill them now, or you can manage this process however you want, you'll get notifications when the process dies. It'll look like a readable file descriptor, et cetera.
So they really try to homogenize everything into looking like a file descriptor of some sort here. And with these kernel changes, you can finally have all the functionalities you might care about. You have the support for sockets already, process descriptors, et cetera. And you have a way of constraining what the process can do. Because it cannot refer to any of the global names anymore after [INAUDIBLE].
All right. Any questions? So here's an interesting puzzle. I was trying to understand from the paper. They make a big deal about dot dot in looking up directory names. So they basically say, well, once you're in capability mode, when you pass a particular name to openat, you cannot use dot dot in those names. And presumably, if you have a Simulink, if a Simulink's target contains dot dot, they will reject it if you're in capability mode.
So is this strictly required? Could you imagine a safe design in principle that allows the use of dot dot? Yeah.
AUDIENCE: Well, you'd need to be able to find whether they have a file or a capability that allows the masses to the parent directory.
AUDIENCE: So it's trivial to go down, because any subdirectory-- you already have access to it by having the capability.
PROFESSOR: That's right. Yeah.
AUDIENCE: But going up, you need to see whether you have any capabilities for the parent directory.
PROFESSOR: That's right. Yeah.
AUDIENCE: Search for it somehow.
PROFESSOR: Yeah. So that's a little bit tricky. And also, it goes against the grain of this whole explicit authority thing. What about if you're using dot dot inside sort of a single open call? So for example, what if you call something like openat some particular directory or file descriptor number, and you open something like, I don't know, b/c/../..?
In principle, this might be safe, right? Because you go down some directory, and then you just climb back up out of it. Yeah?
AUDIENCE: What if c is [INAUDIBLE]?
PROFESSOR: Yeah. So it's a little bit tricky, of course, to define exactly what it means to be safe. Right? You probably have to make sure that c isn't a Simulink that goes somewhere else and so on. Yeah. That's a fairly tricky proposition, to get this right. And I think, in the paper, what they basically argue about is that it's actually quite difficult in practice to implement a set of checks that's sufficient and bypasses all the possible rate conditions here. So they basically just do the conservative thing and disallow any dot dot at any time once you're in capability mode.
There's some interesting rate conditions you could come up with here. The lecture notes have more details. But basically I think these guys are being extra cautious in defining what's allowed and what's not allowed in capability mode. OK.
So here, to answer your question, once you enter capability mode, it seems to be all controlled by your file table. Does your UID still matter, once you enter capability mode? [INAUDIBLE]. Yeah?
AUDIENCE: Well, you could still launch a process that doesn't use capabilities.
PROFESSOR: No. Actually, no, you can't. You have to make sure that-- otherwise you could escape, like well, I can't access-- why don't you run this guy? [INAUDIBLE]. So yeah, cap_enter is inherited by all the children, which is actually hugely important. Yeah?
Anyone else? So what if we kill the UID? So it's supposed to be like going to cap_enter, and we just kill the UID of the current process. We don't actually care what it is anymore. And then the process tries to open a file. What checks should apply? Yeah?
AUDIENCE: Oh, I was thinking that the UID is useful for logging purposes as well, like being able to tell if you did something.
PROFESSOR: So yeah, you're right. Actually, yeah. So that would be actually kind of damaging, right? Like I spawned some sandbox process on my machine and it loses the UID. I'm like I have a hundred processes running on my machine, and I have no idea what they are.
So that's probably not a good plan for a management purpose. You're absolutely right. But I'm just sort of hypothetically saying, well, do we need it for access control, I guess. Yeah?
AUDIENCE: Maybe if this UID is only supposed to be able to access this file by reading or whatever, but you have the file descriptor for it, but then if you lose the UID, you might get permissions to write [INAUDIBLE] or something?
PROFESSOR: Yeah. I think actually what it shows up in is in directories. Because once you add a capability to a file, that's basically it. You have it open with particular privileges, et cetera. But the problem is that they have this hybrid design where they say, well, you can actually add capabilities to directories, and you can open a new file as you're running along. And it might be the case that you add a capability to a directory, like /etc. And you don't have access to necessarily all the files in /etc. But once you enter capability mode, you can now try to open those files by saying, well, I have access to the /etc directory. It's open already. Why don't you give me the file named password in that directory?
And the kernel still needs to make an access control decision on whether to allow you to open a file in that directory with either read mode or write mode or what have you. So I think this is the one place where you still need this ambient privilege, to some extent, because they're trying to build this compatible design where you can have semi-natural semantics for how directories work. Does that make sense? it's like one leftover place, kind of for compatibility reasons, or at least the way that Unix file systems are typically set up.
AUDIENCE: Are there any other places?
PROFESSOR: Good question. I couldn't think of one off hand, but I guess I would have to get their previous desource code to really figure out what's going on. I think most of the other situations don't really require a UID check. Because for networking, it doesn't show up. I think for process descriptors it doesn't show up, either. If you have it, then you just have it. So I think it probably is just file system operations.
For shared memory, it's also-- once you have a shared memory segment, you have it open. Yeah?
AUDIENCE: Could you explain again how exactly the user ID matters if you have a capability?
PROFESSOR: Yeah. So I think where it matters is, you have a capability to a directory. The question is, what does the capability represent? So one interpretation that-- for example, some capability system state, not Capsicum. Pure capability systems. They say, well, if you have a capability to a directory, then of course you have access to all the files in that directory, no questions about it.
And in Unix, this is typically not the case. You can open a directory like /etc, but there's lots of system files in there that are maybe private, like the private key of your server is stored in there. And just because you can look at a directory and open it and list it doesn't mean that you cannot open the files in that directory.
So in Capsicum, if you open a directory like /etc, and then you enter capability mode. And then you say, well, hey, I don't know what this directory is. I just add a file descriptor to it. There's a file in there called "key." Why don't you open that file "key"? And at this point, you probably don't want to allow this capability-based processor to just open it, because that wasn't the intent.
They'll allow you to bypass the Unix permissions on a file. So I think the authors of this paper are careful to design a system which would not violate existing security mechanisms.
AUDIENCE: So you're saying that you can, in some cases, use a combination of the two? So even though it'll be able to change it to directory, inside the directory, which files you can access depends on your user ID?
PROFESSOR: Yeah, exactly. So in Capsicum, the way they get it to work in practice is that, actually, before you enter capability mode, you have to guess. Well, what files am I going to need later? I'm going to need some shared libraries. I'll need some text files. I'll need some templates. I'll need some network connections, et cetera. So you open all these things ahead of time.
And you don't always necessarily know which exact file you need. So what these guys support as well, you can actually just open a directory file descriptor, as well. And then I can look up the particular files later. But it might be that the files don't have all the same permissions. So that's exactly the reason, yeah. Make sense? All right.
So this is the kernel mechanism part of it. Why do they also need this library for libcapsicum? I guess there's two things that they support in that library, as far as I can tell, or two main things. One is that they implement this function they call lch_start that you should use instead of cap_enter. And the other sort of feature the library provides in libcapsicum is this notion called fd lists instead of passing file descriptors by number.
So this fd list thing is probably the easiest thing to explain. It's basically a generalization, or maybe a clean up, of how Unix manages and passes file descriptors between process. So in traditional Unix and Linux, how you use it today, typically when you launch a process, you can pass it some file descriptors. You just open some file descriptors at particular integer numbers in this table and you run the child process that you want to run. Or you run a particular binary, and it inherits all these open slots in the fd table. But there's no real good way to name these things other than by number.
So the somewhat surprising convention, if you haven't [INAUDIBLE] before, is that, well, slot 0 is your input. Slot 1 is your output. Slot 2 is where you should print error messages to. And that's how Unix sort of works. And it sort of works OK if you are just passing these three files or streams to a process. But in Capsicum, what's happening is that you're passing down many more file descriptors around. So you're passing a file descriptor for some files. You're passing a file descriptor for a network connection, for a shared library, what have you. And it becomes much more tedious to manage all these numbers. So basically, libcapsicum provides an abstraction for naming these past file descriptors between processes by some sort of a hierarchical name, instead of just these opaque integers, if you will.
So that's one sort of simple thing that they provide in their library. So I can pass a file descriptor to a process and give it a name. And it doesn't really matter what number it has, a little easier. That make sense? OK.
So then they have this other mechanism, this much more elaborate way to start a sandbox. This lch, libcapsicum Host, API for starting a sandbox, instead of just entering the capability mode. So what happened? Why do they need something more than just entering capability mode? What are you worried about on creating a sandbox? Yeah?
AUDIENCE: It erases all the inherited stuff to give you a clean start.
PROFESSOR: Yeah. So I think they worry about trying to enumerate what are all the things the sandbox has access to. And the problem is that if you just call cap_enter, technically, at the kernel mechanism level, as we talked about just now, it worked. Right? It just prevents you from opening any new capabilities. But the problem is that there might be lots of existing stuff that the process already has access to.
So I guess the simplest example is maybe there are some file descriptors that you forgot you had opened, and it'll just get inherited by this process. So one example is they were looking at tcpdump. And they realized that-- well, first, they changed tcpdump just by calling cap_enter at the point just before they were about to parse all the network input.
So this works well, in some sense, because you can't get any more capabilities. But then they looked at the open file descriptor, and they realized that you have complete access to the user's terminal, because you have an open file descriptor to it. So you can actually sniff all the keystrokes that the user is typing and all that stuff. So it's probably not a great plan for tcpdump. This compromise you probably don't want sniffing everything you're typing.
So instead they-- well, in tcpdump's case, they manually changed these file descriptors to add some capability bits to them, to restrict what kinds of operations you can do. So remember, the capability, at least in Capsicum, has these extra bits that say, here's the class of operations you can perform on a file descriptor. So they basically take what used to be file descriptor 0. It pointed to the user's terminal, tty.
And originally, this was just a direct pointer to the tty structure in the kernel. What they do is they actually-- in order to limit the kind of operations you can perform on this file descriptor, they basically introduced some extra beta structure in the middle. This guy will point to the terminal. And the file descriptor itself will point to some sort of a capability structure. And inside of it is the pointer to the real file that you're trying to access, as well as some restricted bits or permissions on that file descriptor object that you can do.
In their case, they basically can say for tcpdumps standard input, you cannot do anything on it. You can just see that it exists, and that's it. For the output file descriptor, they say, well, you can write to it, but you maybe can't reposition. You can't [INAUDIBLE] back and forth, et cetera. Make sense?
So what else would you worry about, in terms of starting a sandbox? So there is, I guess, the file descriptor state. Anything else that matters? Well, I guess in Unix it's file descriptors and memory. That's pretty much it.
So the other thing that these guys worry about is that it might be that in your address space, you previously allocated some sensitive data. And the process that your sandbox is going to be able to read all its memory. So if there's maybe some password that you checked before when the user was logging in, and you haven't cleared that yet, well, the sandbox process will be able to read that and do something maybe interesting to that.
So the way they solved this problem is, in lch_start, you basically have to start a program fresh. You basically take a program. You explicitly package up all the arguments you want to give it. You explicitly package up all the file descriptors you want to give it. And then you start a new process, or you would call executives to reinitialize your whole virtual memory space.
And then there's no question about what is the set of sensitive data of extra privileges that this process has. It's exactly what you passed to lch_start, in terms of a program name, arguments, and capabilities. Does that make sense?
AUDIENCE: What would happen if the process that you're starting is a setuid 0 binary?
PROFESSOR: Yeah. I think these guys say that they don't actually allow setuid binaries in capability mode, just to avoid some weird interactions that would show up. I think the rules that they implement is that you could have a setuid program that gets its privileges from a setuid binary, and then it can call capenter or lch_start. But once you're in capability mode, you cannot regain extra privileges.
In principle, this could work, but it would be very weird. Because remember, the only place where the UID matters, once you're in capability mode, is in opening these files inside of a directory. So it's not clear this is really a great plan for getting more privileges or [INAUDIBLE] there. Make sense? Yeah?
AUDIENCE: We talked about earlier why the library doesn't really support strict separation between those two. And then we just mentioned all these problems that you could use [INAUDIBLE], so we're still not under a restriction to use lch_start necessarily, right?
PROFESSOR: That's right. Yeah. So lch_start, here's sort of the way to think of it. So you have an application, like maybe tcpdump. Or gzip is the other thing they work with. And what you're basically assuming is the application is probably not compromised, and there are some core part of the application that you worry about sandboxing. In tcpdump's case, it's actually parsing packets coming from the network.
In gzip's case, it's actually taking the file and decompressing it. And you're basically assuming, well, up until a point, the process is probably doing all the right things. It's not exploited. There's probably not a bug yet for the [INAUDIBLE] even. So at that point, you're trusting that it will run lch_start correctly and correctly set up the image, correctly set up all the capabilities, and then restrict itself from making any further system calls outside its capability mode.
And then you run the dangerous stuff. And by then, this setup has happened correctly, and there's no way to escape out of that sandbox. Make sense? All right. So I guess let's look at how you actually use capability mode to sandbox applications.
So we talked a little bit about tcpdump. How do you isolate this process? Another interesting example they had was this gzip program that compresses, decompresses files. So why do they worry about sandboxing it? I guess they worry that the decompression code is going to be potentially buggy, or maybe there's some memory management errors in how they manage the buffers during decompression, et cetera.
So could they-- well, one interesting question, I guess, is why are the changes to gzip seemingly much more complicated than for tcpdump? Any guesses? Well as far as you can tell, it's mostly just a question of how the application is structured internally, right?
So if you had a application that simply compressed a single file, or decompressed a single file, then it might be OK for us to just run it in capability mode without really changing it. You just give it a new standard in for something to decompress, and the standard out goes to the decompressed output, and that would work fine.
The problem, as is almost always the case here with these kind of sandboxing techniques, is that the application actually has much more complicated logic around it. So gzip, for example, can compress multiple files, et cetera. And in that case, you have some sort of a driver process on top which actually has these extra privileges to open multiple files, to create things, et cetera.
And the core logic needs to be often another helper process. And it was just so the case in gzip that the application wasn't structured in a way where this was already a separate process doing all the decompression or compression. So they had to change gzip's core implementation, and, well, some structure of the gzip application, instead of just passing the data to the decompression function to actually send it over an RPC call or really just write it to some almost file descriptor to help process the problems on the side and performs all the decompression with almost no privileges. The only thing it can do is return the decompressed data, or the compressed data, back to the caller process.
That roughly make sense? What's going on in gzip? All right. So I guess one thing we asked for the homework is how do you actually use Capsicum in OKWS? So what do you guys think? Would it be useful? Would the OKWS guys have been excited and switched to FreeBSD because this was much easier to use? Or is this a wash? So what do you think? How would you use Capsicum in FreeBSD? Would this be much different? Yeah.
AUDIENCE: So it means you can get rid of some of the jailing [INAUDIBLE].
PROFESSOR: Yeah. That's true. So truth seems to be completely superseded by this plan of having directory file descriptors and capabilities. So that's great. So you don't need the chroots setting it up. That seems messy. And this is much more precise, also. Because you can-- instead of having a chroot with lots of little things in there, you have to maybe set the permissions on there carefully. You can just open exactly the files that you need. So that seems like a plus. Any other benefits? Yeah.
PROFESSOR: In OKWS, you mean?
PROFESSOR: Yeah. So in OKWS, right, you have this OK launcher daemon that had to launch all these guys. And it was the parent process. Only when they die, the signal goes back to this okld to restart the crash process. And that thing had to run this root, because it had to sandbox things. There's actually a number of things you could do better with Capsicum in OKWS.
So one example is you could probably have okld have many fewer privileges. Because it might need to be root initially to get fort 80. But after that, it could set up sandboxes for everyone else without being root anymore. So that's kind of cool. And maybe you can even delegate the job of responding a process to someone else, maybe a per service monitor Damion that just has this process descriptor handle, or process descriptor for child process, and whenever it crashes, starts a new one.
So I think this process [INAUDIBLE] helps things a lot. And the fact that you can create a sandbox without being root is also quite helpful, as well. Any other stuff, what you could do? Yeah?
AUDIENCE: You could give each one a file descriptor with append only mode to the log.
PROFESSOR: Yeah. So that's pretty cool. So as we were talking last time, in OKWS, well, the oklogd maybe could hamper with the log file. And who knows what the kernel will allow it to do once it has a file descriptor on the log file itself. But here, the fact that we can do much more of a precise capability map on a file descriptor, well, we could give it a log file and say, well, you could just write to it, but not seek. So that basically means append only, if you're the only writer to that file. So that seems kind of nice.
And you could prevent it from reading a file. You could say, well, you can only write, but not read, which is something that's probably difficult to do with Unix permissions alone right now. Make sense? Any other ideas for how Capsicum might help? Would you wish there was more stuff in Capsicum? I guess we always wish there was more stuff.
AUDIENCE: So one thing that perhaps may be tricky is the service team daemons need to connected to their backend databases somehow. Which might be remotely. But you don't want the launch daemon to know about which services each service is going to connect to.
PROFESSOR: Maybe, yeah. That's a good question, right? So in Capsicum, as we were talking about, the network is in global namespace. You have to have existing file descriptors for all the outstanding connections ahead of time.
AUDIENCE: Right. But you don't necessarily want okld to open up all the sockets for all the services. Because it might not know where the services are connected.
PROFESSOR: That's right. Yeah. So that's a little bit of an awkward thing. I absolutely agree. And this is part of the reason why I think capabilities haven't completely subsumed everything in the security world, is because they are kind of awkward to use. Because the guy that gives you all the privileges has to know exactly what things you're going to need, like these connections to backend servers.
So at some level, maybe this is not such a huge problem in OKWS. Because the launcher daemon has to read a Config file and is going to pass the token to the service in the first place. So maybe the token is going to contain the host and port number to which you're connected to. But I agree. It's not great.
Because especially, suppose the database server disconnects you. Well, you're kind of stuck now. The file server is not connected anymore, and you can't connect to a new one. So basically, if the database server crashes, or restarts, or the network breaks, you basically have to terminate it, get yourself response, so you can get a new one of these connections past you. So it's maybe not a great plan in that sense.
AUDIENCE: Could we wrap the system call, the function [INAUDIBLE] to open a socket so that it faults the middleman instead of the socket that the users send out to [INAUDIBLE]?
PROFESSOR: Yeah. This is what I think the FreeBSD guys have done since. Well, there's a bunch of situations like this, where you want to open some file after the fact, or you want to connect to something after going into capability mode. So the FreeBSD developers have added this daemon called Casper, that every capability based process has a handle on. And this Casper daemon runs outside of capability mode, and basically listens to requests from sandbox processes.
And if you want to open some file, or if you want to send a network connection, or a packet, or something, but you didn't have the right capability beforehand, then this Casper daemon will do it for you. But it carefully maintains a list of things that every sandbox process should or should not be able to do. So it's like a systems service. So when you start a capability process, or enter capability mode, by default, this Casper thing will not allow you to do anything extra funny.
But you could say, well, hey, I'm going to start the sandbox process. And you can ask Casper, well, please allow my process to do the following things later. So you could, right? And the cool thing is that you can pass file descriptors or capabilities through fd passing in Unix. So once you have a handle on this Casper guy, you can get more capabilities later on. So it's, again, trade off between being pure capability world versus actually being programmable or easy to use.
So it seems to be working out. I think the particular thing they use it for in FreeBSD, or the thing that shows up often, is making DNS queries. So you want to be able to make DNS queries once you're in a sandbox. And actually, this is a problem they ran into with tcpdump. Because when tcpdump is printing your packets, it wants to print the host name for an IP address.
In order to do this, it has to talk to a DNS server. But you probably don't want to connect to a DNS server ahead of time, or to every DNS server you might ever need. So instead, they use this helper daemon that's going to make DNS queries for you. Make sense? All right.
So I guess the last thing I wanted to talk about is what are the security guarantees that Capsicum provides? So should you trust it? How could Capsicum go wrong? Presumably you can always have security problems, regardless of what mechanism you're using underneath. But what particular things should we worry about in Capsicum when we're building some system here?
Suppose you have to attack this thing. You have to attack this tcpdump thing, or gzip, or whatever it is that they implemented. What would you look at, in terms of bugs or problems?
AUDIENCE: Well, it depends on the developers knowing what they're doing. So they might give a bad capability.
PROFESSOR: That's right. Yeah. So it's actually one interesting property of Capsicum is that it's not a guarantee that the user of the system gets. It's really a tool that the developer has to build more trustworthy or better application software. But I, as a user of the system, have no idea whether this is a good or bad thing that the application is using Capsicum. You could totally misuse it, as you're absolutely right.
So maybe one example is, as they show in the paper, you could give too many privileges to the sandbox process. Like the the TCP helper, or maybe it has access to my console. And that's not so great, but it's hard for me as a user to really tell this in a general purpose fashion. Yeah?
AUDIENCE: It might also be that when you set the permissions to the masks on any given file descriptor that you set two permission masks.
PROFESSOR: Yeah. Right. So it's not just the file descriptors. Also, what can you do with those file descriptors? You're right. Yes. These maps are another part of the story that you have to watch out for. OK. So suppose we got the masks right. We got the file descriptors right. We haven't used lth_start. There's nothing extra in memory.
PROFESSOR: That's true. Yes. So maybe there's like something before you even add the capability mode that's damaging. So it only helps once you jump in. And one slightly annoying thing is that it seems like it can't do a whole lot inside of capability mode, not in the sense that you can't run large computations, but you can't really put a large part of a complicated system into capability mode. Because inevitably, in Unix, you'll need to do something with new processes, opening network connections, et cetera.
And you'll probably need to use some of these global namespaces that are not available in capability mode. So it's probably going to be quite difficult to put large chunks of logic or intricate system code inside of capability mode. So only well-defined chunks of an application are likely to be running in capability mode. It depends. I don't know if this is entirely true or not. In Chrome, for example, large processes do run in capability mode in their design.
It might be that you basically have to have non-capability mode chunks of your application because you wanted to incorporate nicely with Unix, or whatever is is you're running alongside of it.
OK. Any other thing you should worry about? Yeah?
AUDIENCE: Well, whether they implemented capabilities correctly.
AUDIENCE: Whether they've covered all the system calls.
PROFESSOR: That's right. Yes. So that's actually a huge problem, in some sense, already. If you think about it, there's probably hundreds of system calls that the kernel provides you. And they're not especially precisely documented, so you probably have to look at their implementation and see if, for every system call, if there's some way for the applications to get the system call to perform some operation on some extra object that didn't have a file descriptor to it.
And most Unix system calls weren't written with the expectation of everything has to be operation on a file descriptor. So you really have to get every system all right. And probably more worryingly is that the kernel has to be free of bugs, like buffer overflows or whatever other memory corruption like you guys explained [INAUDIBLE].
Otherwise, all of this is complete nonsense. You just are on arbitrary assembly code in the kernel, and you have full control of the machine.
PROFESSOR: Yeah. I guess, yeah. So the one thing I didn't get a chance to talk about is alternative things. So this is in FreeBSD. Linux has this thing called [INAUDIBLE], that allows you to specify which system calls you can operate. If you squinted, it's kind of like Capsicum but very different, in the sense that Capsicum talks about specific file descriptors that you can operate. And in Linux, the [INAUDIBLE] mechanism lets you talk about specific system calls that you could run.
So it's probably less fine grained, but it's what's available in Linux today. And it's actually probably a good idea to look at your applications and see what system call do you expect it to make and then code in a filter and allow it to make only those system calls. The problem is that if you have any interesting applications, it'll probably run exec and open and write, and that's probably enough to do quite a bit of damage to the system. So that's why you probably want the more fine-grained system like Capsicum, where you can say, well, you can run right, but only on this thing, not on my entire home directory.
All right. So I guess we're out of time to talk about Capsicum. So let's talk about native clients on Wednesday and a different way to sandbox programs.