Может ли системный вызов sys_execve() в ядре Linux получать как абсолютные, так и относительные пути?

Код sys_execve() в коде уровня ядра получает абсолютный или относительный путь для параметра filename?

Ответ 1

sys_execve может принимать абсолютные или относительные пути

Пусть это проверит следующим образом:

  • эксперимент с необработанным системным вызовом
  • прочитать исходный код ядра
  • запустите GDB на ядре + QEMU, чтобы проверить наш исходный анализ.

Эксперимент

a.c

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

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

b.c

#include <stdio.h>

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

Тогда:

gcc a.c -o a.out
gcc b.c -o ../b.out
./a.out

Вывод:

hello

Протестировано в 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);
}

getname определяется в fs/namei.c, который является файлом, из которого вышла цитата из "..".

Я не потрудился следовать по полному пути вызова, но я уверен, что getname он заканчивает выполнение разрешения ...

follow_dotdot в том же файле выглядит особенно перспективным.

GDB + QEMU

Чтение источника велико, но мы никогда не можем быть уверены, что пути кода действительно используются.

Есть два способа сделать это:

  • printk, перекомпилируйте, printk, перекомпилируйте
  • GDB + QEMU. Настройка немного грубее, но как только это сделано, это чистое блаженство.

Сначала выполните настройку, как описано в разделе Как отладить ядро ​​Linux с помощью GDB и QEMU?

Теперь мы будем использовать две программы:

init.c

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

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

b.c

#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;
}

Итак, что это заставляет этот type (nd->last_type) на LAST_DOTDOT?

Ну, ищите источник для = LAST_DOTDOT, и мы обнаруживаем, что link_path_walk делает это.

И еще лучше: bt говорит, что link_path_walk является вызывающим, поэтому будет легко понять, что происходит сейчас.

В link_path_walk мы видим:

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

и, следовательно, mistery решается: ".." не был проверкой, которая была прервана нашими предыдущими greps!

Вместо этого две точки проверялись отдельно (потому что . является подкадром).