Код sys_execve()
в коде уровня ядра получает абсолютный или относительный путь для параметра filename
?
Может ли системный вызов sys_execve() в ядре Linux получать как абсолютные, так и относительные пути?
Ответ 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!
Вместо этого две точки проверялись отдельно (потому что .
является подкадром).