In my quest to understand Fedora's SELinux policies, I've looked at the policy source a bit, but it's all 11+ years of m4 macros and requires paging in quite a lot more context than I currently have.

OTOH, I used dedispol to just dump the "assembly language" version of my system's running policy, and I'm getting decent mileage out of just grep and sort.

On this system, to be unconfined the binary systemd executes needs to be tagged bin_t (/bin and /sbin binaries) or usr_t (/usr files).

My next question is, if I'm unpacking a general purpose filesystem not just one binary, can I just slap bin_t on all of them and have things work out?

Annoyingly, the answer seems to be: nope, the permissions are tight. unconfined_t is allowed to _begin execution_ at a binary marked bin_t, but has no other permissions over it, not even the ability to execute bin_t again (i.e. it execution has to be initiated from another security domain)

This is a really neat separation of privileges trick SELinux can do that I mentioned the other day, but I like it so much it bears repeating: you can separate the permission to execute a program, from the permission for a program to begin executing.

systemd running as init_t calls execve("/some/binary/tagged/bin_t"). SELinux policy grants init_t file::execute on bin_t, so that's fine.

By default, executed programs inherit the identity of whoever exec'd them, but you can override that.

Fedora's policy defines this type transition rule: when init_t execs a bin_t file, the new program shall run as the identity unconfined_service_t, instead of retaining its launcher's init_t identity.

This happens atomically as part of execve(), while the new process is being set up.

Oh and the type transition rule just specifies what change should happen, it doesn't _permit_ the change. So there's a separate allow rule that permits init_t to "domain transition" to unconfined_service_t.

Finally, it's time for the program to execute, right?

Not so fast! Is unconfined_service_t allowed to do that? This binary could be untrusted, and although someone can _ask_ to execute it with some sensitive identity and permissions, that would be discretionary access control (the program calling execve decides the new process's permissions). This is SELinux, you can't just use your discretion to grant permissions to stuff!

So after the domain transition, but before the program starts executing, there is one last permission check against the system policy: unconfined_service_t must have the file::entrypoint permission on bin_t files.

Note this doesn't mean that unconfined_service_t is allowed to execve() a bin_t file, that's the file::execute permision. file::entrypoint is, effectively, the permission to begin running the program.

And so, you end up with the following SELinux rules:

allow init_t bin_t : file execute

allow init_t unconfined_service_t : process transition

type_transition init_t bin_t : process unconfined_service_t

allow unconfined_service_t bin_t : file entrypoint

To express "init_t can exec bin_t files. When it does, the new program runs as unconfined_service_t unless the caller requested otherwise."

The separation of the type_transition rule and the 'allow' rule is deliberate design: type_transition just expresses the default behavior that happens, if the caller of execve() doesn't ask for something else.

An SELinux aware launcher can call setexeccon() before execve(), to tell SELinux what identity it would like the child to run as, overriding what type_transition would do.

Of course, it can't just ask for anything! You still need the right process::transition permission.

This gets you a nice hybrid universe: programs don't _have_ to be aware of SELinux, they can just exec stuff, and the system policy can specify what the new program's SELinux identity is, based on the identity of execve()'s caller and the identity of the executable. This gives you a ton of flexibility to transparently start jailing programs without needing to boil the ocean and teach all of them about SELinux.

But, programs that are aware of SELinux can be granted the permission to run other programs as many different identities, and on each execve they can tell SELinux which one they'd like.

The system policy is still in control of what processes can do, but it can delegate to programs some choices of which permission sets to use. Or the programs can choose to not care, in which case the system policy can do basic things by itself.

And the file::entrypoint permission is especially neat, because it allows the system policy to restrict what permissions a program can _ever_ run as, regardless of what other policies say or what you might have tricked another program into doing. Even if you can trick someone to execute a program with the wrong permissions, the system policy can go "hold on, that's not right, this program isn't allowed to begin executing with these permissions" and block it.

You can see how NSA would like this (SELinux was commissioned by the NSA, if you didn't know).

With this, you can express: "I don't care what anyone manages to persuade execve() to do, the program that serves files over HTTP with no authentication can never, ever begin executing with a permission other than nothing_of_value." If you want to allow it to serve sensitive stuff, you need to both allow something to execute the program like that, and also allow the program to be executed like that.

Anyway this isn't getting me any closer to my ability to yolo this bit of behavior I want, because unconfined_service_t is _less_ restricted than other identities, but it's still not up for the level of shenanigans I'm bringing to the table here.

Possibly the shortest path to unblocking me is writing a little policy module that defines an identity that runs in permissive mode then tag my stuff with that and then iterate based on what the audit log screams about.

Hmm but I must be missing something here, because dumping all the permissions unconfined_service_t has... this isn't unconfined at all, this is in fact quite confined. That suggests I'm missing something where it has more permissions that align with the "unconfined" vibe it's aiming for...

Redhat's docs on unconfined_service_t explicitly says that its policy makes the unconfined service effectively subject to only discretionary access control, with SELinux letting mostly anything happen.

The problem is, that really doesn't line up at all with what I see in `sedispol` output, there are a lot of narrow specific permission grants, but nothing that translates to "Ignore mandatory access control thx"

So what am I missing...

The high level policy source code is quite unambiguous. It has: unconfined_domain(unconfined_service_t), which expands to "permit all capabilities, all file operations, all process operations, all everything operations everywhere".

So... I must be using sedispol wrong and missing a big chunk of policy somewhere.

aaah ok that's what I was missing. When the Fedora policy gives unconfined_service_t all file permissions, it's not setting allow rules directly on unconfined_service_t.

Instead, it's adding unconfined_service_t to the files_unconfined "attribute set", loosely a list of identities that can be used in rules to grant permissions to many identities at once.

And then it grants "do whatever to all the files" to the files_unconfined set of identities.

But that doesn't show up in my naive greps :)

I wonder if there's a way to make sedispol expand attribute sets on read-back, because that level of indirection makes a ton of sense for representing the policies in a compact binary form, but wow that took me a while to figure out.

@danderson I wrote an exercise for my students to learn how to do that: https://tim.siosm.fr/cours/tds/selinux_module/

It's in french but should translate well 🙂.

3.2 Module SELinux

Sécurité des infrastructures de virtualisation

TDs INSA-CVL

@siosm Encore plus simple que la traduction, je suis Francais 😁 Merci!

(switching back to English because no accents on keyboard...)

So far I've only learned the kernel policy language and CIL, so this is exactly what I need to get an intro to the Reference Policy framework and tools :)

@danderson Ah ah! I have the slides at https://tim.siosm.fr/cours/ as well for the SELinux part 🙂
Cours enseignés à l'INSA Centre Val de Loire

@danderson yes that’s very unfortunate that it’s so hard to unconfine with systemd.
@eckes Yes although understandable as well, if it was easy to convince the service manager to break out of containment, that would be a problem :) But aside from my own not knowing selinux very well, I'm making progress!