I'm trying to intercept the getrandom
syscall and modify its results. I tried to make a minimal reproducible example, here it is: [the original codebase was written in Rust, had about 400 lines, proper error checking and suffered from the same problem]
#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;
}
The python script pi.py
follows:
#!/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)
This triggers stack smashing protection:
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]
This address area indeed exceeds the stack. Moreover, when I tried to simply replace the getrandom
glibc syscall with a custom overwriter, the error doesn't appear anywhere.
More interestingly, this is the case only if I specify the executable as /usr/bin/env python
. If I directly specify /usr/bin/python
, everything works as expected.
Moreover, if the python executable is specified via env, the arguments stored in the RDI and RSI registers get invalidated (overwritten with something). This is not the case if python
is specified directly.
If I trace the execution of directly invoked ./pi.py
with strace
, even with patched getrandom
(using LD_PRELOAD
), nothing bad happens and the output buffer is overwritten successfully. The buffer is wholly contained in the stack.
I'm running Arch Linux, Linux 4.9.36-1-lts, glibc 2.25, Python 3.6.1.
/edit: It's getting even more interesting. If I patch the libc getrandom routine using the LD_PRELOAD
trick to the following one:
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;
}
Then the unmodified ptrace
program succeeds and works as expected.
/edit2: It gets even more interesting. Adding any logging after the syscall magically fixes the problem. For example this patch: https://gist.github.com/marmistrz/2056c4baec6e910050ddade232decf09 [this was tested on top of commit 0d0a32fb91cdfea1626e6c6b77a9bc44e15a2b8a]