New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(PUP-2169) Invalidate SELinux matchpathcon cache #6278
Conversation
The `file` resource has 4 selinux parameters (`selrange`, `selrole`, `seltype`, `seluser`). When not set, these default to "the value returned by matchpathcon for the file". When puppet is run as a service, the first call to matchpathcon correctly returns the default SELinux security context for the specified file. But on subsequent runs, matchpathcon returns a cached value. This is a problem if the default context has changed since the puppet service was first started. This leads to puppet doing things like reverting file contexts that were set when an RPM was installed (there are several RPMs which bundle SELinux modules and run `restorecon` on file paths when installed). This bad behaviour is easy to replicate. eg create a very simple puppet module 'selinux-test' and apply that to a test node through PE or Foreman etc. ```puppet # selinux-test/init.pp class selinux-test { file { '/selinux-test': ensure => file, } } ``` Set a very short runinterval... ``` puppet config set --section agent runinterval 30 ``` Wait for puppet to create `/selinux-test` with whatever the default context type is for this path (`etc_runtime_t`). ``` [root@testhost ~]# ls -Z /selinux-test -rw-r--r--. root root system_u:object_r:etc_runtime_t:s0 /selinux-test ``` Change the default context type for this file using `fcontext` ``` semanage fcontext -a -t tmp_t /selinux-test ``` Wait another 30 seconds, tailing the puppet agent log and notice how puppet does *not* update the file. Use `restorecon` to relabel the file manually. ``` [root@testhost ~]# restorecon /selinux-test [root@testhost ~]# ls -Z /selinux-test -rw-r--r--. root root system_u:object_r:tmp_t:s0 /selinux-test ``` Wait for puppet to unhelpfully revert the seltype back to `etc_runtime_t`. Restart the puppet service and note how puppet now changes the seltype back to `tmp_t`. The fix is really simple. Just call `matchpathcon_fini` after the call to `matchpathcon`. The code never explicitly called `matchpathcon_init`, but this was done automatically by the call to `matchpathcon`. From the man page... > If matchpathcon_init() has not already been called, then this function > will call it upon its first invocation with a NULL path, defaulting to > the active file contexts configuration. And on `matchpathcon_fini()`... > matchpathcon_fini() frees the memory allocated by a prior call to > matchpathcon_init.() This function can be used to free and reset the > internal state between multiple matchpathcon_init() calls I was initially concerned that calling `matchpathcon_fini` after *each* call to `matchpathcon` might be awful for performance. I did some testing, and even though it is much slower, it's still so fast as to not be an issue (over 500 calls to `matchpathcon` with `matchpathcon_fini` in less than a second). ``` [root@testhost ~]# cat test.rb require 'selinux' require 'benchmark' list_of_files = Dir['/etc/**/*.*'] puts "Testing with all #{list_of_files.size} files under /etc" time = Benchmark.realtime do list_of_files.each do |file| Selinux.matchpathcon('file',0) Selinux.matchpathcon_fini end end puts "Time elapsed #{time*1000} milliseconds" time = Benchmark.realtime do list_of_files.each do |file| Selinux.matchpathcon('file',0) end end puts "Time elapsed #{time*1000} milliseconds" [root@testhost ~]# /opt/puppetlabs/puppet/bin/ruby test.rb Testing with all 507 files under /etc Time elapsed 881.8768259999999 milliseconds Time elapsed 12.083007 milliseconds ```
CLA signed by all contributors. |
I'm kind of nervous about the performance hit. That increase is non-trivial, as you noted. Ie for something like /opt, this is a big deal (not that you would manage everything in opt, but)... changing your script to do so returns
81 seconds vs 341 milliseconds. Do you know how often this would get invoked and in what scenarios? I'm not super familiar with our selinux support yet. |
The worse case scenario I could think of is when you're using a file resource on a directory with The 8746 files in my /opt take very approximately 1 minute on my test system or 2 minutes with the change.
I've been trying to come up with ways you could conditionally invalidate the cache. I can't think of anything simple, sane or without corner-cases waiting to bite. (eg perhaps if you knew that the next resource you applying was also a file resource you'd skip the invalidation somehow?) I think living with the performance drop and documenting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling matchpathcon_fini
definitely seems like the correct behavior. Otherwise we're relying on the ruby layer to free whatever memory was allocated implicitly during the matchpathcon
call, and relying on finalizers for memory deallocation is asking for trouble.
Thanks for the contribution @alexjfisher. That is good enough for me. |
@MosesMendoza Is this suitable for a backport to 4.x? (or even 4.10.x)? |
Yeah I think this is probably safe for the 4.10.x line. @alexjfisher can you rebase / retarget? |
(I'm also happy to do the rebase / separate PR if you don't have time, @alexjfisher) |
@MosesMendoza I've opened a new PR targeting 4.10.x. I'm not sure how you manage your branches, but I guess you'll merge the new PR, close this one, and all of 4.10.x gets periodically merged into master (or maybe into 5.3.x and then into master)? |
@alexjfisher yes that's correct |
superseded by #6289 |
BTW, Sorry about the daft bug in my test.rb script above. |
The
file
resource has 4 selinux parameters (selrange
,selrole
,seltype
,seluser
). When not set, these default to "the valuereturned by matchpathcon for the file". When puppet is run as a
service, the first call to matchpathcon correctly returns the default
SELinux security context for the specified file. But on subsequent
runs, matchpathcon returns a cached value. This is a problem if the
default context has changed since the puppet service was first started.
This leads to puppet doing things like reverting file contexts that were
set when an RPM was installed (there are several RPMs which bundle
SELinux modules and run
restorecon
on file paths when installed).This bad behaviour is easy to replicate. eg create a very simple puppet
module 'selinux-test' and apply that to a test node through PE or
Foreman etc.
Set a very short runinterval...
Wait for puppet to create
/selinux-test
with whatever the defaultcontext type is for this path (
etc_runtime_t
).Change the default context type for this file using
fcontext
Wait another 30 seconds, tailing the puppet agent log and notice how
puppet does not update the file.
Use
restorecon
to relabel the file manually.Wait for puppet to unhelpfully revert the seltype back to
etc_runtime_t
.Restart the puppet service and note how puppet now changes the seltype
back to
tmp_t
.The fix is really simple. Just call
matchpathcon_fini
after the callto
matchpathcon
. The code never explicitly calledmatchpathcon_init
, but this was done automatically by the call tomatchpathcon
.From the man page...
And on
matchpathcon_fini()
...I was initially concerned that calling
matchpathcon_fini
after eachcall to
matchpathcon
might be awful for performance. I did sometesting, and even though it is much slower, it's still so fast as to not
be an issue (over 500 calls to
matchpathcon
withmatchpathcon_fini
in less than a second).