Если исполняемый файл python указан через `env`, ptrace разбивает стек
Я пытаюсь перехватить getrandom
syscall и изменить его результаты. Я попытался сделать минимальный воспроизводимый пример, вот он: [исходная кодовая база была написана на Rust, имела около 400 строк, надлежащую проверку ошибок и страдала от той же проблемы]
#include <stdio.h>
#include <stdlib.h>
#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>
void wait_sigtrap() {
int status;
wait(&status);
if (WIFEXITED(status)) exit(0);
}
int main() {
pid_t child = fork();
if (child == 0) {
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
const char* file = "./pi.py";
if (execl(file, file, "1000", NULL) < 0) {
perror("execl");
return 1;
};
} else {
pid_t pid = child;
wait_sigtrap(); // there will be an initial stop after traceme, ignore
// it
ptrace(PTRACE_SYSCALL, pid, 0, 0); // wait for another
for (;;) {
// detect enter, get syscall no
wait_sigtrap();
long no = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, 0);
if (no != SYS_getrandom) {
ptrace(PTRACE_SYSCALL, pid, 0, 0); // wait for another
wait_sigtrap(); // wait for exit
} else {
// getrandom
long bufptr = ptrace(PTRACE_PEEKUSER, pid, 8 * RDI, 0);
long buflen = ptrace(PTRACE_PEEKUSER, pid, 8 * RSI, 0);
printf("getrandom request: 0x%016lx 0x%016lx\n", bufptr,
buflen);
fflush(stdout);
ptrace(PTRACE_SYSCALL, pid, 0, 0); // wait for another
wait_sigtrap(); // wait for exit
long ret = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, 0);
if (ret < 0) {
printf("Syscall %ld exited with an error, not touching it",
no);
fflush(stdout);
} else {
long ind = 0;
while (ind < buflen) {
ptrace(PTRACE_POKEDATA, pid, bufptr + ind, 0);
ind += sizeof(long);
}
}
}
ptrace(PTRACE_SYSCALL, pid, 0, 0); // wait for another
}
}
return 0;
}
Скрипт Python pi.py
следующим образом:
#!/usr/bin/env python
from math import sqrt
from sys import argv, exit
from os import getpid, system
from random import random
argc = len(argv)
if argc != 2:
print("Usage: ./pi.py num_steps")
exit(1)
inside = 0
n = int(argv[1])
for i in range(0, n):
x = random()
y = random()
if sqrt(x*x+y*y) <= 1:
inside += 1
pi = 4*inside/n
print(pi)
Это запускает защиту от разрушения стека:
getrandom request: 0x00007fee772e29a0 0x0000000000000018
getrandom request: 0x00007ffc1788eb90 0x00000000000009c0
getrandom request: 0x00007ffc1788e420 0x00000000000009c0
*** stack smashing detected ***: python terminated
======= Backtrace: =========
/usr/lib/libc.so.6(+0x7254c)[0x7fee7735554c]
/usr/lib/libc.so.6(__fortify_fail+0x37)[0x7fee773e1307]
/usr/lib/libc.so.6(__fortify_fail+0x0)[0x7fee773e12d0]
/usr/lib/python3.6/lib-dynload/_random.cpython-36m-x86_64-linux-gnu.so(+0x1285)[0x7fee75169285]
======= Memory map: ========
00400000-00401000 r-xp 00000000 fe:00 22965216 /usr/bin/python3.6
00601000-00602000 r--p 00001000 fe:00 22965216 /usr/bin/python3.6
00602000-00603000 rw-p 00002000 fe:00 22965216 /usr/bin/python3.6
0082e000-00924000 rw-p 00000000 00:00 0 [heap]
7fee74f2e000-7fee74f44000 r-xp 00000000 fe:00 22941269 /usr/lib/libgcc_s.so.1
7fee74f44000-7fee75143000 ---p 00016000 fe:00 22941269 /usr/lib/libgcc_s.so.1
7fee75143000-7fee75144000 r--p 00015000 fe:00 22941269 /usr/lib/libgcc_s.so.1
7fee75144000-7fee75145000 rw-p 00016000 fe:00 22941269 /usr/lib/libgcc_s.so.1
7fee75168000-7fee7516c000 r-xp 00000000 fe:00 23600949 /usr/lib/python3.6/lib-dynload/_random.cpython-36m-x86_64-linux-gnu.so
7fee7516c000-7fee7536b000 ---p 00004000 fe:00 23600949 /usr/lib/python3.6/lib-dynload/_random.cpython-36m-x86_64-linux-gnu.so
7fee7536b000-7fee7536c000 r--p 00003000 fe:00 23600949 /usr/lib/python3.6/lib-dynload/_random.cpython-36m-x86_64-linux-gnu.so
7fee7536c000-7fee7536d000 rw-p 00004000 fe:00 23600949 /usr/lib/python3.6/lib-dynload/_random.cpython-36m-x86_64-linux-gnu.so
7fee7536d000-7fee7536f000 r-xp 00000000 fe:00 23600965 /usr/lib/python3.6/lib-dynload/_bisect.cpython-36m-x86_64-linux-gnu.so
7fee7536f000-7fee7556e000 ---p 00002000 fe:00 23600965 /usr/lib/python3.6/lib-dynload/_bisect.cpython-36m-x86_64-linux-gnu.so
7fee7556e000-7fee7556f000 r--p 00001000 fe:00 23600965 /usr/lib/python3.6/lib-dynload/_bisect.cpython-36m-x86_64-linux-gnu.so
7fee7556f000-7fee75570000 rw-p 00002000 fe:00 23600965 /usr/lib/python3.6/lib-dynload/_bisect.cpython-36m-x86_64-linux-gnu.so
7fee75570000-7fee75586000 r-xp 00000000 fe:00 23600924 /usr/lib/python3.6/lib-dynload/_sha3.cpython-36m-x86_64-linux-gnu.so
7fee75586000-7fee75785000 ---p 00016000 fe:00 23600924 /usr/lib/python3.6/lib-dynload/_sha3.cpython-36m-x86_64-linux-gnu.so
7fee75785000-7fee75786000 r--p 00015000 fe:00 23600924 /usr/lib/python3.6/lib-dynload/_sha3.cpython-36m-x86_64-linux-gnu.so
7fee75786000-7fee75788000 rw-p 00016000 fe:00 23600924 /usr/lib/python3.6/lib-dynload/_sha3.cpython-36m-x86_64-linux-gnu.so
7fee75788000-7fee75796000 r-xp 00000000 fe:00 23600925 /usr/lib/python3.6/lib-dynload/_blake2.cpython-36m-x86_64-linux-gnu.so
7fee75796000-7fee75995000 ---p 0000e000 fe:00 23600925 /usr/lib/python3.6/lib-dynload/_blake2.cpython-36m-x86_64-linux-gnu.so
7fee75995000-7fee75996000 r--p 0000d000 fe:00 23600925 /usr/lib/python3.6/lib-dynload/_blake2.cpython-36m-x86_64-linux-gnu.so
7fee75996000-7fee75997000 rw-p 0000e000 fe:00 23600925 /usr/lib/python3.6/lib-dynload/_blake2.cpython-36m-x86_64-linux-gnu.so
7fee75997000-7fee75be8000 r-xp 00000000 fe:00 22946259 /usr/lib/libcrypto.so.1.1
7fee75be8000-7fee75de7000 ---p 00251000 fe:00 22946259 /usr/lib/libcrypto.so.1.1
7fee75de7000-7fee75e05000 r--p 00250000 fe:00 22946259 /usr/lib/libcrypto.so.1.1
7fee75e05000-7fee75e0f000 rw-p 0026e000 fe:00 22946259 /usr/lib/libcrypto.so.1.1
7fee75e0f000-7fee75e12000 rw-p 00000000 00:00 0
7fee75e12000-7fee75e18000 r-xp 00000000 fe:00 23600946 /usr/lib/python3.6/lib-dynload/_hashlib.cpython-36m-x86_64-linux-gnu.so
7fee75e18000-7fee76017000 ---p 00006000 fe:00 23600946 /usr/lib/python3.6/lib-dynload/_hashlib.cpython-36m-x86_64-linux-gnu.so
7fee76017000-7fee76018000 r--p 00005000 fe:00 23600946 /usr/lib/python3.6/lib-dynload/_hashlib.cpython-36m-x86_64-linux-gnu.so
7fee76018000-7fee76019000 rw-p 00006000 fe:00 23600946 /usr/lib/python3.6/lib-dynload/_hashlib.cpython-36m-x86_64-linux-gnu.so
7fee76019000-7fee76023000 r-xp 00000000 fe:00 23600935 /usr/lib/python3.6/lib-dynload/math.cpython-36m-x86_64-linux-gnu.so
7fee76023000-7fee76222000 ---p 0000a000 fe:00 23600935 /usr/lib/python3.6/lib-dynload/math.cpython-36m-x86_64-linux-gnu.so
7fee76222000-7fee76223000 r--p 00009000 fe:00 23600935 /usr/lib/python3.6/lib-dynload/math.cpython-36m-x86_64-linux-gnu.so
7fee76223000-7fee76225000 rw-p 0000a000 fe:00 23600935 /usr/lib/python3.6/lib-dynload/math.cpython-36m-x86_64-linux-gnu.so
7fee76225000-7fee76265000 rw-p 00000000 00:00 0
7fee76265000-7fee76268000 r-xp 00000000 fe:00 23600948 /usr/lib/python3.6/lib-dynload/_heapq.cpython-36m-x86_64-linux-gnu.so
7fee76268000-7fee76467000 ---p 00003000 fe:00 23600948 /usr/lib/python3.6/lib-dynload/_heapq.cpython-36m-x86_64-linux-gnu.so
7fee76467000-7fee76468000 r--p 00002000 fe:00 23600948 /usr/lib/python3.6/lib-dynload/_heapq.cpython-36m-x86_64-linux-gnu.so
7fee76468000-7fee7646a000 rw-p 00003000 fe:00 23600948 /usr/lib/python3.6/lib-dynload/_heapq.cpython-36m-x86_64-linux-gnu.so
7fee7646a000-7fee7666a000 rw-p 00000000 00:00 0
7fee7666a000-7fee7677a000 r-xp 00000000 fe:00 22939716 /usr/lib/libm-2.25.so
7fee7677a000-7fee7697a000 ---p 00110000 fe:00 22939716 /usr/lib/libm-2.25.so
7fee7697a000-7fee7697b000 r--p 00110000 fe:00 22939716 /usr/lib/libm-2.25.so
7fee7697b000-7fee7697c000 rw-p 00111000 fe:00 22939716 /usr/lib/libm-2.25.so
7fee7697c000-7fee7697e000 r-xp 00000000 fe:00 22939712 /usr/lib/libutil-2.25.so
7fee7697e000-7fee76b7d000 ---p 00002000 fe:00 22939712 /usr/lib/libutil-2.25.so
7fee76b7d000-7fee76b7e000 r--p 00001000 fe:00 22939712 /usr/lib/libutil-2.25.so
7fee76b7e000-7fee76b7f000 rw-p 00002000 fe:00 22939712 /usr/lib/libutil-2.25.so
7fee76b7f000-7fee76b82000 r-xp 00000000 fe:00 22939717 /usr/lib/libdl-2.25.so
7fee76b82000-7fee76d81000 ---p 00003000 fe:00 22939717 /usr/lib/libdl-2.25.so
7fee76d81000-7fee76d82000 r--p 00002000 fe:00 22939717 /usr/lib/libdl-2.25.so
7fee76d82000-7fee76d83000 rw-p 00003000 fe:00 22939717 /usr/lib/libdl-2.25.so
7fee76d83000-7fee7704a000 r-xp 00000000 fe:00 22965217 /usr/lib/libpython3.6m.so.1.0
7fee7704a000-7fee77249000 ---p 002c7000 fe:00 22965217 /usr/lib/libpython3.6m.so.1.0
7fee77249000-7fee7724c000 r--p 002c6000 fe:00 22965217 /usr/lib/libpython3.6m.so.1.0
7fee7724c000-7fee772b2000 rw-p 002c9000 fe:00 22965217 /usr/lib/libpython3.6m.so.1.0
7fee772b2000-7fee772e3000 rw-p 00000000 00:00 0
7fee772e3000-7fee7747f000 r-xp 00000000 fe:00 22939771 /usr/lib/libc-2.25.so
7fee7747f000-7fee7767e000 ---p 0019c000 fe:00 22939771 /usr/lib/libc-2.25.so
7fee7767e000-7fee77682000 r--p 0019b000 fe:00 22939771 /usr/lib/libc-2.25.so
7fee77682000-7fee77684000 rw-p 0019f000 fe:00 22939771 /usr/lib/libc-2.25.so
7fee77684000-7fee77688000 rw-p 00000000 00:00 0
7fee77688000-7fee776a1000 r-xp 00000000 fe:00 22939790 /usr/lib/libpthread-2.25.so
7fee776a1000-7fee778a0000 ---p 00019000 fe:00 22939790 /usr/lib/libpthread-2.25.so
7fee778a0000-7fee778a1000 r--p 00018000 fe:00 22939790 /usr/lib/libpthread-2.25.so
7fee778a1000-7fee778a2000 rw-p 00019000 fe:00 22939790 /usr/lib/libpthread-2.25.so
7fee778a2000-7fee778a6000 rw-p 00000000 00:00 0
7fee778a6000-7fee778c9000 r-xp 00000000 fe:00 22939772 /usr/lib/ld-2.25.so
7fee778ca000-7fee778ef000 rw-p 00000000 00:00 0
7fee778ef000-7fee77aa2000 r--p 00000000 fe:00 22961001 /usr/lib/locale/locale-archive
7fee77aa2000-7fee77aa6000 rw-p 00000000 00:00 0
7fee77ac8000-7fee77ac9000 rw-p 00000000 00:00 0
7fee77ac9000-7fee77aca000 r--p 00023000 fe:00 22939772 /usr/lib/ld-2.25.so
7fee77aca000-7fee77acb000 rw-p 00024000 fe:00 22939772 /usr/lib/ld-2.25.so
7fee77acb000-7fee77acc000 rw-p 00000000 00:00 0
7ffc17872000-7ffc17893000 rw-p 00000000 00:00 0 [stack]
7ffc17917000-7ffc17919000 r--p 00000000 00:00 0 [vvar]
7ffc17919000-7ffc1791b000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Эта область адресов действительно превышает стек. Более того, когда я пытался просто заменить getrandom
glibc syscall с пользовательской перезаписью, ошибка нигде не появляется.
Что еще интереснее, это так, только если я указываю исполняемый файл как /usr/bin/env python
, Если я прямо укажу /usr/bin/python
все работает как положено.
Более того, если исполняемый файл python указан через env, аргументы, хранящиеся в регистрах RDI и RSI, становятся недействительными (перезаписываются чем-то). Это не тот случай, если python
указывается напрямую.
Если я отслеживаю выполнение непосредственно вызванного ./pi.py
с strace
даже с залатанными getrandom
(с помощью LD_PRELOAD
), ничего плохого не происходит и выходной буфер успешно перезаписывается. Буфер целиком содержится в стеке.
Я использую Arch Linux, Linux 4.9.36-1-lts, glibc 2.25, Python 3.6.1.
/ edit: Становится еще интереснее. Если я исправлю подпрограмму getcndom libc, используя LD_PRELOAD
Хитрость в следующем:
int getrandom(void *buf, size_t buflen, unsigned int flags)
{
(void) flags;
syscall(318, buf, buflen, flags);
printf("Using our getrandom\n");
uint8_t* b = buf;
for (int i = 0; i < buflen; ++i) {
b[i] = 0;
}
return buflen;
}
Тогда немодифицированный ptrace
Программа успешно работает и работает как положено.
/ edit2: становится еще интереснее. Добавление любого журнала после системного вызова волшебным образом решает проблему. Например, этот патч: https://gist.github.com/marmistrz/2056c4baec6e910050ddade232decf09[это было проверено поверх коммита 0d0a32fb91cdfea1626e6c6b77a9bc44e15a2b8a]