1
0

proj: Restructure

This commit is contained in:
2025-03-04 14:49:15 +01:00
parent d85f506a8d
commit de790eabf9
10 changed files with 291 additions and 207 deletions

2
proj/intercept/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/main
/main_*

28
proj/intercept/Makefile Normal file
View File

@@ -0,0 +1,28 @@
CC=gcc
CFLAGS=-std=c99 -pedantic -Wall -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_SVID_SOURCE -D_POSIX_C_SOURCE=200809L -g
.PHONY: all clean
all: default
default: bin intercept.so main_intercept
bin:
mkdir -p bin/
bin/main.o: ../test1/src/main.c
$(CC) -c -o $@ $^ $(CFLAGS)
bin/intercept_preload.o: src/intercept.c
$(CC) -fPIC -c -o $@ $^ $(CFLAGS) -DINTERCEPT_PRELOAD
intercept.so: bin/intercept_preload.o
$(CC) -shared -o $@ $^ $(CFLAGS) -lc -ldl
main_intercept: bin/main.o src/intercept.c
$(CC) -o $@ $^ $(CFLAGS) -lc -Wl,--wrap=malloc,--wrap=free,--wrap=calloc,--wrap=realloc,--wrap=reallocarray,--wrap=getopt,--wrap=exit,--wrap=close,--wrap=sigaction,\
--wrap=sem_init,--wrap=sem_open,--wrap=sem_post,--wrap=sem_wait,--wrap=sem_trywait,--wrap=sem_timedwait,--wrap=sem_getvalue,--wrap=sem_close,--wrap=sem_unlink,--wrap=sem_destroy,\
--wrap=shm_open,--wrap=shm_unlink,--wrap=mmap,--wrap=munmap,\
--wrap=ftruncate
clean:
rm -rf main_intercept bin/* *.so *.ko *.o

View File

@@ -45,7 +45,7 @@ static int (*__real_shm_open)(const char *, int, mode_t);
static int (*__real_shm_unlink)(const char *); static int (*__real_shm_unlink)(const char *);
static void *(*__real_mmap)(void *, size_t, int, int, int, off_t); static void *(*__real_mmap)(void *, size_t, int, int, int, off_t);
static int (*__real_munmap)(void *, size_t); static int (*__real_munmap)(void *, size_t);
extern int (*__real_ftruncate)(int, off_t); static int (*__real_ftruncate)(int, off_t);
#define load(name) \ #define load(name) \
if (((__real_ ## name) = dlsym(RTLD_NEXT, #name)) == NULL) { \ if (((__real_ ## name) = dlsym(RTLD_NEXT, #name)) == NULL) { \
fprintf(stderr, "intercept: unable to load symbol '%s': %s", #name, strerror(errno)); \ fprintf(stderr, "intercept: unable to load symbol '%s': %s", #name, strerror(errno)); \
@@ -558,7 +558,9 @@ static void init(void) {
return; return;
} }
fprintf(stderr, "intercept: intercepting function/system calls and logging to unix socket\n"); fprintf(stderr, "intercept: intercepting function/system calls and logging to unix socket\n");
msg("PID:%li", getpid()); char buf[256] = "";
const ssize_t ret = readlink("/proc/self/exe", buf, sizeof(buf));
msg("PID:%li%s%s", getpid(), ret != -1 ? ";PATH:" : "", buf);
} else if (val && strncmp(val, "tcp://", 6) == 0) { } else if (val && strncmp(val, "tcp://", 6) == 0) {
mode = 5; mode = 5;
// TODO socket/tcp mode // TODO socket/tcp mode

View File

@@ -3,7 +3,6 @@
from typing import Optional, TypedDict, NotRequired from typing import Optional, TypedDict, NotRequired
from socketserver import UnixStreamServer, StreamRequestHandler, ThreadingMixIn from socketserver import UnixStreamServer, StreamRequestHandler, ThreadingMixIn
import argparse
import os import os
import re import re
@@ -21,7 +20,8 @@ class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer):
class Handler(StreamRequestHandler): class Handler(StreamRequestHandler):
pid: int pid: Optional[int]
path: Optional[str]
stack: list[tuple[int, str, tuple]] stack: list[tuple[int, str, tuple]]
ret_addr: int ret_addr: int
@@ -32,9 +32,11 @@ class Handler(StreamRequestHandler):
def handle(self): def handle(self):
first = self.rfile.readline() first = self.rfile.readline()
self.pid = int(first.split(b':')[1]) meta = {a[0]: a[1] for a in [tuple(p.decode('utf-8').split(':', 1)) for p in first.split(b' ', 1)[1].strip().split(b';')]}
self.pid = int(meta['PID']) if 'PID' in meta else None
self.path = meta['PATH'] if 'PATH' in meta else None
self.stack = [] self.stack = []
print(f'Process with PID {self.pid} connected') print(f'Process with PID {self.pid} connected ({self.path})')
self.before() self.before()
try: try:
while True: while True:
@@ -317,136 +319,6 @@ class Handler(StreamRequestHandler):
raise NotImplementedError() raise NotImplementedError()
class MemoryAllocationTester(Handler):
allocated: dict[int, tuple[str, int, int]]
max_allocated: int
num_malloc: int
num_realloc: int
num_free: int
num_invalid_free: int
def before(self):
self.allocated = {}
self.max_allocated = 0
self.num_malloc = 0
self.num_realloc = 0
self.num_free = 0
self.num_invalid_free = 0
def after(self):
if len(self.allocated) > 0:
print("Not free'd:")
for ptr, (func, ret, size) in self.allocated.items():
print(f' 0x{ptr:x}: {size} bytes ({func}, return address 0x{ret:x})')
else:
print("All blocks free'd!")
print(f'Max allocated: {self.max_allocated} bytes')
def update_max_allocated(self):
total = sum(a[2] for a in self.allocated.values())
if total > self.max_allocated:
self.max_allocated = total
def after_malloc(self, size, ret_value, errno=None) -> None:
self.num_malloc += 1
if ret_value != 0:
self.allocated[ret_value] = ('malloc', self.ret_addr, size)
self.update_max_allocated()
def after_calloc(self, nmemb, size, ret_value, errno=None) -> None:
self.num_malloc += 1
if ret_value != 0:
self.allocated[ret_value] = ('calloc', self.ret_addr, nmemb * size)
self.update_max_allocated()
def after_realloc(self, ptr, size, ret_value, errno=None) -> None:
self.num_realloc += 1
if ptr != 0:
if ret_value != 0:
v = self.allocated[ptr]
del self.allocated[ptr]
self.allocated[ret_value] = (v[0], v[1], size)
self.update_max_allocated()
def after_reallocarray(self, ptr, nmemb, size, ret_value, errno=None) -> None:
self.num_realloc += 1
if ptr != 0:
if ret_value != 0:
v = self.allocated[ptr]
del self.allocated[ptr]
self.allocated[ret_value] = (v[0], v[1], nmemb * size)
self.update_max_allocated()
def after_free(self, ptr) -> None:
self.num_free += 1
if ptr != 0:
if ptr in self.allocated:
del self.allocated[ptr]
else:
self.num_free -= 1
self.num_invalid_free += 1
class ReturnValueCheckTester(Handler):
pass
class InterruptedCheckTester(Handler):
cycles: int = 50
functions: dict[str, tuple[str or None, str]] = {
'sem_wait': ('fail EINTR', 'return 0'),
'sem_trywait': ('fail EINTR', 'return 0'),
'sem_timedwait': ('fail EINTR', 'return 0'),
'sem_post': (None, 'return 0'),
'ftruncate': ('fail EINTR', 'ok'),
}
counter: int = 0
last_func_name: Optional[str] = None
last_ret_addr: Optional[int] = None
tested_functions: dict[tuple[str, int], str]
@property
def while_testing(self) -> bool:
return self.counter % self.cycles != 0
def before(self) -> None:
self.tested_functions = {}
def after(self) -> None:
if self.while_testing:
self.error()
for (name, ret_addr), status in self.tested_functions.items():
print(f'{name} (0x{ret_addr:x}) -> {status}')
def error(self):
print(f'Error: Return value and errno EINTR not handled correctly in {self.last_func_name} (return address 0x{self.last_ret_addr:x})')
self.tested_functions[(self.last_func_name, self.last_ret_addr)] = 'failed'
self.counter = 0
self.last_func_name = None
self.last_ret_addr = None
def before_fallback(self, func_name: str, *args) -> str:
if self.while_testing and (self.last_func_name != func_name or self.last_ret_addr != self.ret_addr):
self.error()
return 'ok'
elif func_name not in self.functions:
return 'ok'
elif self.functions[func_name][0] is None:
return self.functions[func_name][1]
self.counter += 1
if self.while_testing:
self.last_ret_addr = self.ret_addr
self.last_func_name = func_name
self.tested_functions[(self.last_func_name, self.last_ret_addr)] = 'running'
return self.functions[func_name][0]
else:
self.tested_functions[(self.last_func_name, self.last_ret_addr)] = 'passed'
self.last_ret_addr = None
self.last_func_name = None
return self.functions[func_name][1]
def intercept(socket: str, handler: type[Handler]) -> None: def intercept(socket: str, handler: type[Handler]) -> None:
try: try:
with ThreadedUnixStreamServer(socket, handler) as server: with ThreadedUnixStreamServer(socket, handler) as server:
@@ -459,14 +331,3 @@ def intercept(socket: str, handler: type[Handler]) -> None:
os.unlink(socket) os.unlink(socket)
except FileNotFoundError: except FileNotFoundError:
pass pass
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('socket', metavar='FILE')
args = parser.parse_args()
intercept(args.socket, Handler)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,165 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from intercept import *
FUNCTION_ERRORS: dict[str, list[str]] = {
'malloc': ['ENOMEM'],
'calloc': ['ENOMEM'],
'realloc': ['ENOMEM'],
'reallocarray': ['ENOMEM'],
'close': ['EBADF'], # EINTR, EIO
'sigaction': ['EINVAL'],
'sem_init': ['EINVAL', 'ENOSYS'],
'sem_open': ['EACCES', 'EEXIST', 'EINVAL', 'EMFILE', 'ENAMETOOLONG', 'ENFILE', 'ENOENT', 'ENOMEM'],
'sem_post': ['EINVAL', 'EOVERFLOW'],
'sem_wait': ['EINTR', 'EINVAL'],
'sem_trywait': ['EAGAIN', 'EINTR', 'EINVAL'],
'sem_timedwait': ['EINTR', 'EINVAL', 'ETIMEDOUT'],
'sem_getvalule': ['EINVAL'],
'sem_close': ['EINVAL'],
'sem_unlink': ['EACCES', 'ENAMETOOLONG', 'ENOENT'],
'sem_destroy': ['EINVAL'],
'shm_open': ['EACCES', 'EEXIST', 'EINVAL', 'EMFILE', 'ENAMETOOLONG', 'ENFILE', 'ENOENT'],
'shm_unlink': ['EACCES', 'ENAMETOOLONG', 'ENOENT'],
'mmap': ['EACCES', 'EBADF', 'EINVAL', 'EMFILE', 'ENODEV', 'ENOMEM', 'ENOTSUP', 'ENXIO', 'EOVERFLOW'], # EAGAIN
'munmap': ['EINVAL'],
'ftruncate': ['EINTR', 'EINVAL', 'EFBIG', 'EIO', 'EBADF'],
}
SKIP_ERRORS: list[str] = ['EINTR']
IGNORE_ERRORS: list[str] = ['EINVAL', 'EBADF', 'EOVERFLOW', 'ENAMETOOLONG']
class MemoryAllocationTester(Handler):
allocated: dict[int, tuple[str, int, int]]
max_allocated: int
num_malloc: int
num_realloc: int
num_free: int
num_invalid_free: int
def before(self):
self.allocated = {}
self.max_allocated = 0
self.num_malloc = 0
self.num_realloc = 0
self.num_free = 0
self.num_invalid_free = 0
def after(self):
if len(self.allocated) > 0:
print("Not free'd:")
for ptr, (func, ret, size) in self.allocated.items():
print(f' 0x{ptr:x}: {size} bytes ({func}, return address 0x{ret:x})')
else:
print("All blocks free'd!")
print(f'Max allocated: {self.max_allocated} bytes')
def update_max_allocated(self):
total = sum(a[2] for a in self.allocated.values())
if total > self.max_allocated:
self.max_allocated = total
def after_malloc(self, size, ret_value, errno=None) -> None:
self.num_malloc += 1
if ret_value != 0:
self.allocated[ret_value] = ('malloc', self.ret_addr, size)
self.update_max_allocated()
def after_calloc(self, nmemb, size, ret_value, errno=None) -> None:
self.num_malloc += 1
if ret_value != 0:
self.allocated[ret_value] = ('calloc', self.ret_addr, nmemb * size)
self.update_max_allocated()
def after_realloc(self, ptr, size, ret_value, errno=None) -> None:
self.num_realloc += 1
if ptr != 0:
if ret_value != 0:
v = self.allocated[ptr]
del self.allocated[ptr]
self.allocated[ret_value] = (v[0], v[1], size)
self.update_max_allocated()
def after_reallocarray(self, ptr, nmemb, size, ret_value, errno=None) -> None:
self.num_realloc += 1
if ptr != 0:
if ret_value != 0:
v = self.allocated[ptr]
del self.allocated[ptr]
self.allocated[ret_value] = (v[0], v[1], nmemb * size)
self.update_max_allocated()
def after_free(self, ptr) -> None:
self.num_free += 1
if ptr != 0:
if ptr in self.allocated:
del self.allocated[ptr]
else:
self.num_free -= 1
self.num_invalid_free += 1
class InterruptedCheckTester(Handler):
cycles: int = 50
functions: dict[str, tuple[str or None, str]] = {
fn: ('fail EINTR' if fn not in ('sem_post',) else None,
'return 0' if fn.startswith('sem_') else 'ok')
for fn, errors in FUNCTION_ERRORS.items()
if 'EINTR' in errors or fn in ('sem_post',)
}
counter: int = 0
last_func_name: Optional[str] = None
last_ret_addr: Optional[int] = None
tested_functions: dict[tuple[str, int], str]
@property
def while_testing(self) -> bool:
return self.counter % self.cycles != 0
def before(self) -> None:
self.tested_functions = {}
def after(self) -> None:
if self.while_testing:
self.error()
for (name, ret_addr), status in self.tested_functions.items():
print(f'{name} (0x{ret_addr:x}) -> {status}')
def error(self):
print(f'Error: Return value and errno EINTR not handled correctly in {self.last_func_name} (return address 0x{self.last_ret_addr:x})')
self.tested_functions[(self.last_func_name, self.last_ret_addr)] = 'failed'
self.counter = 0
self.last_func_name = None
self.last_ret_addr = None
def before_fallback(self, func_name: str, *args) -> str:
if self.while_testing and (self.last_func_name != func_name or self.last_ret_addr != self.ret_addr):
self.error()
return 'ok'
elif func_name not in self.functions:
return 'ok'
elif self.functions[func_name][0] is None:
return self.functions[func_name][1]
self.counter += 1
if self.while_testing:
self.last_ret_addr = self.ret_addr
self.last_func_name = func_name
self.tested_functions[(self.last_func_name, self.last_ret_addr)] = 'running'
return self.functions[func_name][0]
else:
self.tested_functions[(self.last_func_name, self.last_ret_addr)] = 'passed'
self.last_ret_addr = None
self.last_func_name = None
return self.functions[func_name][1]
class ReturnValueCheckTester(Handler):
functions: dict[str, list[str]] = {
fn: [e for e in errors if e not in SKIP_ERRORS and e not in IGNORE_ERRORS]
for fn, errors in FUNCTION_ERRORS.items()
if len(set(errors) - set(SKIP_ERRORS) - set(IGNORE_ERRORS)) > 0
}

View File

@@ -1,46 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import intercept
class TestHandler(intercept.Handler):
allocated: dict[int, int]
def before(self):
self.allocated = {}
def after(self):
if len(self.allocated) > 0:
print("Not free'd:")
for ptr, size in self.allocated.items():
print(f' 0x{ptr:x}: {size} bytes')
else:
print("All blocks free'd!")
def after_malloc(self, size, ret_value, errno=None) -> None:
if ret_value != 0:
self.allocated[ret_value] = size
def before_free(self, ptr) -> str:
if ptr != 0:
del self.allocated[ptr]
return 'ok'
class GetoptHandler(intercept.Handler):
def after_getopt(self, argc, argv, optstring, ret_value) -> None:
print(argc, [v[1] for v in argv[1]], optstring[1], ret_value)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('socket', metavar='FILE')
args = parser.parse_args()
intercept.intercept(args.socket, intercept.MemoryAllocationTester)
if __name__ == '__main__':
main()

28
proj/server/src/test-interrupts Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import argparse
import threading
import subprocess
import intercept
import intercept.standard
def socket_thread(socket: str) -> None:
intercept.intercept(socket, intercept.standard.InterruptedCheckTester)
def main() -> None:
parser = argparse.ArgumentParser()
args, extra = parser.parse_known_args()
socket_name = f'/tmp/intercept.interrupts.{os.getpid()}.sock'
t1 = threading.Thread(target=socket_thread, args=(socket_name,))
t1.daemon = True
t1.start()
subprocess.run(extra, env={'LD_PRELOAD': os.getcwd() + '/../../intercept/intercept.so', 'INTERCEPT': 'unix:' + socket_name})
if __name__ == '__main__':
main()

28
proj/server/src/test-memory Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import argparse
import threading
import subprocess
import intercept
import intercept.standard
def socket_thread(socket: str) -> None:
intercept.intercept(socket, intercept.standard.MemoryAllocationTester)
def main() -> None:
parser = argparse.ArgumentParser()
args, extra = parser.parse_known_args()
socket_name = f'/tmp/intercept.memory.{os.getpid()}.sock'
t1 = threading.Thread(target=socket_thread, args=(socket_name,))
t1.daemon = True
t1.start()
subprocess.run(extra, env={'LD_PRELOAD': os.getcwd() + '/../../intercept/intercept.so', 'INTERCEPT': 'unix:' + socket_name})
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import argparse
import threading
import subprocess
import intercept
import intercept.standard
def socket_thread(socket: str) -> None:
intercept.intercept(socket, intercept.standard.ReturnValueCheckTester)
def main() -> None:
parser = argparse.ArgumentParser()
args, extra = parser.parse_known_args()
socket_name = f'/tmp/intercept.return-values.{os.getpid()}.sock'
t1 = threading.Thread(target=socket_thread, args=(socket_name,))
t1.daemon = True
t1.start()
subprocess.run(extra, env={'LD_PRELOAD': os.getcwd() + '/../../intercept/intercept.so', 'INTERCEPT': 'unix:' + socket_name})
if __name__ == '__main__':
main()

View File

@@ -4,7 +4,7 @@ CFLAGS=-std=c99 -pedantic -Wall -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_SVID_SOURCE -
.PHONY: all clean .PHONY: all clean
all: default all: default
default: bin main intercept.so main_intercept #test_kernel.ko default: bin main
bin: bin:
mkdir -p bin/ mkdir -p bin/
@@ -12,26 +12,14 @@ bin:
bin/main.o: src/main.c bin/main.o: src/main.c
$(CC) -c -o $@ $^ $(CFLAGS) $(CC) -c -o $@ $^ $(CFLAGS)
bin/intercept_preload.o: src/intercept.c
$(CC) -fPIC -c -o $@ $^ $(CFLAGS) -DINTERCEPT_PRELOAD
intercept.so: bin/intercept_preload.o
$(CC) -shared -o $@ $^ $(CFLAGS) -lc -ldl
test_kernel.ko: src/test_kernel.c test_kernel.ko: src/test_kernel.c
$(CC) -D__KERNEL__ -DMODULE -I/usr/src/linux/include -o $@ $^ $(CC) -D__KERNEL__ -DMODULE -I/usr/src/linux/include -o $@ $^
main: bin/main.o main: bin/main.o
$(CC) -o $@ $^ $(CFLAGS) -lc -lpthread $(CC) -o $@ $^ $(CFLAGS) -lc -lpthread
main_intercept: bin/main.o src/intercept.c
$(CC) -o $@ $^ $(CFLAGS) -lc -Wl,--wrap=malloc,--wrap=free,--wrap=calloc,--wrap=realloc,--wrap=reallocarray,--wrap=getopt,--wrap=exit,--wrap=close,--wrap=sigaction,\
--wrap=sem_init,--wrap=sem_open,--wrap=sem_post,--wrap=sem_wait,--wrap=sem_trywait,--wrap=sem_timedwait,--wrap=sem_getvalue,--wrap=sem_close,--wrap=sem_unlink,--wrap=sem_destroy,\
--wrap=shm_open,--wrap=shm_unlink,--wrap=mmap,--wrap=munmap,\
--wrap=ftruncate
clean: clean:
rm -rf main main_wrapped bin/* *.so *.ko *.o rm -rf main bin/* *.so *.ko *.o
#ifneq ($(KERNELRELEASE),) #ifneq ($(KERNELRELEASE),)