Linux 内核如何知道它应该从系统调用路径参数中读取多少字节?

How does the Linux kernel know how many bytes it should read from system call path arguments?

我在 google 中搜索,我发现 Linux 内核使用变量结构。

#define EMBEDDED_LEVELS 2
struct nameidata {
    struct path path;
    struct qstr last;
    struct path root;
    struct inode    *inode; /* path.dentry.d_inode */
    unsigned int    flags;
    unsigned    seq, m_seq;
    int     last_type;
    unsigned    depth;
    int     total_link_count;
    struct saved {
        struct path link;
        struct delayed_call done;
        const char *name;
        unsigned seq;
    } *stack, internal[EMBEDDED_LEVELS];
    struct filename *name;
    struct nameidata *saved;
    struct inode    *link_inode;
    unsigned    root_seq;
    int     dfd;
} __randomize_layout;

例如 execve 系统调用(在此处 https://elixir.bootlin.com/linux/latest/source/fs/exec.c 找到)
此函数会将文件名指针作为 pathName 传递给另一个函数,并将 nameidata 结构名称设置为此 pathName

static int __do_execve_file(int fd, struct filename *filename,
                struct user_arg_ptr argv,
                struct user_arg_ptr envp,
                int flags, struct file *file)

我的问题是它如何计算从堆栈传递给此函数的参数的长度(例如 "/bin/sh")?

(编者注:execve(2)const char *pathname arg 没有 指向堆栈内存。我认为这个问题是假设 shellcode您确实在 user-space 堆栈上构建路径并传递指向该路径的用例。)

(我正在学习汇编并且我被困在系统调用的参数传递部分)

Linux 使用零终止字符串,这是 C 的标准字符串格式。字符串的结尾用零字节标记,字符串中第一个零字节之后的任何字节都不是字符串的一部分字符串。值得注意的是,这意味着文件名中不能有零字节。 (出于同样的原因,大多数 shellcode 不能有零字节,因为它们旨在利用某种字符串缓冲区溢出。)

在实践中,内核通常不需要知道文件名的长度,而是使用像 strcmp 这样的函数逐字节比较字符串,在比较不同的第一个字节或在第一个字节停止遇到第一个零字节。但是,如果需要,可以使用 strlen.

等函数计算字符串的长度

终于找到答案了
这是我找到的源代码

#include <stdio.h>

char hello[] = "HelloA[=10=]AAABB";

int main( void )
{
   __asm__
   (
    "mov $hello , %eax;"
    "push %eax;"
    "call myprint;"
   );
}

void myprint(char input[])
{
    printf("%s" , input);
}

我发现'\0'是字符串terminator.so上面的代码只会输出"HelloA"。其余的将被忽略。

我发现的另一个有趣的事情是,当你在 C 中创建一个 char 数组时,编译器会在它的末尾添加一个字节作为空终止符。

所以如果我们创建这样的东西

char hello[] = "Hello"

实际上是这样编译的:

char hello[] = "Hello[=12=]"

你的 hello 的大小将是 6 而不是 5。

最后,在汇编编程中,我们必须考虑在系统调用中传递参数的空终止符。只要 linux 内核是用 C 编程语言编写的,我们就必须接受 C 编程语言的规则。


这是 char hello[] 的 Gdb 结果

0x8049597 <hello>:      0x48    0x65    0x6c    0x6c    0x6f    0x41    0x00    0x41
0x804959f <hello+8>:    0x41    0x41    0x42    0x42    0x00    0x77    0x6f    0x72

0x8049597 是我们字符串的起始地址("HelloA[=43=]AAABB")。
我们将 \0 放在 A 字符之后。字符 'A' 等于 Ascii table 中的 0x41 十六进制数。 \0 是 0x00.
这就是为什么 printf 函数只会显示字符串的前 6 个字符。