Much of my job is based on writing SELinux policy. The problem is that SELinux is not well suited to the needs of a desktop environment. Consider a web browser. Ideally, it should not be able to read any local files (except its own). But sometimes, users want to use web browsers on local files; and sometimes users want to save/download web resources to arbitrary locations, so you end up needing to give browsers those permissions anyway.
Redhat's approach is a targeted policy. Users and user applications run in an unconfined domain, while system and services run in a secured domain.
Android uses a similar approach where SELinux provides system level protection (as well as application level isolation). However, the security model most people (even Android app developers) are familiar with is not SELinux. The difference between an app that has permission to use the microphone and one that does not is not SELinux. It is implemented by a userspace security manager.
I am not fammilar with Apparmor, but I do not see how any solution based on the Linux kernel's security module system would be adequate. The type of security needed for a desktop system requires a lot of userspace work.
> But sometimes, users want to use web browsers on local files
Well, that's just risky computer usage. I'd think it's a reasonable compromise, if users would copy files into a specific up/download folder for the web browser to access. Such can be enforced with, e.g. firejail.
Clearly this is slightly inconvenient, but security has always been at odds with convenience and always will be.
I've experience this too while writing policies, but firefox could open an unconfined file manager which is like a "selinux setuid", where it asks the user what to do and change the mcs level of a file until it is written to/read. I don't think this is unsolvable.
Edit: actually, that file manager could pass an open fd to firefox, I'm not sure how that would work policy-wise
Or maybe a hook could be added to the kernel in the looks of "if mozilla_t is denied access to user_home_t, execute /usr/bin/notify and if it returns 0 allow it for this time"
You can kind of do that already using SELinux booleans.
In policy, have something like:
if (mozilla_read_user_home) {
allow mozilla_t user_home_t:file read_file_perms;
}
Then, firefox could execute /usr/bin/request_file_access user_home_t read
request_file_access could then be spawned in a privileged domain and:
1) Check the domain of the parent process and see that it is mozilla_t
2) Check whatever security policy it wants to implement (possibly by prompting the user)
3) Assuming the access is allowed, set the mozilla_read_user_home variable to true (and set a timer to disable it at some point in the future)
4) firefox then can open the file normally.
Keep in mind that denials actually happen quite frequently on many SELinux systems, and policy is usualy written to not even log the expected ones. Any hook that needs to run on every denial would likely introduce unacceptable performance hits; particuarly if it needs to spawn another process.
Assuming the file-manager is a typical program that firefox spawn's by calling some form of exec on the relevent executable, it is quite resonable within SELinux for that file-manager to run in a different domain [0]. At this point, that file manager becomes part of the user-space security model I was talking about, where it acts as the gate-keeper of access.
Using MCS is interesting here. It still the problem that it involves relabeling files as a matter of course, which is generally frowned upon. From a practical perspective, this type of relabeling would turn into effectivly a lock [0.5] on the file, where no other "confined" domain can access it. The plus side of using catagories is that you can run all of the applications in the same (or one of a pre-determined set) of application domains while keeping them isolated [1]. This has the side benifit of avoid the issue of applications loading new policy as they are being installed. [2]
My instinct for the problem is to, as your edit suggests, leverage file descriptors. From a non-SELinux perspective, I have never passed file descriptors other than parent -> child, but it appears you can do so with unix sockets [3]. From a policy perspective, there are three sets of permissions that are relevent:
1) Permission to read/write to arbitrary user files. Both firefox and the file manager would need these
2) Permission to open arbitrary user files. Only the file manager would need this; firefox should not have it.
3) Permission to use file descriptors opened by the file manager. Firefox would need this.
When I do a policy analysis, I generally assume that having (1) grants full access to the file; however if your security model dependent on it, there is no reason you could not restrict file access by leveraging (2) and (3).
The userspace implementation would be more complicated than the policy. For example, in the case of a local webpage, firefox does not want to open just a single file, but also any resource file that that file requires (and files that it links to and those resource files). So, what would need to happen is:
1) Firefox spawns file-manager
2) User selected *.html file from local file system
3) Firefox reads selected file and creates a list of resource files it needs to read
4) Firefox spawns file manager and requests read access to resource files.
5) ???
6) File manager either grants or denies access to the resource files.
The hard part is step 5, which is implemented entirely in userspace.
[0] I wouldn't go so far as to say it should run in an unconfined domain, since it should be restricted from, for example, network access.
[0.5] Not a very good lock either; SELinux does not handle access revocation very well.
[1] This is the approach Android takes.
[2] SELinux does have a module system; however there is no mechanism to limit what permissions a module can add.
It doesn't have to be a lock, since MCS can have (AFAIK) 1023 different categories and multiple processes can access the same resource depending on their categories
EDIT: Good point about requesting related resources though, not sure how you'd go about that (I'm not even sure you'd actually want that either since you could include '../../../etc/passwd' or something as an <img> tag)
1023 is the limit. If you can fit within 1023, then a simple MCS policy could work without lock-like implications.
Android works around the 1023 limit by giving each app a set of categories. This works when a file can only ever be owned by 1 app, but becomes more difficult when multiple owners are possible.
E.G. If you want to grant access to the catagory set c1,c10,c20 and c5,c15,c25 then you need to make sure there is nothing running as c1,c15,c20. This is probably doable, but I have not worked out what is does to the effective number of catagories you could have.
> EDIT: Good point about requesting related resources though, not sure how you'd go about that (I'm not even sure you'd actually want that either since you could include '../../../etc/passwd' or something as an <img> tag)
The answer is in step 5. Somehow, the userspace security manager needs to know that firefox is not allowed to use ../../../etc/passwd, but is (maybe?) allowed to use ../index.html.
As I said. SELinux can be an effective component of a secure desktop system. But you need to overlay it with some other security system; and at this point no one has figured out what that security system should look like from a user perspective.
Redhat's approach is a targeted policy. Users and user applications run in an unconfined domain, while system and services run in a secured domain.
Android uses a similar approach where SELinux provides system level protection (as well as application level isolation). However, the security model most people (even Android app developers) are familiar with is not SELinux. The difference between an app that has permission to use the microphone and one that does not is not SELinux. It is implemented by a userspace security manager.
I am not fammilar with Apparmor, but I do not see how any solution based on the Linux kernel's security module system would be adequate. The type of security needed for a desktop system requires a lot of userspace work.