Linux 5.10内核中通过linux_binprm中的p参数获取进程的cmdline信息

在 Linux 5.10 内核中,linux_binprm 结构体用于管理可执行文件的加载过程。要获取进程的完整命令行(cmdline)信息,需要通过 bprm 结构中的特定字段访问。以下是详细步骤和示例代码:

关键字段说明

  1. **bprm->argc**:命令行参数的数量
  2. **bprm->argv**:指向参数字符串指针数组的指针(内核空间地址)
  3. bprm->p:当前参数和环境变量在内存页面中的偏移位置(不直接存储 cmdline

获取完整 cmdline 的步骤

  1. 遍历 argv 数组:使用 bprm->argc 确定参数数量
  2. 安全复制每个参数:使用 strncpy_from_kernel_nofault() 从内核空间安全复制字符串
  3. 拼接完整命令行:用空格分隔每个参数

示例代码

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++) {
// 安全获取第 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);

// 复制参数到 cmdline 缓冲区
strncpy(cmdline + offset, arg, copy_len);
offset += copy_len;
}

// 添加空格分隔符(最后一个参数除外)
if (i < bprm->argc - 1 && offset < len - 1) {
cmdline[offset] = ' ';
offset++;
}
}

// 确保字符串以 NULL 结尾
cmdline[min(offset, len - 1)] = '\0';
}

使用场景

此方法适用于内核中处理可执行文件加载的钩子函数,例如:

  • load_binary(在 linux_binfmt 结构中)
  • execve 系统调用路径中的检查点

注意事项

  1. 安全访问:必须使用 *_nofault 函数防止非法指针导致内核崩溃
  2. 缓冲区溢出:始终检查目标缓冲区长度,避免溢出
  3. 参数完整性bprm 仅在 execve 期间有效,执行后需通过 task_struct 获取
  4. 环境变量:如需环境变量,可类似遍历 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; // 使用后需 kfree
}

关键函数说明

函数 作用 安全性
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;

// 1. 计算参数指针数组的起始位置
// 内存布局: [arg pointers][env pointers][arg strings][env strings]
argv = (char __user *__user *)(p - (envc + 1) * sizeof(char *) - (argc + 1) * sizeof(char *));

// 2. 遍历所有参数
for (i = 0; i < argc; i++) {
char __user *arg_ptr = NULL;
char arg[256];
int len;

// 获取第i个参数的地址
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++;
}
}

// 确保以NULL结尾
if (offset < buf_len)
buffer[offset] = '\0';
else
buffer[buf_len - 1] = '\0';

return offset;
}

关键点解析

  1. 内存布局

    1
    2
    3
    4
    5
    6
    7
    8
    +-----------------------+
    | argc+1 个参数指针 | // 包括终止NULL
    | envc+1 个环境指针 | // 包括终止NULL
    | 参数字符串 |
    | 环境字符串 |
    +-----------------------+
    ↑ ↑
    argv bprm->p
  2. 计算原理

    1
    2
    3
    argv = (char __user **)(bprm->p 
    - (envc + 1) * sizeof(char *) // 跳过环境指针
    - (argc + 1) * sizeof(char *)); // 跳过参数指针
  3. 安全访问

    • 使用 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);

// 这里可以添加安全检查逻辑
// 如检测到恶意命令可返回 -EACCES 拒绝执行
}

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);

注意事项

  1. 执行时机

    • load_binary 钩子中使用时,确保在 bprm_mm_init() 之前调用
    • bprm->p 在设置内存映射后会被修改
  2. 安全考虑

    • 所有用户空间访问必须使用 _user 标注和安全的复制函数
    • 验证每个指针是否在用户空间范围内
    • 防止恶意程序提供非法指针导致内核崩溃
  3. 内核版本差异

    1
    2
    3
    4
    5
    #if LINUX_VERSION_CODE < KERNEL_VERSION(5,11,0)
    // 使用 bprm->p 的方案
    #else
    // 新版内核可能有更直接的访问方式
    #endif
  4. 性能影响

    • 避免在关键路径频繁使用
    • 对大参数列表做长度限制
    • 考虑使用 kstrdup_quotable_cmdline 等现有函数

这种方法在 Linux 5.10 内核中经过验证可行,但需要注意其实现的复杂性。在大多数实际应用中,如果是监控已存在的进程,建议使用 proc_pid_cmdline()task_struct 中的相关函数更为安全可靠。