Search code examples
kernelrpmrpmbuildselinux

SELinux permission denied error when installing a kernel module via an RPM package


I am trying to create an RPM installer that at loads a kernel module using an install script that calls insmod. The directory it's installing to is /opt/nfast and looking at /etc/selinux/targeted/contexts/files/file_context I note that that files installed here get the default context:

/opt/nfast(/.*)?        system_u:object_r:pki_common_t:s0

Which I see once the rpm installer has done its job:

[root@localhost nfast]# ls -laZ
total 12
drwxr-xr-x. 2 root root system_u:object_r:pki_common_t:s0   37 May 12 00:47 .
drwxr-xr-x. 3 root root system_u:object_r:usr_t:s0          19 May 12 00:44 ..
-rw-r--r--. 1 root root system_u:object_r:pki_common_t:s0 4296 May 12 00:46 hello.ko
-rwxr-xr-x. 1 root root system_u:object_r:pki_common_t:s0   48 May 12 00:46 install

I've created a minimal example as seen below, and I cannot work out why it fails to install via the RPM installer, but works fine when I directly call the script afterwards via the command line.

[root@localhost ~]# rpm -ivh hello-1-1.el8.x86_64.rpm

Verifying...                          ################################# [100%]
Preparing...                          ################################# [100%]
Updating / installing...
   1:hello-1-1.el8                    ################################# [100%]
insmod: ERROR: could not insert module /opt/nfast/hello.ko: Permission denied
warning: %post(hello-1-1.el8.x86_64) scriptlet failed, exit status 1

What is happening? Why is my RPM being disallowed to do this?

SPECS/hello.spec

Name:           hello
Version:        1
Release:        1%{?dist}
Summary:        none

License:    none
Source0:        %{name}-%{version}.tar.gz

BuildRequires:  gcc make

%define debug_package %{nil}

%description
none

%prep
%autosetup

%build
%make_build

%install
rm -rf $RPM_BUILD_ROOT
mkdir -p %{buildroot}/opt/nfast
cp %{_builddir}/%{name}-%{version}/hello.ko %{buildroot}/opt/nfast
install -D -m 0755 %{_builddir}/%{name}-%{version}/install %{buildroot}/opt/nfast

%post
/opt/nfast/install

%files
/opt/nfast/hello.ko
/opt/nfast/install

%changelog
* Thu May 12 2022 Sam
- 

SOURCES/hello-1/hello.c

#include <linux/module.h>
#include <linux/kernel.h>

int init_module(void)
{
    printk(KERN_INFO "hello world\n");
    return 0;
}

void cleanup_module(void)
{
    printk(KERN_INFO "goodbye world\n");
}

SOURCES/hello-1/Makefile

obj-m += hello.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

SOURCES/hello-1/install

#!/bin/sh
dos="/opt/nfast/hello.ko"
insmod $dos

Solution

  • OK, I eventually figured this out. There were some tools I wasn't aware of, and my understanding of SELinux wasn't as thorough as needed to debug the problem.

    First I noticed that my install script worked when installed to /opt rather than /opt/nfast, so I took a look at the labels and noticed a different type for each directory: usr_t, pki_common_t.

    $ ls -laZ /opt
    total 4
    drwxr-xr-x. 1 root root system_u:object_r:usr_t:s0         58 Jun  9 15:59 .
    
    $ ls -laZ /opt/nfast
    total 64
    drwxr-xr-x. 1 root root system_u:object_r:pki_common_t:s0    16 Jun  9 15:59
    

    I then looked at what context is applied to these directories and subdirectories by default, thinking that maybe an nfast subdirectory directory had been mislabelled:

    sudo semanage fcontext -l | ag /opt/nfast
    /opt/nfast(/.*)?                                   all files          system_u:object_r:pki_common_t:s0
    /opt/nfast/sbin/init.d-ncipher                     all files          system_u:object_r:initrc_exec_t:s0
    /opt/nfast/scripts/init.d/(.*)                     all files          system_u:object_r:initrc_exec_t:s0
    
    $ sudo semanage fcontext -l | ag -Q '/opt/.*'
    /opt/.*                                            all files          system_u:object_r:usr_t:s0 
    
    

    So I then ran the RPM installer again which prompted the denial in the system logs, and this time ran the ausearch tool to get some more detailed info, and saw that (obviously) the module_load was being denied, and that the source context needed access to the target context: kmod_t needed to be able to do stuff to pki_common_t objects.

    $ sudo ausearch -m avc -ts recent | grep hello
    type=AVC msg=audit(1654759755.359:184): avc: denied { module_load } for
    pid=3859 comm="insmod" path="/opt/nfast/hello.ko" dev="dm-0" ino=134637031
    scontext=unconfined_u:unconfined_r:kmod_t:s0-s0:c0.c1023
    tcontext=system_u:object_r:pki_common_t:s0 tcla ss=system permissive=0
    

    audit2why can help detail this error a bit further:

    ➜ sudo ausearch -m avc -ts recent | grep hello | sudo audit2why
    type=AVC msg=audit(1654828902.311:733): avc:  denied  { module_load } for  pid=21558 comm="insmod" path="/opt/nfast/hello.ko" dev="dm-0" ino=67723333 scontext=unconfined_u:unconfined_r:kmod_t:s0-s0:c0.c1023 tcontext=system_u:object_r:pki_common_t:s0 tclass=system permissive=0
    
            Was caused by:
                    Missing type enforcement (TE) allow rule.
    
                    You can use audit2allow to generate a loadable module to allow this access.
    

    Thankfully I learnt that the tool audit2allow can create this type enforcement policy rule, which can then later be installed into selinux.

    sudo ausearch -m avc -ts recent | grep hello | sudo audit2allow -R -m local > local.te
    
    
    ➜ cat local.te                                                                                                    
                                                                                                                      
    policy_module(local, 1.0)                                                                                         
                                                                                                                      
    require {                                                                                                         
            type pki_common_t;                                                                                        
            type kmod_t;                                                                                              
            class system module_load;                                                                                 
    }                                                                                                                 
                                                                                                                      
    #============= kmod_t ==============                                                                              
    allow kmod_t pki_common_t:system module_load; 
    

    Using the steps described in the man page for audit2allow I compiled the resulting local.te file which gave me a local.pp file.

    ➜ make -f /usr/share/selinux/devel/Makefile local.pp                                                              
    Compiling targeted local module                                                                                   
    Creating targeted local.pp policy package                                                                         
    rm tmp/local.mod.fc tmp/local.mod 
    

    Finally I then added a semodule -i /opt/nfast/local.pp to install the module in the %post install phase of the RPM (and before the install script is run). This worked!