Just to be clear, this is NOT intended to be a rant against the existance of SELinux. There exist security environments where the amount of time and effort involved in properly maintaining an SELinux system is well justified and the various rollout delays are acceptable losses.
This IS a rant against the idea that SELinux is some sort of one size fits all silver bullet suitable for rollout in 'normal' ISP and corporate environments. In fact, the great complexity and interlocking dependancies of SELinux practically garantee that SELinux will either end up set to permissive (where it provides no actual security) or will be substantially defanged introducing gaping holes in the system.
Many of the 'complaints' lodged here may simply be symptoms of SELinux being a work in progress. As it matures, some of these issues may well be resolved. Certainly they are solvable problems. This paper is not addressed to the theoretical underpinnings of security design, but to the practicalities of SELinux as a security mechanism for use today as is.
Since most installations will NOT build a policy from scratch, but instead will customize a default installed policy, we will take all of the hard work that goes into that default policy as granted. Instead, we will look at the case of a single cgi application to be installed into a single virtual domain.
First that CGI will need to be installed on a test server. This is absolutely necessary since each and every type of access to each and every file or socket that the CGI will EVER need must be known up front in order to write a policy module for it. That includes completely harmless accesses that happen only when an obscure but occasionally critical function is invoked. The best (perhaps only) way to do that is to run SELinux in permissive mode. If SELinux is actually needed on the production server, then clearly it must never be in permissive mode. Since permissive or enforcing is all or nothing, and there is no way to specify that a particular group of executables should be audited but not enforced, a test server is the only answer.
Now, a test suite must be devised that causes every possible access to every possible file the CGI will ever need to make. This can be 'guestimated', but if left at that, there will be inevitable unconsidered cases where the CGI WILL fail in production.
Once that is done, there will be a substantial volume of audit information in logs. This may be processed by
audit2allow in order to generate a ruleset that will permit all of it.
It is possible to stop there and install the new policy module, but it will nearly inevitably allow more than is actually needed.
This is due to the granularity of SELinux. SELinux rules operate on types. In turn, types are assigned to files by pathname. Should a case ever arise that some program (domain) needs to be allowed access to some files of a type but not others, the one and only option is to create an additional type for those that are allowable. Unfortunatly, this also means that the rules for every single domain in the system that accesses the original type must now be duplicated for the new subtype. It is NOT possible to declare that a single file has 2 types.
The end result is that policy is not actually modular at all. You will end up modifying the default policy source to add all of those additional rules to the relevant domains. This is because the SELinux policy language does NOT allow any modification to a type later in the source.
For example we might have a base type httpd_t. We want to allow a transition to cgi_myapp_t when Apache runs myapp.cgi. Unfortunatly, this MUST be specified in the httpd_t type rather than in the cgi_myapp_t type module.
While in a DOD/NSA setting there is a great value in being able to consult a single segment of policy to know EXACTLY what a given type may or may not do, including what domains it could potentially transition to, in the rest of the world, we need to keep specifications as modular as possible. We have domain experts who understand nearly everything about a particular program or suite of programs but know little or nothing about other areas of the server. We don't WANT them wasting time learning 'everything under the sun'.
In a development environment, we would prefer that the development and test machines be fully standard except for the application under development, and if SELinux is to be supported, a single module adding rules for the app under development. Being forced to hack on the global policy just to allow our policy module to exist is simply unacceptable.
In production the situation is more extreme. A given server may need to have any (almost) arbitrary combination of packages installed. Each app would naturally need an SELinux policy module to support it's requirements. Unfortunatly, this means we have an arbitrary number of possibly conflicting patches against the global policy that must be applied cleanly. Updating the kernel becomes a week long exercize in pain now that the global policy MUST be updated with it and that arbitrarily large and conflicting patchset MUST be rolled forward.
Since a developer cannot know what the server's global policy will look like, it is not possible to just release a tarball including a policy module source and expect it to 'just work' on any production server at all. We're back to the bad old days where 'installing software' really meant patching software and so required a team of programmers. Again, in a DOD/NSA environment where the future of a country depends on the security of a system and money is no object, this might be a bearable pain, but nobody is going to put up with that in a production web server environment.
The crux of the problem is that SELinux is exactly backwards from what the 'real world' needs and wants in a security system. SELinux sees the 'role' and what that role is allowed to do with each object type as the central access control. That is, it defines subjects and defines what objects it may act on how. This is specified in the subject definition.
In the real world, we need the central element to be the object (type) and to define what roles may access that object. That is, we want to centralize the policy in the object that is acted on. While arguably that is a weakening of the policy specification since it permits restrictions to be lifted in an out of the way policy somewhere, it is much stronger than the default of turning SELinux off in order to get something to work.
For an example, I write myapp. It includes 2 files,
/usr/etc/myapp.spwdb. I know that of those,
myapp.spwdb is the one that contains sensitive passwords and that
myapp.pwdb contains little of interest to hackers. The webmaster and sysadmin don't know those files exiat and they don't WANT to know. Security of myapp is MY responsability.
Unless we want to spend a month in committee meetings, it must be possible for me to specify the policy for myapp and to grant Apache the ability to execute the cgis in myapp. Further, I should be able to create a role
myapp_admin_r that can take care of myapp. All the sysadmin wants to know is who is allowed to have that role. Meanwhile he certainly does NOT want me messing with his global policy. He may be interested to know what I am proposing to permit. If that is all specified neatly in one place, he can just look at my policy source.
In production, installing myapp on an additional server should just require the pre-compiled policy module. Policy source should not be needed on a production server.
If it was just one app, one time that MIGHT be a bearable pain, but it is NOT. The trend is towards multi-service consolidated servers and away from single service machines. Even a dedicated web server doesn't JUST run Apache, it will run Apache plus a variety of CGI applications.
Policy rules are strongly dependant on kernel version. In turn, the SELinux libraries and utilities are strongly dependant on policy version.
So a few weeks later when you run up2date or yum, you get the new kernel with the critical updates, the new SELinux libraries and the new policy that is absolutely necessary for the new kernel version and OOOPS, your hard work is all gone because something changed out from under you. You can try to apply a patch to the new policy source but it won't be likely to compile without substantial changes.
If you DONT upgrade the policy, SELinux will be quite broken and so will a few system programs like su and sudo. To get su to work you'll have to update the policy or set SELinux to disabled.
Many of these issues are related to the all-or-nothing nature of SELinux. While the targeted policy does allow for the existance of an unconfined_t domain that essentially bypasses SELinux while permitting other domains to be confined, this simply makes the all-or-nothing granular to the domain level. Currently no mechanism exists to grant free access to everything except for one or two types.
That is, while there is a mode called permissive, there is no permissive specification method. The most familiar example of a permissive specification is iptables. The tables may be configured strict or permissive in the sense of 'anything not explicitly prohibited is permitted' vs. 'anything not specifically permitted is prohibited'. SELinux only allows for the latter style.
Likewise, if ACLs are enabled in a filesystem, either style of special case may be implemented easily. That is, with an ACL, access rights may be granted OR revoked with the standard Unix permissions specifying a coarser grained default permission.
Fundamentally, there is certainly a place for manditory access controls (as opposed to the familiar discretionary controls). Having support for SELinux in the kernel provides a good framework for putting such a system in place.
The problem is that as implemented, SELinux violates the principle that code (that is the kernel) should implement policy rather than set it. SELinux SETS the policy that anything not permitted is prohibited. While that is stronger than the permissive policy, it is not always appropriate.
No security system can escape from human psychology. The most interesting interaction between a security system or policy and human psychology is that a threshold point always exists. That is, as strictness of a security policy increases, so does actual security up to that threshold. A slight increase in strictness beyond the threshold will result in a sharp decline in actual security.
That threshold is more or less a threshold of pain. The point where the urge to bypass security measures (or disable them) exceeds the understanding of their importance.
In an ideal world, we would never compromise on security, but this is not an ideal world. Security has a benefit (perhaps hard to quantify, but existant). Security has a cost (equally hard to fully quantify). When percieved costs exceed percieved benefits, security will inevitably take a back seat. If a security measure can be scaled finely, it may be scaled back just a bit. If, on the other hand it's all or nothing, it will end up being nothing.