Practical Windows Sandboxing, Part 2

Once you have a process in a restricted token, the next tool you can use to limit what it can do is a job object. Like restricted tokens, these shipped in Windows 2000. A job object is similar to how ulimits work on UNIX(ish) OS’s, but don’t do some of the things ulimits do (like a limit on the number of open handles), and does a bunch of stuff that ulimits won’t do (at least last time I looked – things could have changed), as well as some very Windows-specific limitations. Basically, these are fairly simple – you create a job object by calling CreateJobObject, and then call AssignProcessToJobObject to stick a process into the job object.

Your first problem is that if the process is already running, what good is it to stick into a job object? What keeps it from doing bad stuff before we put it into a job object? Not much, but there are 2 ways to handle the problem. First is that up until the point we process user input, if we wrote the code, we ought to be able to trust the process. We could let it initialize normally, put it in a job object, and then allow it to process user input. A second approach is to create the process suspended, put it into the job object, and then call ResumeThread on the thread handle for the main thread we got back from CreateProcess (or for a restricted token, CreateProcessAsUser).

Once you have a job object, typically you’ll want to set some limits. One of the most useful things to do is to associate an IO completion port with the job object, and set up a watcher thread that reacts to events in the object. For example, I could set up a CPU time limit, but since you can never be quite sure what’s just taking a long time and what’s a denial of service attack, just setting a hard limit will kill the process when it hits the limit. With an IO completion port, we might be able to make a more intelligent decision.

Some of the more important limits are in the JOBOBJECT_BASIC_LIMIT_INFORMATION struct. Using this, we can limit the number of active processes, memory and CPU consumption, and scheduling priority. An important flag determines whether a process in a job object can create processes outside the job object, and if we’re using this for a sandbox, this certainly needs to be disallowed.

The UI restrictions are interesting – we can prevent a process from creating and switching desktops (though this won’t cover quite everything), changing display settings (I’m not sure if this would work anyway, but it doesn’t hurt), calling ExitWindows, which can log you off (and which could reboot the system if you hadn’t already dropped that privilege), reading and writing the clipboard, and gives the job object its own global atom table. I haven’t seen many exploits around messing with the global atoms, but that’s probably just an accident – the global atoms represent a string table, and consistent with its old 16-bit Windows heritage, there doesn’t seem to be any security associated with it. Those with long memories may recall that the kernel mode call that the global atoms used was responsible for the “getadmin” exploit, which came out July 4, 1997. Russ Cooper and I were the first ones to report that to Microsoft, which was a fun story, but I digress…

There are a couple of problems with job objects – the first is that they do not nest. Once a process is in a job object, it can’t get out, and it can’t be assigned to another job object. If your parent process is somehow put in a job object (which can happen on Vista), and you don’t create the new process as a breakaway process, when you go to assign the process to the job object, you get ERROR_ACCESS_DENIED. Not the error I’d expect, but apparently whoever wrote AssignProcessToJobObject thought that was the only possible way for this to fail. There are a couple of actual uses for job objects in the OS, so maybe one day this will change. Another thing I see as a problem is that there isn’t a way to restrict the process from using ordinary sockets to get on the network, other than the firewall APIs (depending on which OS you have).

A third problem is that one might think that the UI limits might prevent an application from getting back to the default desktop – after all, SwitchDesktop is blocked, but as we shall see, there’s a problem lurking, which will be the topic of the next post.


Comments (4)

  1. So, knowing basically nothing about i/o completion ports (aside from a very cursory idea), how would you use a completion port to make sensible decisions about whether a process is just taking a long time, or in a DoS situation?

    [dcl] The completion port gets a notification when things happen to the job object. This allows you to tune your responses, which can be complex. As a trivial example, one approach might be to allow a certain amount of CPU time per size unit of the file. You could also base your response on system loading, and be more liberal with limits when the system isn’t loaded, and more stringent if it is. You might choose to lower the priority of the task.

  2. Skywing says:

    I’d be extremely wary of trying to put multiple security contexts on the same desktop.  As far as I know, pre-Vista, except for things that don’t ever use (for example) common controls or custom window messages, this is pretty much impossible to do.  Even Vista has a couple of mistakes in the way it does things, though it’s certainly much better than any previous version with respect to sharing a desktop across multiple security contexts / privilege levels.

    [dcl] Yup, exactly the topic of part 3.

  3. Foolhardy says:

    What about the JOB_OBJECT_UILIMIT_HANDLES restriction? Doesn’t that prevent processes inside the job from getting USER handles (e.g. window handles) belonging to processes outside the job (unless specifically permitted via UserHandleGrantAccess) thereby defeating shatter attacks?

    Re: ERROR_ACCESS_DENIED, I believe the actual error is STATUS_PROCESS_IN_JOB, which RtlNtStatusToDosError lumps into ERROR_ACCESS_DENIED (along with a lot of other things).

    [dcl] It may defeat some window message based attacks, but it doesn’t stop SetWindowsHook.

  4. Jednym z powodów wprowadzenia nowych formatów plików w pakiecie Microsoft Office była "zbytnia złożoność" dotychczas używanych formatów binarnych. Zaletą "starych" formatów była niewątpliwie ich szybkość, jednak wraz ze zwiększeniem się