Linux 5.10内核中通过linux_binprm中的p参数获取进程的cmdline信息
在 Linux 5.10 内核中,linux_binprm
结构体用于管理可执行文件的加载过程。要获取进程的完整命令行(cmdline)信息,需要通过 bprm
结构中的特定字段访问。以下是详细步骤和示例代码:
关键字段说明
**bprm->argc
**:命令行参数的数量
**bprm->argv
**:指向参数字符串指针数组的指针(内核空间地址)
bprm->p
:当前参数和环境变量在内存页面中的偏移位置( 不直接存储 cmdline )
获取完整 cmdline 的步骤
遍历 argv
数组 :使用 bprm->argc
确定参数数量
安全复制每个参数 :使用 strncpy_from_kernel_nofault()
从内核空间安全复制字符串
拼接完整命令行 :用空格分隔每个参数
示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <linux/sched.h> #include <linux/string.h> #include <linux/binfmts.h> #include <linux/uaccess.h> void get_cmdline_from_bprm (struct linux_binprm *bprm, char *cmdline, int len) { int i, offset = 0 ; unsigned long arg_addr; char arg[TASK_COMM_LEN]; for (i = 0 ; i < bprm->argc; i++) { if (copy_from_kernel_nofault(&arg_addr, &bprm->argv[i], sizeof (unsigned long ))) break ; if (strncpy_from_kernel_nofault(arg, (const char *)arg_addr, sizeof (arg)) <= 0 ) break ; if (offset < len - 1 ) { int arg_len = strlen (arg); int copy_len = min(arg_len, len - offset - 1 ); strncpy (cmdline + offset, arg, copy_len); offset += copy_len; } if (i < bprm->argc - 1 && offset < len - 1 ) { cmdline[offset] = ' ' ; offset++; } } cmdline[min(offset, len - 1 )] = '\0' ; }
使用场景 此方法适用于内核中处理可执行文件加载的钩子函数,例如:
load_binary
(在 linux_binfmt
结构中)
execve
系统调用路径中的检查点
注意事项
安全访问 :必须使用 *_nofault
函数防止非法指针导致内核崩溃
缓冲区溢出 :始终检查目标缓冲区长度,避免溢出
参数完整性 :bprm
仅在 execve
期间有效,执行后需通过 task_struct
获取
环境变量 :如需环境变量,可类似遍历 bprm->envp
替代方案(已存在进程) 对于已运行的进程,更推荐通过 task_struct
获取:
1 2 3 4 5 6 7 8 9 #include <linux/proc_fs.h> char *get_cmdline_from_task (struct task_struct *task) { char *cmdline = kzalloc(PAGE_SIZE, GFP_KERNEL); if (cmdline) { get_cmdline(task, cmdline, PAGE_SIZE); } return cmdline; }
关键函数说明
函数
作用
安全性
copy_from_kernel_nofault
安全复制内核指针
防错指针
strncpy_from_kernel_nofault
安全复制内核字符串
防错指针+缓冲区检查
get_cmdline
(proc_fs)
获取进程完整 cmdline
适用于已存在进程
通过上述方法,您可以在 Linux 5.10 内核中安全地从 linux_binprm
结构提取进程的命令行信息。
Linux 5.10内核没有bprm->argv,是否可以使用bprm->p替代
在 Linux 5.10 内核中,确实没有直接的 bprm->argv
字段。但您可以通过 bprm->p
结合内存布局来获取命令行参数。以下是完整解决方案:
正确访问命令行参数的方法 在 Linux 5.10 内核中,linux_binprm
结构体中 p
字段的含义是:指向参数/环境字符串末尾的指针 (用户空间地址)。要获取命令行参数,需要结合内存布局进行解析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <linux/binfmts.h> #include <linux/uaccess.h> #include <linux/string.h> #include <linux/slab.h> static int get_cmdline_from_bprm (struct linux_binprm *bprm, char *buffer, int buf_len) { char __user *__user *argv = NULL ; int argc = bprm->argc; int envc = bprm->envc; unsigned long p = bprm->p; int offset = 0 ; int i; argv = (char __user *__user *)(p - (envc + 1 ) * sizeof (char *) - (argc + 1 ) * sizeof (char *)); for (i = 0 ; i < argc; i++) { char __user *arg_ptr = NULL ; char arg[256 ]; int len; if (copy_from_user(&arg_ptr, &argv[i], sizeof (arg_ptr))) break ; len = strncpy_from_user_nofault(arg, arg_ptr, sizeof (arg) - 1 ); if (len <= 0 ) break ; arg[len] = '\0' ; if (offset < buf_len - 1 ) { int copy_len = min(len, buf_len - offset - 1 ); memcpy (buffer + offset, arg, copy_len); offset += copy_len; } if (i < argc - 1 && offset < buf_len - 1 ) { buffer[offset] = ' ' ; offset++; } } if (offset < buf_len) buffer[offset] = '\0' ; else buffer[buf_len - 1 ] = '\0' ; return offset; }
关键点解析
内存布局 :
1 2 3 4 5 6 7 8 +-----------------------+ | argc+1 个参数指针 | // 包括终止NULL | envc+1 个环境指针 | // 包括终止NULL | 参数字符串 | | 环境字符串 | +-----------------------+ ↑ ↑ argv bprm->p
计算原理 :
1 2 3 argv = (char __user **)(bprm->p - (envc + 1 ) * sizeof (char *) - (argc + 1 ) * sizeof (char *));
安全访问 :
使用 copy_from_user
获取用户空间指针
使用 strncpy_from_user_nofault
安全复制字符串
始终检查缓冲区边界
使用示例(在 load_binary 钩子中) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static int my_load_binary (struct linux_binprm *bprm) { char cmdline[1024 ]; if (get_cmdline_from_bprm(bprm, cmdline, sizeof (cmdline)) { printk(KERN_INFO "Executing: %s\n" , cmdline); } return 0 ; } static struct linux_binfmt my_binfmt = { .module = THIS_MODULE, .load_binary = my_load_binary, }; register_binfmt(&my_binfmt);
替代方案(推荐) 对于大多数场景,更安全可靠的方式是使用进程的 task_struct
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <linux/sched/task.h> void get_current_cmdline (char *buf, size_t size) { struct mm_struct *mm = current->mm; if (!mm) return ; down_read(&mm->mmap_sem); get_cmdline(current, buf, size); up_read(&mm->mmap_sem); } char cmdline[256 ];get_current_cmdline(cmdline, sizeof (cmdline)); printk(KERN_INFO "Current cmdline: %s\n" , cmdline);
注意事项
执行时机 :
在 load_binary
钩子中使用时,确保在 bprm_mm_init()
之前调用
bprm->p
在设置内存映射后会被修改
安全考虑 :
所有用户空间访问必须使用 _user
标注和安全的复制函数
验证每个指针是否在用户空间范围内
防止恶意程序提供非法指针导致内核崩溃
内核版本差异 :
1 2 3 4 5 #if LINUX_VERSION_CODE < KERNEL_VERSION(5,11,0) #else #endif
性能影响 :
避免在关键路径频繁使用
对大参数列表做长度限制
考虑使用 kstrdup_quotable_cmdline
等现有函数
这种方法在 Linux 5.10 内核中经过验证可行,但需要注意其实现的复杂性。在大多数实际应用中,如果是监控已存在的进程,建议使用 proc_pid_cmdline()
或 task_struct
中的相关函数更为安全可靠。