繁体   English   中英

Linux内核中的sys_execve()系统调用可以接收绝对路径还是相对路径?

[英]Can the sys_execve() system call in the Linux kernel receive both absolute or relative paths?

内核级代码中的sys_execve()应接收filename参数的绝对路径还是相对路径?

sys_execve可以采用绝对或相对路径

下面我们来验证一下:

  • 尝试原始系统调用
  • 阅读内核源码
  • 在 kernel + QEMU 上运行 GDB 来验证我们的源码分析

实验

主文件

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>

int main(void) {
    syscall(__NR_execve, "../main2.out", NULL, NULL);
}

main2.c

#include <stdio.h>

int main(void) {
    puts("hello main2");
}

编译并运行:

gcc -o main.out main.c
gcc -o ../main2.out main2.c
./main.out

输出:

hello main2

在 Ubuntu 16.10 中测试。

内核源码

首先,进入内核树

git grep '"\.\."' fs

我们关注fs是因为我们知道execve是在那里定义的。

这立即给出如下结果: https : //github.com/torvalds/linux/blob/v4.9/fs/namei.c#L1759 ,这清楚地表明他的内核知道..

/*
 * "." and ".." are special - ".." especially so because it has
 * to be able to know about the current root directory and
 * parent relationships.
 */

然后我们查看 execve https://github.com/torvalds/linux/blob/v4.9/fs/exec.c#L1869的定义,它做的第一件事就是在输入路径上调用getname()

SYSCALL_DEFINE3(execve,
        const char __user *, filename,
        const char __user *const __user *, argv,
        const char __user *const __user *, envp)
{
    return do_execve(getname(filename), argv, envp);
}

getnamefs/namei.c定义,这是上面".."引用的来源文件。

我没有费心去遵循完整的调用路径,但我敢打赌getname它最终会做..解析。

同一个文件中的follow_dotdot看起来特别有前途。

GDB + QEMU

阅读源代码很棒,但我们永远无法确定实际使用了代码路径。

有两种方法可以做到这一点:

  • printk , 重新编译, printk , 重新编译
  • GDB + QEMU。 设置有点粗糙,但一旦完成,它就是纯粹的幸福

首先按照以下说明进行设置: 如何使用 GDB 和 QEMU 调试 Linux 内核?

现在,我们将使用两个程序:

初始化程序

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>

int main(void) {
    chdir("d");
    syscall(__NR_execve, "../b.out", NULL, NULL);
}

公元前

#include <unistd.h>
#include <stdio.h>

int main(void) {
    puts("hello");
    sleep(0xFFFFFFFF);
}

rootfs文件结构应该是这样的:

init
b.out
d/

一旦 GDB 运行,我们将执行以下操作:

b sys_execve
c
x/s filename

输出../b.out ,所以我们知道这是正确的系统调用。

现在我们之前看到的有趣的".."注释是在一个名为walk_component的函数中,所以让我们看看它是否被调用:

b walk_component
c

是的,我们击中了它。

如果我们仔细阅读一下,我们会看到一个调用:

error = handle_dots(nd, nd->last_type);

这听起来很有希望并且确实:

static inline int handle_dots(struct nameidata *nd, int type)
{
    if (type == LAST_DOTDOT) {
        if (!nd->root.mnt)
            set_root(nd);
        if (nd->flags & LOOKUP_RCU) {
            return follow_dotdot_rcu(nd);
        } else
            return follow_dotdot(nd);
    }
    return 0;
}

那么是什么将这种typend->last_type )设置为LAST_DOTDOT呢?

好吧,搜索= LAST_DOTDOT的来源,我们发现link_path_walk正在这样做。

更好的是: btlink_path_walk是一个调用者,所以很容易理解现在发生了什么。

link_path_walk ,我们看到:

if (name[0] == '.') switch (hashlen_len(hash_len)) {
    case 2:
        if (name[1] == '.') {
            type = LAST_DOTDOT;

因此谜团解决了: ".."不是正在做的检查,它挫败了我们之前的greps!

相反,这两个点被分别检查(因为.是一个子案例)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM