内核模块阻止特定文件写入后文件内容被清空的问题排查求助
问题成因分析
核心原因:文件截断操作早于sys_write执行
当用户通过覆盖式写入(比如echo "content" > authorized_keys)操作目标文件时,sys_open/sys_openat系统调用会先执行O_TRUNC标志对应的截断动作——直接将文件长度置为0,之后才会调用sys_write。你只拦截了sys_write但未阻止前置的截断操作,因此文件内容会被清空。
而钩子sys_openat修改文件描述符模式的方案失效,是因为O_TRUNC的截断动作在文件打开时已经完成,后续修改模式无法回滚该操作。
次要原因:sys_write钩子的返回逻辑误导应用层
你直接返回regs->dx(应用期望写入的字节数),会让应用误以为写入成功,可能触发后续的文件指针调整、缓存刷新等操作,进一步导致文件状态异常。
排查与解决方向
1. 拦截文件打开阶段的截断标志
针对目标文件,在sys_openat钩子中清除O_TRUNC、O_CREAT等会修改文件的标志,同时保留应用层感知的“打开成功”状态:
static asmlinkage long my_openat(const struct pt_regs *regs) { const char __user *filename = (const char __user *)regs->si; int flags = (int)regs->dx; struct path path; char buf[256]; // 将用户空间文件名解析到内核空间 if (getname(filename, &path) != 0) { return orig_openat(regs); } d_path(&path, buf, sizeof(buf)); // 匹配目标文件的完整路径(避免误拦截同名文件) if (strstr(buf, "/authorized_keys") != NULL) { // 清除截断和创建标志 flags &= ~(O_TRUNC | O_CREAT); // 移除写入权限,保留只读模式 flags &= ~(O_WRONLY | O_RDWR); flags |= O_RDONLY; // 修改寄存器中的flags参数 regs->dx = flags; } path_put(&path); return orig_openat(regs); }
2. 全面拦截所有文件修改类系统调用
除了sys_write,还需要拦截sys_pwrite64、sys_writev、sys_pwritev等写入类调用,同时覆盖sys_truncate、sys_ftruncate等直接修改文件长度的系统调用,确保没有遗漏的修改路径。
3. 修正sys_write钩子的偏移量处理
直接返回期望字节数会导致应用层误以为文件指针已移动,需恢复文件偏移量以避免后续异常:
static asmlinkage long my_write(const struct pt_regs *regs){ unsigned int fd = (unsigned int)regs->di; struct task_struct* task = current; struct file* opened_file = task->files->fdt->fd[fd]; loff_t original_pos; if(opened_file){ if(strcmp(opened_file->f_path.dentry->d_name.name, "authorized_keys") == 0){ pr_info("Preventing write...\n"); // 保存并恢复原文件偏移量 original_pos = opened_file->f_pos; opened_file->f_pos = original_pos; return regs->dx; } }else{ pr_info("FD is ZERO\n"); } return orig_write(regs); }
4. 分场景验证问题
- 测试追加写入(
echo "content" >> authorized_keys):该场景不会触发O_TRUNC,如果文件未被清空,可确认问题根源来自open阶段的截断。 - 查看内核日志(
dmesg),确认open和write阶段的钩子是否被正确触发,参数修改是否生效。
内容的提问来源于stack exchange,提问作者Compi
相关产品推荐
相关产品推荐

