Linux安全模块:共享库加载校验与路径、魔数读取异常排查
问题解答
1. 路径获取失败与魔数读取错误的原因及解决方法
1.1 路径显示(efault)的原因
- 文件对象在
file_open钩子触发时可能未完成完整初始化,f_path的路径解析依赖VFS的挂载点、符号链接遍历,此时直接读取可能无法得到有效路径。 - 直接使用
dentry->d_name.name仅能获取文件名,无法得到完整路径;自定义的xattracl_get_realpath若未正确处理内核路径解析的资源管理(如path_get()/path_put()),也会导致路径获取失败。
1.2 魔数读取随机错误的原因
- 代码存在语法错误:
kernel_read(file,value,4,0)==-1);多了一个右括号,且未检查实际读取的字节数是否为4,读取失败时仍会使用未初始化的value,导致垃圾数据。 file_open触发时,文件的文件指针位置未初始化,kernel_read使用偏移0不一定能正确读取部分文件系统或特殊打开模式下的文件。- 未检查文件的可读权限:
struct file的f_mode可能没有FMODE_READ,直接读取会返回无效数据。 - 调用
filp_open会递归触发自身的file_open钩子,导致栈溢出panic。
1.3 正确获取魔数的方法
无需重新打开文件,直接通过inode读取文件第一个块的内容(内核态安全操作):
#include <linux/fs.h> #include <linux/buffer_head.h> int xattracl_file_magic_check_elf(struct file *file) { struct inode *inode = file_inode(file); struct buffer_head *bh; int iself = 0; // 仅处理普通文件 if (!S_ISREG(inode->i_mode)) { return 0; } // 读取文件第一个磁盘块 bh = sb_bread(inode->i_sb, 0); if (!bh) { return -ENOENT; } // 校验ELF魔数 const char *magic = bh->b_data; iself = (magic[0] == 0x7F) && (magic[1] == 'E') && (magic[2] == 'L') && (magic[3] == 'F'); printk(KERN_INFO "[xattracl] magic: %02X%02X%02X%02X iself: %d\n", (unsigned char)magic[0], (unsigned char)magic[1], (unsigned char)magic[2], (unsigned char)magic[3], iself); brelse(bh); return iself; }
路径获取使用内核标准d_path()函数,注意缓冲区的正确管理:
char *xattracl_get_realpath(struct path *path) { char *buf = (char *)__get_free_page(GFP_NOFS); if (!buf) { return NULL; } // 获取路径,返回值指向buf中的有效路径 char *realpath = d_path(path, buf, PAGE_SIZE); if (IS_ERR(realpath)) { free_page((unsigned long)buf); return NULL; } // 复制到可长期保存的kmalloc缓冲区 char *ret = kstrdup(realpath, GFP_NOFS); free_page((unsigned long)buf); return ret; }
2. 合规的LSM共享库加载检查方式
不要使用file_open钩子,该钩子范围过大且易触发递归问题,推荐使用官方设计的精准钩子:
2.1 使用mmap_file钩子
共享库加载最终会通过mmap映射到进程内存,mmap_file钩子专门用于检查文件映射操作,可过滤出带执行权限的映射(共享库加载的特征):
static int xattracl_mmap_file(struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags) { // 仅拦截带执行权限的映射 if (!(prot & PROT_EXEC)) { return 0; } // 检查是否为ELF文件 int iself = xattracl_file_magic_check_elf(file); if (!iself) { return 0; } // 检查扩展属性 struct inode *inode = file_inode(file); char value[XATTRACL_XATTR_VALUE_SIZE]; int size = __vfs_getxattr(file->f_path.dentry, inode, XATTRACL_XATTR_NAME, value, sizeof(value)); // 无对应xattr或值不匹配则拒绝加载 if (size < 0 || strncmp(value, XATTRACL_XATTR_ACTION_ALLOW, size) != 0) { char buf[PAGE_SIZE]; printk(KERN_INFO "[xattracl] Deny loading: %s\n", d_path(&file->f_path, buf, sizeof(buf))); return -EPERM; } return 0; } // 注册钩子 static struct security_hook_list xattracl_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(mmap_file, xattracl_mmap_file), // 其他需要的钩子... };
2.2 使用bprm_check_security钩子
该钩子在进程执行二进制文件(包括共享库依赖加载)前触发,适合检查可执行文件的合法性:
static int xattracl_bprm_check_security(struct linux_binprm *bprm) { struct file *file = bprm->file; int iself = xattracl_file_magic_check_elf(file); if (!iself) { return 0; } // 检查xattr逻辑同上 struct inode *inode = file_inode(file); char value[XATTRACL_XATTR_VALUE_SIZE]; int size = __vfs_getxattr(file->f_path.dentry, inode, XATTRACL_XATTR_NAME, value, sizeof(value)); if (size < 0 || strncmp(value, XATTRACL_XATTR_ACTION_ALLOW, size) != 0) { char buf[PAGE_SIZE]; printk(KERN_INFO "[xattracl] Deny execution: %s\n", d_path(&file->f_path, buf, sizeof(buf))); return -EPERM; } return 0; } // 注册钩子 LSM_HOOK_INIT(bprm_check_security, xattracl_bprm_check_security),
2.3 方案优势
- 精准拦截:仅针对可执行文件/共享库的加载场景,避免无差别拦截普通文件打开。
- 无递归风险:直接使用传入的内核对象操作,无需重新打开文件,不会触发自身钩子递归。
- 符合LSM规范:这两个钩子是官方推荐的可执行文件安全检查入口,SELinux、AppArmor均使用类似逻辑实现权限管控。
内容的提问来源于stack exchange,提问作者Pairman
相关产品推荐
相关产品推荐

