Search code examples
filelinux-kernelshared-librariesxattrlinux-security-module

Linux Security Module: Is there a way to check/audit shared library loading?


I'm working on a linux security module based on xattr (file's extended attributes), and I want to implement security measures to prevent shared libraries without certain xattr from loading. This requires me to hook file_open and check the file's magic number to determine if it's a shared library, then do xattr based security checks.

The following code is my implementation (not completely finished yet). The hooked file_open xattracl_file_open(struct file *file) calls xattracl_magic_check_elf(struct file* file) to check if the file is ELF format by reading its first 4 bytes.

But I found two issues. Firstly, I can't get the file path directly from this struct file, which will always be printed as (efault), therefore I used a self defined xattracl_get_realpath(struct path *path) (similar to what the tomoyo LSM module does). Secondly, Every time the 4 bytes being read, they seem to have different values, which I completely can't comprehend.

Then I tried to call flip_open() since I can get the real pathname and get the "real" struct file to read its 4 bytes, and this won't work since the hooked file_open() will call itself recursively and lead to kernel panic.

The code (some parts are omitted for easier reading):

// ............

// Read magic number from a file's first 4B
// Param:
//  struct file *file: the file to be checked
// Return:
//  (int)-ENOMEM/-ENOENT/0/1 for memory allocation error/file reading error/being ELF/not ELF
int xattracl_file_magic_check_elf(struct file *file){
    const char *realpath=xattracl_get_realpath(&file->f_path);
    if(!realpath){
        return -ENOMEM;
    }
    char *value=(char*)kmalloc(4,GFP_NOFS);
    if(!value){
        return -ENOMEM;
    }
    kernel_read(file,value,4,0)==-1);
    int iself=(value[0]==0x7F)&&(value[1]==0x45)&&(value[2]==0x4C)&&(value[3]==0x46);
    printk(KERN_INFO"[xattracl] (file_magic_check_elf) file:%s magic:%02X%02X%02X%02X iself:%d\n",realpath,value[0],value[1],value[2],value[3],iself);
    filp_close(realfile,NULL);
    kfree(realpath);
    kfree(value);
    return iself;
}

// Hooked file_open for ELF loading control
// Param:
//  struct file *file: the file to be checked
// Return:
//  (static int)-ENOMEM/-EPERM/0 for memory allocation error/denying loading/allowing loading
static int xattracl_file_open(struct file *file){
    // If file is not ELF, allow loading.
    int iself=xattracl_file_magic_check_elf(file);
    if(!iself){
        // Only return 0 for testing
        // return -EPERM;
        return 0;
    }
    // Get the file's xattr
    struct dentry *dentry=file->f_path.dentry;
    struct inode *inode=d_backing_inode(dentry);
    char *value=(char*)kmalloc(XATTRACL_XATTR_VALUE_SIZE,GFP_NOFS);
    if(!value){
        return -ENOMEM;
    }
    int size=__vfs_getxattr(dentry,inode,XATTRACL_XATTR_NAME,value,XATTRACL_XATTR_VALUE_SIZE);
    value[MAX(size,0)]=0;
    // Users can only load shared libraries with XATTRACL_XATTR_NAME:XATTRACL_XATTR_ACTION_ALLOW
    int action=strcmp(value,XATTRACL_XATTR_ACTION_ALLOW)?-EPERM:0;
    printk(KERN_INFO"[xattracl] (file_open) file:%s type:%s "XATTRACL_XATTR_NAME":%s action:%s\n",dentry->d_name.name,iself?"OTHER":"ELF",value,action?"deny":"allow");
    kfree(value);
    // Only return 0 for testing
    // return action;
    return 0;
}

// ............

static struct security_hook_list xattracl_hooks[] __lsm_ro_after_init={
    // ............
    LSM_HOOK_INIT(file_open,xattracl_file_open),
    // ............
};

// ............

The dmesg logs for this weird phenomenon:



[pairman@fedora Downloads]$ dmesg | grep /usr/lib64/libc.so.6

[  176.002714] [xattracl] (file_magic_check_elf) file:/usr/lib64/libc.so.6, magic:35205B31 elf:0

[  176.006182] [xattracl] (file_magic_check_elf) file:/usr/lib64/libc.so.6, magic:CDDF5D32 elf:0

[  177.045407] [xattracl] (file_magic_check_elf) file:/usr/lib64/libc.so.6, magic:4DA46A31 elf:0

[  177.045412] [xattracl] (file_magic_check_elf) file:/usr/lib64/libc.so.6, magic:354E8438 elf:0

[  177.050782] [xattracl] (file_magic_check_elf) file:/usr/lib64/libc.so.6, magic:CDDF5D32 elf:0

[  177.053388] [xattracl] (file_magic_check_elf) file:/usr/lib64/libc.so.6, magic:5DDB5E31 elf:0

[  179.175885] [xattracl] (file_magic_check_elf) file:/usr/lib64/libc.so.6, magic:4DA46A31 elf:0

[  179.175907] [xattracl] (file_magic_check_elf) file:/usr/lib64/libc.so.6, magic:354E8438 elf:0

[pairman@fedora Downloads]$ xxd /usr/lib64/libc.so.6 | head -1

00000000: 7f45 4c46 0201 0103 0000 0000 0000 0000  .ELF............



I've been searching for similar scenarios and source code of selinux and apparnor, but haven't found anything useful.

  1. Explanations for why I can't get file path directly from the file (which is (efault)), and why it's content seems random, incorrect and changes everytime, and how can I get the file's magic number correctly without calling file_open.

  2. Is there other ways to check and audit shared library loading in a proper linux-security-module way? So that I don't have to drown in this pain.


Solution

  • Hooking mmap_file() and checking if the file is an executable by using the PERM_EXEC argument solved this.

    Thanks to @Marco Bonelli's comment.