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.
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
.
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.
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.