WIP: Multiple policy cache directories
Implement support for multiple policy cache directories. A new policy cache dir will exist for each unique set of AppArmor kernel features that the parser sees. By default, the policy cache directories are located in
/etc/apparmor.d/cache.d/. Each policy cache directory is a 8 character string which is the output of a djb2 hash where the input is the features string that's constructed from apparmorfs (or a different source if specified on the parser command line).
This results in cache directories such as
/etc/aparmor.d/cache.d/db76596e/. Their contents are identical to the contents of
Admins can discover the location of the policy cache directory path of the currently running kernel by passing the
--print-cache-dir option to
Outside of general discussion about this approach, there are some known issues with this merge request that need addressing before the "WIP" prefix can be removed from the merge request:
The parser does not yet expose the
- Hash collisions are not detected/handled. (Is this needed?)
- Tests are needed to identify changes in the calculations so that we don't unknowingly modify the algorithm.
- Tests are needed to stress this feature some more.
- Consider how this feature melds with policy versioning.
Nothing todo yet, and definitely something I want but I think we are going to have to revisit some of the details for use in combination with policy versioning. I'm working on draft for policy versioning so that we can start the discussion and get the details nailed down.
I agree that there's a strong tie-in with policy versioning which is why I dusted this old branch off and got it working. I think that they should be treated as two individual features but design decisions made in one feature could influence the other.
Yes Hash collision detection is required. While a collision is unlikely, a collision resulting in the wrong policy being loaded could be catastrophic. From broken systems on boot to applications not being confined.
Another thing to consider is switching from djb2 hashing to something better. Possibly a murmur2 or murmer3 based hash. https://github.com/aappleby/smhasher/wiki/MurmurHash3
In the work I did on policy hashing and also type caching I went with murmur hash 2 because it had a 2a variant allowing for incremental builds. However there now seems to be a pmurmur hash implemention of murmur hash3 in pure C and allowing for incremental builds (unfortunately its only the 32 bit variant).
I agree. I think the easiest way to handle this is to detect the collision and then purge the cache directory before proceeding. A hash collision is going to be very rare and the potential for a user to boot back and forth between the two kernels that caused the collision is extremely small.
I think I agree that purging or skipping the cache is probably sufficient. I do have questions around what we will do when systemd is doing early policy load based off of the cache and there is no compiler fallback, until later in the boot.
Isn't that a mostly separate problem that exists any time there's not a valid cache available?
Yes. However with multiple caches available supporting early policy loads becomes much more feasible, and I see it being used more. Ideally I think any such collisions should be detected during a policy build phase during a session. If you are relying the cache/binary policy to exist at early boot any issues should be resolved before doing such a boot. My questions really revolve around how we could resolve such collisions, not at boot but the policy build phase.
So having thought about it more, I think in early boot (no compiler fallback) skip is the appropriate thing to do, because even if it purges it can't do anything and will end up skipping anyways. I think we should leave purging and replacing up to the compiler.
changed title from WIP: Multiple policy cache directores to WIP: Multiple policy cache directoriesToggle commit list
I think in early boot (no compiler fallback) skip is the appropriate thing to do
Note that even not loading a profile can cause trouble if a service file has
=-whatever) because in this case systemd will prevent starting the service if the AppArmor profile is not loaded.
Yes not loading a profile is bad too, either one can cause breakage. However for early profile loads where a compile is not feasible it may not even be possible to load the existing cache files. The only place to deal with this properly is later with a policy compile and a reboot into the newly created cache, a boot time compile won't fix it for early policy.
One thing we could consider is allowing systemd to set an apparmor mode if its early policy load is skipped, this would allow disabling apparmor, keeping the current behavior, or even breaking the boot (opt in only). Disabling would allow avoiding bad policy breakage, while breaking the boot would ensure the system would not come up if there were problems in loading the security policy.
This is beyond what is covered by this merge request but fits with the discussion
Another possibility for handling missing cache in early boot is doing what selinux is doing to relabel the fs (https://lwn.net/Articles/693620/). Basically it adds a systemd generator unit in early boot, if it determines that the fs needs to be relabeled, it diverts boot to a minimal target with selinux in permission mode, does the relabel, and then reboots.
AppArmor could do something similar where if the cache is missing, the boot is diverted, the cache is generated and then we reboot.
Note: that an empty cache is different than a cache not existing, or being stale, and it could be possible to split the cache such that this would only be done for profiles that require their load to occur during early boot.