Bug 1660142
| Summary: | SELinux policy contains a lot of type_transition rules, leading to high memory usage by kernel | ||
|---|---|---|---|
| Product: | Red Hat Enterprise Linux 8 | Reporter: | Marko Myllynen <myllynen> |
| Component: | selinux-policy | Assignee: | Zdenek Pytela <zpytela> |
| Status: | CLOSED WONTFIX | QA Contact: | BaseOS QE Security Team <qe-baseos-security> |
| Severity: | medium | Docs Contact: | |
| Priority: | medium | ||
| Version: | 8.0 | CC: | dwalsh, eparis, fweimer, lvrabec, mmalik, omosnace, plautrba, sdsmall, ssekidde, zpytela |
| Target Milestone: | rc | Keywords: | Triaged |
| Target Release: | --- | ||
| Hardware: | Unspecified | ||
| OS: | Unspecified | ||
| Whiteboard: | |||
| Fixed In Version: | Doc Type: | If docs needed, set a value | |
| Doc Text: | Story Points: | --- | |
| Clone Of: | Environment: | ||
| Last Closed: | 2020-03-05 09:36:59 UTC | Type: | Bug |
| Regression: | --- | Mount Type: | --- |
| Documentation: | --- | CRM: | |
| Verified Versions: | Category: | --- | |
| oVirt Team: | --- | RHEL 7.3 requirements from Atomic Host: | |
| Cloudforms Team: | --- | Target Upstream Version: | |
| Embargoed: | |||
| Bug Depends On: | 1682526 | ||
| Bug Blocks: | |||
|
Description
Marko Myllynen
2018-12-17 15:46:41 UTC
Note that the size of (binary) policy grew quite a bit between RHEL-7 and RHEL-8: RHEL-7 (x86_64): # wc -c /sys/fs/selinux/policy 3851447 /sys/fs/selinux/policy RHEL-8 (x86_64): # wc -c /sys/fs/selinux/policy 8349112 /sys/fs/selinux/policy When converted into the kernel's internal structures it likely takes up even more memory than that so that might explain why the difference is bigger than expected. There might be also other factors influencing memory usage (e.g. AVC cache size), but this looks like the most likely suspect so far. Comparing the output of seinfo on 7 and 8, the 'Type_trans' number particularly stands out (all the other ones are similar across both policies): RHEL-7: Type_trans: 18061 RHEL-8: Type_trans: 234632 Lukas, any idea why the policy is so much larger on RHEL-8 compared to 7? So, I dumped all type transition rules on Fedora 29 (which has very similar seinfo numbers as RHEL-8) by running 'sesearch -T >typetrans.txt' and I got an ~18MB text file with 242229 lines (seinfo reports 'Type_trans: 242229' on F29). Turns out more than half of these rules are of the form: type_transition <something>_t device_t:(chr|blk)_file <something>_device_t "<something>"; What I find strange is that a lot of these rules appear to be rather arbitrary, e.g.: [...] type_transition cloud_init_t device_t:chr_file v4l_device_t "em83006"; type_transition cloud_init_t device_t:chr_file sound_device_t "mixer9"; type_transition cloud_init_t device_t:blk_file fixed_disk_device_t "ram4"; type_transition cloud_init_t device_t:blk_file fixed_disk_device_t "sdd9"; [...] type_transition devicekit_disk_t device_t:chr_file modem_device_t "noz6"; type_transition devicekit_disk_t device_t:chr_file v4l_device_t "radio0"; type_transition devicekit_disk_t device_t:chr_file tape_device_t "st00"; [...] What the hell?! The situation seems to be similar on RHEL-8: # wc -l typetrans.txt 234632 typetrans.txt # grep -c 'device_t:' typetrans.txt 147307 On RHEL-7 there are no such rules at all: # wc -l typetrans.txt 66471 typetrans.txt # grep -c 'device_t:' typetrans.txt 0 I just realized I was working over an unsorted file when I thought it had been already sorted... The rules are not arbitrary, there is just a *lot* of domains that are part of 'named_filetrans_domain' and this generates a type_transition rule for each device (of which there are apparently about 1700...) for each of these domains. Still, this is a terrible situation and I wonder if we can do without these rules since they are apparently not enabled/needed on RHEL-7? Or at least reduce the number of domains that belong to 'named_filetrans_domain'? (Ideally we'd have some kind of domain groups support in the kernel, but that would be a really big RFE...) Moving to selinux-policy, since I am pretty confident now that the root of the problem lies there. devtmpfs created problems for SELinux; previously, there was just a regular tmpfs /dev and udev handled node creation and labeling, and was modified for SELinux to set the labels correctly when the nodes were first created based on file_contexts, which maps pathname regexes and file types to security contexts. With devtmpfs, the kernel creates the device nodes initially and lacked sufficient information to label them correctly, leaving them temporarily in some default label (e.g. device_t) until udev gets around to labeling them correctly, and this was breaking things. Red Hat added a named type transition rules to cover them all for all unconfined domains IIRC, but named type transition rules weren't really designed for this purpose - they were for distinguishing e.g. /etc/shadow vs /etc/passwd default labels and other corner cases where we needed to take the last component name into account since the creating process and the parent directory were the same for multiple files that needed different contexts. In Android, we avoid the problem just by disabling devtmpfs entirely; I don't really understand the benefit when using udev (ueventd in Android) anyway. Anyway, options would include: - Being far more selective about what domains we define these named type transitions for, - Introducing some degree of wildcarding or regex support to the named type transition rules (requires changes to libsepol and the kernel) rather than only doing exact matches, - Disabling devtmpfs. Maybe others I haven't thought of. Not sure though why this differs for RHEL-7 since it also used devtmpfs? Actually, I do see type_transition rules on device_t on RHEL-7 too, although not so many. Maybe your grep isn't quite general enough to cover differences in output format for sesearch between setools3 (RHEL-7) and setools4 (RHEL-8)? (In reply to Stephen Smalley from comment #7) > Not sure though why this differs for RHEL-7 since it also used devtmpfs? > Actually, I do see type_transition rules on device_t on RHEL-7 too, although > not so many. Maybe your grep isn't quite general enough to cover > differences in output format for sesearch between setools3 (RHEL-7) and > setools4 (RHEL-8)? You're right, I was grepping for the wrong string on RHEL-7. The correct RHEL-7 count is: # sesearch -T | egrep -c "device_t : (blk|chr)_file" 17402 Perhaps if we changed the transition rule policy to be for kernel_t, we could get the same results with a lot less rules? (In reply to Daniel Walsh from comment #9) > Perhaps if we changed the transition rule policy to be for kernel_t, we > could get the same results with a lot less rules? Yes, why do we need the rules for anything except for kernel_t in the first place? The files are created from the kdevtmpfs kernel thread running in kernel_t, and before that was introduced, they were switching creds to the init cred so it was also kernel_t. I don't understand why the rules were added for other domains. Well they do make some sense for unconfined_t as well, but I would be willing to sacrifice this for the memory savings. mknod /dev/foobar Ending up with foobar_device_t is a nice feature. Lukas lets get this change into Rawhide, and see how much it saves and if it causes any issues. (In reply to Daniel Walsh from comment #13) > Well they do make some sense for unconfined_t as well, but I would be > willing to sacrifice this for the memory savings. > > mknod /dev/foobar > > Ending up with foobar_device_t is a nice feature. mknod could/should be using selabel_lookup() or matchpathcon() or similar to label new device nodes from userspace, no need to pollute the kernel policy for that. On the kernel side, extending the name-based type transitions to support some form of wildcarding or regex matching would obviously reduce the number of rules required per domain, e.g. instead of bcache0, bcache1, ..., we could have one rule for bcache[0-9] or bcache*. Even just simple prefix match i.e. terminal wildcard would improve things. man mknod
...
-Z set the SELinux security context to default type
But the user still needs to remember to execute the -Z. Sadly touch to change the default.
We have similar features in mv, cp, install, but none are default on SELinux systems. And I thought
from a security point of view for MLS systems that would be bad.
The kernel stuff we talked about years ago, but it was never implemented. I did some more digging in the kernel and userspace code and after some experiments I arrived at the following: 1. The kernel already *does* have support for type attributes (contrary to what I assumed initially), *but* they are only recognized for allow rules. The userspace has no other choice than fully expand type attributes in all other rules when generating the binary policy. 2. Adding kernel-side support for type attributes in type transition rules is relatively easy and I already have a preliminary patch for this. Making userspace not expand all type transition rules is going to be more tricky, but definitely doable. 3. After applying a proof-of-concept patch to libsepol that does not expand type attributes in named file transition rules, the size of Fedora Rawhide policy dropped from the current ~8.4 MiB to only ~2.8 MiB (!!). The total amount of type transition rules was reduced from ~250K to only ~28K. The RHEL-8 policy is similar to the Fedora one, so we can also expect a similar effect there. I'll open separate bugs for this functionality for Fedora and RHEL-8 and link them to this one. I'll leave this one open for discussion about possible selinux-policy-side mitigation (removing some of the transition rules) in case we want to do that as well. Hi Ondrej, Numbers looks really great! Could you link new bugs to this one? I agree to keep this bug open for further investigation in policy. Thanks for help, Lukas. Mildly related to this: We recently committed a change on Rawhide [1], which removes ~23 MB of redundant files from selinux-policy-{targeted|minimum|mls}. These files were not shipped on RHEL-7, but they are shipped on RHEL-8 (they were added in Fedora in an attempt to fix BZ 1290659, which was later incompletely reverted and the unused files leaked to RHEL-8).
Before:
$ du -sh /usr/share/selinux/
23M /usr/share/selinux/
After:
$ du -sh /usr/share/selinux/
232K /usr/share/selinux/
[1] https://src.fedoraproject.org/rpms/selinux-policy/c/e4f809196473459c18881b41a2bd93fbce579c25?branch=master
FYI, I found a way to store the filename transition rules in memory more efficiently, see: https://lore.kernel.org/selinux/20200212112255.105678-1-omosnace@redhat.com/T/ The above kernel patch series should resolve the issue of increased memory usage by kernel. Eventually I plan to follow up with patches to update the binary policy format so that on-disk usage is also reduced. The issue with Fedora/RHEL-8 policy overusing these rules/unconfined domains remains (that is a much more tricky problem), but at least the performance/memory impact should be mitigated in future kernel versions. We are looking for a solution in kernel in the upstream. Closing this bz as WONTFIX, the footprint and performance impact is expected to be resolved as a part of the next RHEL major release. Just FYI, I posted some kernel + userspace patches that also reduce the binary policy size impact of too many filename transitions: https://lore.kernel.org/selinux/20200327151941.95619-1-omosnace@redhat.com/T/ https://lore.kernel.org/selinux/20200328124550.199568-1-omosnace@redhat.com/T/ With these patches the binary policy size should get back to RHEL-7 numbers. |