From 2fff206f6af6b3b4e65873d7694ee6100aa22b8e Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Tue, 6 May 2025 18:36:51 +0200 Subject: [PATCH] proj/test-memory: Add getline() and getdelim() --- proj/intercept/Makefile | 3 +- proj/intercept/src/intercept.c | 59 ++++++++++++++++++++++++++- proj/server/src/intercept/__init__.py | 48 +++++++++++++++------- proj/server/src/intercept/standard.py | 44 +++++++++++++++----- proj/server/src/test-memory | 2 +- 5 files changed, 129 insertions(+), 27 deletions(-) diff --git a/proj/intercept/Makefile b/proj/intercept/Makefile index 34be243..a7c7683 100644 --- a/proj/intercept/Makefile +++ b/proj/intercept/Makefile @@ -26,7 +26,8 @@ main_intercept: bin/main.o src/intercept.c --wrap=ftruncate,--wrap=fork,--wrap=wait,--wrap=waitpid,--wrap=pipe,--wrap=dup,--wrap=dup2,--wrap=dup3,\ --wrap=execl,--wrap=execlp,--wrap=execle,--wrap=execv,--wrap=execvp,--wrap=execvpe,--wrap=execve,--wrap=fexecve,\ --wrap=socket,--wrap=bind,--wrap=listen,--wrap=accept,--wrap=connect,--wrap=getaddrinfo,--wrap=freeaddrinfo,\ ---wrap=send,--wrap=sendto,--wrap=sendmsg,--wrap=recv,--wrap=recvfrom,--wrap=recvmsg +--wrap=send,--wrap=sendto,--wrap=sendmsg,--wrap=recv,--wrap=recvfrom,--wrap=recvmsg,\ +--wrap=getline,--wrap=getdelim clean: rm -rf main_intercept bin/* *.so *.ko *.o diff --git a/proj/intercept/src/intercept.c b/proj/intercept/src/intercept.c index 92c111c..959c2f9 100644 --- a/proj/intercept/src/intercept.c +++ b/proj/intercept/src/intercept.c @@ -94,6 +94,8 @@ func_def(ssize_t, sendmsg)(int, const struct msghdr *, int); func_def(ssize_t, recv)(int, void *, size_t, int); func_def(ssize_t, recvfrom)(int, void *, size_t, int, struct sockaddr *, socklen_t *); func_def(ssize_t, recvmsg)(int, struct msghdr *, int); +func_def(ssize_t, getline)(char **, size_t *, FILE *); +func_def(ssize_t, getdelim)(char **, size_t *, int, FILE *); #define func_idx_malloc 0 #define func_idx_calloc 1 @@ -151,6 +153,8 @@ func_def(ssize_t, recvmsg)(int, struct msghdr *, int); #define func_idx_recv 53 #define func_idx_recvfrom 54 #define func_idx_recvmsg 55 +#define func_idx_getline 56 +#define func_idx_getdelim 57 #define FUNCTIONS \ X(malloc) \ @@ -208,7 +212,9 @@ func_def(ssize_t, recvmsg)(int, struct msghdr *, int); X(sendmsg) \ X(recv) \ X(recvfrom) \ - X(recvmsg) + X(recvmsg) \ + X(getline) \ + X(getdelim) #define case_const(name) case name: return #name @@ -2756,3 +2762,54 @@ ssize_t sym(recvmsg)(int sockfd, struct msghdr *message, int flags) { } return ret; } + +ssize_t sym(getline)(char **restrict lineptr, size_t *restrict n, FILE *restrict stream) { + init(); + Dl_info info; + if (!dladdr(ret_addr, &info) || !func_flags[func_idx_getline] || !lib_flags[lib_idx]) return __real_getline(lineptr, n, stream); + char src_file[256]; + msg("getline(%p:%p, %p:%li, %p)" ret_str, lineptr, lineptr != NULL ? lineptr : NULL, n, n != NULL ? *n : 0, stream, ret_data); + if (mode >= 4) { + char msg_buf[BUFFER_SIZE]; + rcv(msg_buf, sizeof(msg_buf)); + if (strncmp(msg_buf, "modify ", 7) == 0) { + // TODO getline modify + fprintf(stderr, "intercept: %s: modify command not implemented\n", "getline"); + } else if_return_int_errno(getline) + else if_fail_int_errno(getline) + else if_invalid(getline) + } + ssize_t ret = __real_getline(lineptr, n, stream); + if (ret >= 0) { + msg("return %li; errno %s; n=%li, line=%eb", ret, strerrorname_np(errno), *n, ret, *lineptr); + } else { + msg("return %li; errno %s; n=%li", ret, strerrorname_np(errno), *n); + } + return ret; +} + +ssize_t sym(getdelim)(char **restrict lineptr, size_t *restrict n, int delim, FILE *restrict stream) { + init(); + Dl_info info; + if (!dladdr(ret_addr, &info) || !func_flags[func_idx_getdelim] || !lib_flags[lib_idx]) return __real_getdelim(lineptr, n, delim, stream); + char src_file[256]; + msg("getdelim(%p:%p, %p:%li, %p)" ret_str, lineptr, lineptr != NULL ? lineptr : NULL, n, n != NULL ? *n : 0, stream, ret_data); + if (mode >= 4) { + char msg_buf[BUFFER_SIZE]; + rcv(msg_buf, sizeof(msg_buf)); + if (strncmp(msg_buf, "modify ", 7) == 0) { + // TODO getdelim modify + fprintf(stderr, "intercept: %s: modify command not implemented\n", "getdelim"); + } else if_return_int_errno(getdelim) + else if_fail_int_errno(getdelim) + else if_invalid(getdelim) + } + ssize_t ret = __real_getdelim(lineptr, n, delim, stream); + if (ret >= 0) { + msg("return %li; errno %s; n=%li, line=%eb", ret, strerrorname_np(errno), *n, ret, *lineptr); + } else { + msg("return %li; errno %s; n=%li", ret, strerrorname_np(errno), *n); + } + return ret; +} + diff --git a/proj/server/src/intercept/__init__.py b/proj/server/src/intercept/__init__.py index 46c86bb..3739596 100644 --- a/proj/server/src/intercept/__init__.py +++ b/proj/server/src/intercept/__init__.py @@ -1,16 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from typing import Optional, TypedDict, NotRequired, BinaryIO +from typing import Optional, TypedDict, NamedTuple, NotRequired, BinaryIO from socketserver import UnixStreamServer, StreamRequestHandler, ThreadingMixIn import os import re type Pointer = int -type PointerTo[T] = tuple[Pointer, T] -type Constant = tuple[int, str] -type Flags = tuple[int, list[str]] + +class PointerTo[T](NamedTuple): + ptr: Pointer + target: T + +class Constant(NamedTuple): + raw: int + name: str + +class Flags(NamedTuple): + bitfield: int + flags: list[str] + StructTimeSpec = TypedDict('StructTimeSpec', {'tv_sec': int, 'tv_nsec': int}) StructSigAction = TypedDict('StructSigAction', {'sa_flags': Flags, 'sa_handler': NotRequired[Pointer], 'sa_sigaction': NotRequired[Pointer], 'sa_mask': list[str]}) StructSockAddr = TypedDict('StructSockAddr', {'sa_family': Constant, 'sa_data': NotRequired[bytes], @@ -166,7 +176,7 @@ class Parser: if m: idx = len(m.group(0)) - 1 s, i = Parser.parse_str(argument[idx:]) - idx = i + idx += i if idx < len(argument) and argument[idx] in ',;': idx += 1 return s, idx @@ -207,7 +217,7 @@ class Parser: idx += i if idx < len(argument) and argument[idx] == ',': idx += 1 - return (val, list(l)), idx + return PointerTo(val, list(l)), idx elif argument[idx] == '|': m = re.match(r'^[| A-Za-z0-9_]*', argument[idx:]) flags = m.group(0) @@ -217,20 +227,20 @@ class Parser: if idx < len(argument) and argument[idx] == ',': idx += 1 flags = [f.strip() for f in flags[1:-1].split('|') if len(f.strip()) > 0] - return (val, flags), idx + return Flags(val, flags), idx elif argument[idx] == '"': s, i = Parser.parse_str(argument[idx:]) idx += i if idx < len(argument) and argument[idx] == ',': idx += 1 - return (val, s), idx + return PointerTo(val, s), idx elif argument[idx] == '{': idx += 1 l, i = Parser.parse_args(argument[idx:], named=True) idx += i if idx < len(argument) and argument[idx] == ',': idx += 1 - return (val, list(l)), idx + return PointerTo(val, l), idx else: m = re.match(r'[A-Z0-9_]+', argument[idx:]) if not m: @@ -239,7 +249,7 @@ class Parser: idx += len(value) if idx < len(argument) and argument[idx] == ',': idx += 1 - return (val, value), idx + return Constant(val, value), idx @staticmethod def parse_args(arguments: str, named: bool = False, ret: bool = False) -> tuple[tuple or dict, int]: @@ -594,12 +604,12 @@ class Parser: def before_sendto(self, sockfd: int, buf: PointerTo[bytes], length: int, flags: Flags, dest_addr: PointerTo[StructSockAddr], addrlen: int) -> str: raise NotImplementedError() def after_sendto(self, sockfd: int, buf: PointerTo[bytes], length: int, flags: Flags, dest_addr: PointerTo[StructSockAddr], addrlen: int, - ret_value: int, errno: str = None) -> None: + ret_value: int, errno: str = None) -> None: raise NotImplementedError() def before_sendmsg(self, sockfd: int, message: StructMsgHdr, flags: Flags) -> str: raise NotImplementedError() def after_sendmsg(self, sockfd: int, message: StructMsgHdr, flags: Flags, - ret_value: int, errno: str = None) -> None: + ret_value: int, errno: str = None) -> None: raise NotImplementedError() def before_recv(self, sockfd: int, buf_ptr: Pointer, size: int, flags: Flags) -> str: raise NotImplementedError() @@ -609,12 +619,22 @@ class Parser: def before_recvfrom(self, sockfd: int, buf_ptr: Pointer, size: int, flags: Flags, src_addr_ptr: Pointer, addrlen_ptr: Pointer) -> str: raise NotImplementedError() def after_recvfrom(self, sockfd: int, buf_ptr: Pointer, size: int, flags: Flags, src_addr_ptr: Pointer, addrlen_ptr: Pointer, - ret_value: int, errno: str = None, buf: PointerTo[bytes] = None, src_addr: PointerTo[StructSockAddr] = None, addrlen: int = None) -> None: + ret_value: int, errno: str = None, buf: PointerTo[bytes] = None, src_addr: PointerTo[StructSockAddr] = None, addrlen: int = None) -> None: raise NotImplementedError() def before_recvmsg(self, sockfd: int, message_ptr: Pointer, flags: Flags) -> str: raise NotImplementedError() def after_recvmsg(self, sockfd: int, message_ptr: Pointer, flags: Flags, - ret_value: int, errno: str = None, message: PointerTo[StructMsgHdr] = None) -> None: + ret_value: int, errno: str = None, message: PointerTo[StructMsgHdr] = None) -> None: + raise NotImplementedError() + def before_getline(self, line_ptr: PointerTo[Pointer], n_ptr: PointerTo[int], stream: Pointer) -> str: + raise NotImplementedError() + def after_getline(self, line_ptr: PointerTo[Pointer], n_ptr: PointerTo[int], stream: Pointer, + ret_value: int, errno: str = None, n: int = None, line: PointerTo[bytes] = None) -> None: + raise NotImplementedError() + def before_getdelim(self, line_ptr: PointerTo[Pointer], n_ptr: PointerTo[int], delim: int, stream: Pointer) -> str: + raise NotImplementedError() + def after_getdelim(self, line_ptr: PointerTo[Pointer], n_ptr: PointerTo[int], delim: int, stream: Pointer, + ret_value: int, errno: str = None, n: int = None, line: PointerTo[bytes] = None) -> None: raise NotImplementedError() diff --git a/proj/server/src/intercept/standard.py b/proj/server/src/intercept/standard.py index 9deaf1a..9ee588c 100644 --- a/proj/server/src/intercept/standard.py +++ b/proj/server/src/intercept/standard.py @@ -74,25 +74,25 @@ class MemoryAllocationParser(Parser): self.max_allocated = total def after_malloc(self, size, ret_value, errno=None) -> None: - self.num_alloc += 1 if ret_value != 0: + self.num_alloc += 1 self.allocated[ret_value] = (self.get_call_id('malloc'), size) self.update_max_allocated() def after_calloc(self, nmemb, size, ret_value, errno=None) -> None: - self.num_alloc += 1 if ret_value != 0: + self.num_alloc += 1 self.allocated[ret_value] = (self.get_call_id('calloc'), nmemb * size) self.update_max_allocated() def after_realloc(self, ptr, size, ret_value, errno=None) -> None: if ptr == 0: - self.num_alloc += 1 if ret_value != 0: + self.num_alloc += 1 self.allocated[ret_value] = (self.get_call_id('realloc'), size) else: - self.num_realloc += 1 if ret_value != 0 and ptr in self.allocated: + self.num_realloc += 1 v = self.allocated[ptr] del self.allocated[ptr] self.allocated[ret_value] = (v[0], size) @@ -100,22 +100,46 @@ class MemoryAllocationParser(Parser): def after_reallocarray(self, ptr, nmemb, size, ret_value, errno=None) -> None: if ptr == 0: - self.num_alloc += 1 if ret_value != 0: + self.num_alloc += 1 self.allocated[ret_value] = (self.get_call_id('reallocarray'), nmemb * size) else: - self.num_realloc += 1 if ret_value != 0: + self.num_realloc += 1 v = self.allocated[ptr] del self.allocated[ptr] self.allocated[ret_value] = (v[0], nmemb * size) self.update_max_allocated() def after_getaddrinfo(self, node, service, hints, res_ptr, ret_value, errno=None, res=None) -> None: - self.num_alloc += 1 - if ret_value[0] == 0 and res is not None: - size = sum(48 + r['ai_addrlen'] for r in res[1]) - self.allocated[res[0]] = (self.get_call_id('getaddrinfo'), size) + if ret_value.raw == 0 and res is not None: + self.num_alloc += 1 + size = sum(48 + r['ai_addrlen'] for r in res.target) + self.allocated[res.ptr] = (self.get_call_id('getaddrinfo'), size) + self.update_max_allocated() + + def after_getline(self, line_ptr, n_ptr, stream, ret_value, errno=None, n=None, line=None) -> None: + if ret_value >= 0 and n is not None and line is not None: + if line_ptr.target == 0: + self.num_alloc += 1 + self.allocated[line.ptr] = (self.get_call_id('getline'), n) + elif line_ptr.target != line.ptr: + self.num_realloc += 1 + v = self.allocated[line.ptr] + del self.allocated[line.ptr] + self.allocated[ret_value] = (v[0], n) + self.update_max_allocated() + + def after_getdelim(self, line_ptr, n_ptr, delim, stream, ret_value, errno=None, n=None, line=None) -> None: + if ret_value >= 0 and n is not None and line is not None: + if line_ptr.target == 0: + self.num_alloc += 1 + self.allocated[line.ptr] = (self.get_call_id('getdelim'), n) + elif line_ptr.target != line.ptr: + self.num_realloc += 1 + v = self.allocated[line.ptr] + del self.allocated[line.ptr] + self.allocated[ret_value] = (v[0], n) self.update_max_allocated() def after_free(self, ptr) -> None: diff --git a/proj/server/src/test-memory b/proj/server/src/test-memory index b744957..17d6d61 100755 --- a/proj/server/src/test-memory +++ b/proj/server/src/test-memory @@ -46,7 +46,7 @@ def main() -> None: 'LD_PRELOAD': os.getcwd() + '/../../intercept/intercept.so', 'INTERCEPT': 'file:' + log_file, 'INTERCEPT_VERBOSE': '1', - 'INTERCEPT_FUNCTIONS': ','.join(['malloc', 'calloc', 'realloc', 'reallocarray', 'free', 'getaddrinfo', 'freeaddrinfo']), + 'INTERCEPT_FUNCTIONS': ','.join(['malloc', 'calloc', 'realloc', 'reallocarray', 'free', 'getaddrinfo', 'freeaddrinfo', 'getline', 'getdelim']), 'INTERCEPT_LIBRARIES': ','.join(['*', '-/lib*', '-/usr/lib*']), }) finally: