From 48fe07b2d443bd73b277f94b4ab37a630e2f3171 Mon Sep 17 00:00:00 2001 From: Aria Li Date: Tue, 4 Jun 2024 15:00:49 -0700 Subject: [PATCH] (PUP-7126) Replace deprecated Selinux.matchpathcon functions This commit: - Deprecates get_selinux_default_context which calls the deprecated Selinux.matchpathcon and replaces it with get_selinux_default_context_with_handle which calls Selinux.selabel_lookup instead. The new method requires a handle since selabel_lookup requires a handle - Adds a getter method for the class variable, selinux_handle, in the POSIX file provider which is used to get the handle for get_selinux_default_context_with_handle. With this getter method, selinux_handle will only initialized once with Selinux.selabel_lookup. - Updates post_resource_eval in the POSIX file provider to call Selinux.selabel_close instead of the deprecated Selinux.matchpathcon_fini when terminating selinux_handle. After, selinux_handle is set to nil. Co-authored-by: William Bradford Clark --- lib/puppet/provider/file/posix.rb | 18 ++++++++++++++++-- lib/puppet/type/file/selcontext.rb | 13 +++++++------ lib/puppet/util/selinux.rb | 18 ++++++++++++++---- spec/unit/transaction_spec.rb | 10 +++++----- spec/unit/type/file/selinux_spec.rb | 14 +++++++++++--- spec/unit/util/selinux_spec.rb | 29 +++++++++++++++++++++++++++++ 6 files changed, 82 insertions(+), 20 deletions(-) diff --git a/lib/puppet/provider/file/posix.rb b/lib/puppet/provider/file/posix.rb index 445dbb13cbf..f4c645b6336 100644 --- a/lib/puppet/provider/file/posix.rb +++ b/lib/puppet/provider/file/posix.rb @@ -12,8 +12,22 @@ require 'etc' require_relative '../../../puppet/util/selinux' - def self.post_resource_eval - Selinux.matchpathcon_fini if Puppet::Util::SELinux.selinux_support? + class << self + def selinux_handle + return nil unless Puppet::Util::SELinux.selinux_support? + + # selabel_open takes 3 args: backend, options, and nopt. The backend param + # is a constant, SELABEL_CTX_FILE, which happens to be 0. Since options is + # nil, nopt can be 0 since nopt represents the # of options specified. + @selinux_handle ||= Selinux.selabel_open(Selinux::SELABEL_CTX_FILE, nil, 0) + end + + def post_resource_eval + if @selinux_handle + Selinux.selabel_close(@selinux_handle) + @selinux_handle = nil + end + end end def uid2name(id) diff --git a/lib/puppet/type/file/selcontext.rb b/lib/puppet/type/file/selcontext.rb index 225bb5f2c5e..474c64a0ccc 100644 --- a/lib/puppet/type/file/selcontext.rb +++ b/lib/puppet/type/file/selcontext.rb @@ -40,11 +40,12 @@ def retrieve end def retrieve_default_context(property) + return nil if Puppet::Util::Platform.windows? if @resource[:selinux_ignore_defaults] == :true return nil end - context = get_selinux_default_context(@resource[:path], @resource[:ensure]) + context = get_selinux_default_context_with_handle(@resource[:path], provider.class.selinux_handle) unless context return nil end @@ -85,7 +86,7 @@ def sync end Puppet::Type.type(:file).newparam(:selinux_ignore_defaults) do - desc "If this is set then Puppet will not ask SELinux (via matchpathcon) to + desc "If this is set then Puppet will not ask SELinux (via selabel_lookup) to supply defaults for the SELinux attributes (seluser, selrole, seltype, and selrange). In general, you should leave this set at its default and only set it to true when you need Puppet to not try to fix @@ -98,7 +99,7 @@ def sync Puppet::Type.type(:file).newproperty(:seluser, :parent => Puppet::SELFileContext) do desc "What the SELinux user component of the context of the file should be. Any valid SELinux user component is accepted. For example `user_u`. - If not specified it defaults to the value returned by matchpathcon for + If not specified it defaults to the value returned by selabel_lookup for the file, if any exists. Only valid on systems with SELinux support enabled." @@ -109,7 +110,7 @@ def sync Puppet::Type.type(:file).newproperty(:selrole, :parent => Puppet::SELFileContext) do desc "What the SELinux role component of the context of the file should be. Any valid SELinux role component is accepted. For example `role_r`. - If not specified it defaults to the value returned by matchpathcon for + If not specified it defaults to the value returned by selabel_lookup for the file, if any exists. Only valid on systems with SELinux support enabled." @@ -120,7 +121,7 @@ def sync Puppet::Type.type(:file).newproperty(:seltype, :parent => Puppet::SELFileContext) do desc "What the SELinux type component of the context of the file should be. Any valid SELinux type component is accepted. For example `tmp_t`. - If not specified it defaults to the value returned by matchpathcon for + If not specified it defaults to the value returned by selabel_lookup for the file, if any exists. Only valid on systems with SELinux support enabled." @@ -132,7 +133,7 @@ def sync desc "What the SELinux range component of the context of the file should be. Any valid SELinux range component is accepted. For example `s0` or `SystemHigh`. If not specified it defaults to the value returned by - matchpathcon for the file, if any exists. Only valid on systems with + selabel_lookup for the file, if any exists. Only valid on systems with SELinux support enabled and that have support for MCS (Multi-Category Security)." diff --git a/lib/puppet/util/selinux.rb b/lib/puppet/util/selinux.rb index cd2c636f2e7..bfb730ecc95 100644 --- a/lib/puppet/util/selinux.rb +++ b/lib/puppet/util/selinux.rb @@ -46,6 +46,7 @@ def get_selinux_current_context(file) # Retrieve and return the default context of the file. If we don't have # SELinux support or if the SELinux call fails to file a default then return nil. + # @deprecated matchpathcon is a deprecated method, selabel_lookup is preferred def get_selinux_default_context(file, resource_ensure = nil) return nil unless selinux_support? # If the filesystem has no support for SELinux labels, return a default of nil @@ -68,11 +69,20 @@ def get_selinux_default_context(file, resource_ensure = nil) end retval = Selinux.matchpathcon(file, mode) - if retval == -1 - return nil - end + retval == -1 ? nil : retval[1] + end - retval[1] + def get_selinux_default_context_with_handle(file, handle) + return nil unless selinux_support? + # If the filesystem has no support for SELinux labels, return a default of nil + # instead of what selabel_lookup would return + return nil unless selinux_label_support?(file) + + # Handle is needed for selabel_lookup + raise ArgumentError, _("Cannot get default context with nil handle") unless handle + + retval = Selinux.selabel_lookup(handle, file, 0) + retval == -1 ? nil : retval[1] end # Take the full SELinux context returned from the tools and parse it diff --git a/spec/unit/transaction_spec.rb b/spec/unit/transaction_spec.rb index c7c278bf1c7..716d5d07625 100644 --- a/spec/unit/transaction_spec.rb +++ b/spec/unit/transaction_spec.rb @@ -758,15 +758,15 @@ def post_resource_eval transaction.evaluate end - it "should call Selinux.matchpathcon_fini in case Selinux is enabled ", :if => Puppet.features.posix? do - selinux = double('selinux', is_selinux_enabled: true, matchpathcon_fini: nil) + it "should call Selinux.selabel_close in case Selinux is enabled", :if => Puppet.features.posix? do + handle = double('selinux_handle') + selinux = class_double('selinux', is_selinux_enabled: 1, selabel_close: nil, selabel_open: handle, selabel_lookup: -1) stub_const('Selinux', selinux) - + stub_const('Selinux::SELABEL_CTX_FILE', 0) resource = Puppet::Type.type(:file).new(:path => make_absolute("/tmp/foo")) transaction = transaction_with_resource(resource) - expect(Selinux).to receive(:matchpathcon_fini) - expect(Puppet::Util::SELinux).to receive(:selinux_support?).and_return(true) + expect(Selinux).to receive(:selabel_close).with(handle) transaction.evaluate end diff --git a/spec/unit/type/file/selinux_spec.rb b/spec/unit/type/file/selinux_spec.rb index 2292bfd3496..aeedefacebe 100644 --- a/spec/unit/type/file/selinux_spec.rb +++ b/spec/unit/type/file/selinux_spec.rb @@ -50,13 +50,16 @@ end it "should handle no default gracefully" do - expect(@sel).to receive(:get_selinux_default_context).with(@path, :file).and_return(nil) + skip if Puppet::Util::Platform.windows? + expect(@sel).to receive(:get_selinux_default_context_with_handle).with(@path, nil).and_return(nil) expect(@sel.default).to be_nil end - it "should be able to detect matchpathcon defaults" do + it "should be able to detect default context on platforms other than Windows", unless: Puppet::Util::Platform.windows? do allow(@sel).to receive(:debug) - expect(@sel).to receive(:get_selinux_default_context).with(@path, :file).and_return("user_u:role_r:type_t:s0") + hnd = double("SWIG::TYPE_p_selabel_handle") + allow(@sel.provider.class).to receive(:selinux_handle).and_return(hnd) + expect(@sel).to receive(:get_selinux_default_context_with_handle).with(@path, hnd).and_return("user_u:role_r:type_t:s0") expectedresult = case param when :seluser; "user_u" when :selrole; "role_r" @@ -66,6 +69,11 @@ expect(@sel.default).to eq(expectedresult) end + it "returns nil default context on Windows", if: Puppet::Util::Platform.windows? do + expect(@sel).to receive(:retrieve_default_context) + expect(@sel.default).to be_nil + end + it "should return nil for defaults if selinux_ignore_defaults is true" do @resource[:selinux_ignore_defaults] = :true expect(@sel.default).to be_nil diff --git a/spec/unit/util/selinux_spec.rb b/spec/unit/util/selinux_spec.rb index 21c1fea4801..1df1c46b785 100644 --- a/spec/unit/util/selinux_spec.rb +++ b/spec/unit/util/selinux_spec.rb @@ -241,6 +241,35 @@ end end + describe "get_selinux_default_context_with_handle" do + it "should return a context if a default context exists" do + without_partial_double_verification do + expect(self).to receive(:selinux_support?).and_return(true) + expect(self).to receive(:find_fs).with("/foo").and_return("ext3") + hnd = double("SWIG::TYPE_p_selabel_handle") + expect(Selinux).to receive(:selabel_lookup).with(hnd, '/foo', 0).and_return([0, "user_u:role_r:type_t:s0"]) + expect(get_selinux_default_context_with_handle("/foo", hnd)).to eq("user_u:role_r:type_t:s0") + end + end + + it "should raise an ArgumentError when handle is nil" do + allow(self).to receive(:selinux_support?).and_return(true) + allow(self).to receive(:selinux_label_support?).and_return(true) + expect{get_selinux_default_context_with_handle("/foo", nil)}.to raise_error(ArgumentError, /Cannot get default context with nil handle/) + end + + it "should return nil if there is no SELinux support" do + expect(self).to receive(:selinux_support?).and_return(false) + expect(get_selinux_default_context_with_handle("/foo", nil)).to be_nil + end + + it "should return nil if selinux_label_support returns false" do + expect(self).to receive(:selinux_support?).and_return(true) + expect(self).to receive(:find_fs).with("/foo").and_return("nfs") + expect(get_selinux_default_context_with_handle("/foo", nil)).to be_nil + end + end + describe "parse_selinux_context" do it "should return nil if no context is passed" do expect(parse_selinux_context(:seluser, nil)).to be_nil