Subject & Object Tranquility (part 2)

Well it seems to be a popular topic lately. First of all lets clarify my statement on subject tranquility I made previously. As stated before, the problem with the lack of tranquility comes from policy not reflecting what is being enforced (due to revocation) and the introduction of time into analysis and design. Note that int SELinux, revocation issues can also result from policy reloads, not just relabeling of subjects/objects directly.

What about dynamic transitions?
Dynamic transitions are an evil hack to add support for legacy applications that are even bigger hacks! Domain transitions occur on the execve call, that is before a process is actually created. The process keeps this label as long as it is running (tranquility). Subject tranquility is maintained throughout the duration of a process as long as dynamic transitions are not used. Dynamic transitions are capable of changing the type of a process at any point during execution. Talk about an inconsistent state. Introducing dynamic transitions is wrong and a hack! In addition to adding the complexity of time to design and analysis it opens a whole new can of worms. The address space is the same for the process regardless of it’s label. So let’s say you’re using a dyntrans b/c you only trust a small segment of code to run in a certain privileged domain. An exploit could very easily result running untrusted code in the privileged domain. It would be a trivial exercise; just overwrite the return address of a function to return to somewhere in the untrusted segment and bam…the untrusted code is running in the privileged domain. If dynamic transitions are evil shouldn’t all domain transitions be evil since they destroy tranquility?
No beause normal domain transitions maintain tranquility (ed note: this is actually not correct, please refer to the comment1 and comment2 below… however the content of this section is still valid wrt time, just not wrt to relabeling) As stated above, the transitions happen on the execve call and the type is kept for the lifetime of the application. This means there is the complexity of time is never introduced. When one process starts another process one of three things can occur: 1. no transition will take place (the child runs in the parent’s domain) 2. an automatic transition will specify the domain of the child process 3. the parent will set the context of the child before the execve() takes place

The first one is self explanitory. The second can easily be analyzed and determined (a parent/executable combination can only automatically transition to one child domain, any more is a policy error). The third is interesting because it permits a parent application to choose the destination domain for it’s child. This is useful for things like login processes where a single application might need to start up children in different domains (ie login running bash in separate domains depending on the user). Applications permitted to do this are considered trusted in this regard and are kept to a minimum. Even though these applications are “trusted” policy still governs the transitions permitted. Going back to the previous example policy must permit login to run bash in a certain domain; login can’t just pick any arbitrary domain. Regardless all three maintain tranquility. Policy never reflects something that is not being enforced (revocation) and the complexity of time is never introduced.

OK so now everyone is clear on subject tranquility in a TE environment right? So what about mmapd files and shared memory? In the case of mmaping can we just relabel files since revocation is fairly instantaneous?
No! Bad! There is no current way to relabel a shared memory segment but we still have to deal with policy reloads. With mmap’d files we have to deal with both relabeling of the underlying file, and policy reloads. However before we get into that, lets talk about why there are revocation issues in the first place. The short of it is that once a segment of memory has been mapped into your processes space than the kernel doesn’t do much else. Remember, you can treat shared memory segments and mmapd files as any other piece of memory, read to it, write to it, execute it, etc (all depending on how you map it). The kernel doesn’t perform any types of check beyond ensure that you stay in your own space. Can you imagine the overhead introduced if access control checks occured everytime you tried to access memory.
So the relabeling problem with mmap’d files is that once a file has been mmap’d, changing the label will have no effect on the processes that have it pinned in memory. So you may think your shutting down your pipeline by relabeling files, or changing the security properties of an mmap’d file shared by several processes by changing the label, but it doesn’t work at all.

Now in the case of shared memory and mmap’d files across policy reloads the same revocation problems exist. If the first policy allowed the access, the second policy can’t revoke it. As an aside, when any relabel/policy reload is performed, time is an issue. How the heck can you analyze policy when one moment the policy means one thing and the next moment it means something else? At least with reloads you can analyze the first policy, the second policy, and perhaps the state of the system at time of reload to understand everything (ignoring the revocation problems described above….) As for relabeling, a few trusted applications must relabel for administrative purposes. RPM, emerge, and other package managers must be able to properly configure the system. Can I just relabel a parent directory and expect a revocation to take place on all files contained within that directory?
No! Bad! Permissions on parent directories are not checked with every read() and write(). Imagine the overhead this would introduce. Directories are only used to lookup an inode, once that inode is obtained the directory is worthless. What about “mv” and the other coreutils? Don’t they break the tranquil model?
I was exploring the commands in the coreutils package and encountered some interesting facts. The “mv” command may require varying permissions to complete successfully. If the source(s) and destination are on the same filesystem one set of permissions are required (a rename operation technically). If the source(s) and destination are on different filesystems then a different set of permissions are required (basically a copy then unlink). This is fairly intuitive but I had never thought about the varying codepaths (and thus varying security hooks) for the mv command. Regardless the source security context is maintained (as long as you’re running a recent version of coreutils with the appropriate SELinux patches). This maintains object tranquility. You still must be aware that moving a file to another location on the same filesystem still provides an open writer with access to that file. The inode is still valid and the security context is not changed. Moving a file to another filesystem may preserve the security context, but the inode will no longer be valid. The writer could write() as much as it wanted after the mv, but as soon as the file is closed it will disappear in a puff of smoke. OK, maybe not a puff of smoke, that depends on a few other things…

Spencer Shimko 07 November 2005