344 lines
11 KiB
C
344 lines
11 KiB
C
|
|
#include <getopt.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <time.h>
|
|
#include <stdarg.h>
|
|
|
|
#ifdef INTERCEPT_PRELOAD
|
|
static void *(*__real_malloc)(size_t);
|
|
static void *(*__real_calloc)(size_t, size_t);
|
|
static void *(*__real_realloc)(void *, size_t);
|
|
static void (*__real_free)(void *);
|
|
static int (*__real_getopt)(int, char *const [], const char *);
|
|
#define __load(var, name) \
|
|
if (((var) = dlsym(RTLD_NEXT, name)) == NULL) { \
|
|
fprintf(stderr, "intercept: unable to load symbol '%s': %s", name, strerror(errno)); \
|
|
return; \
|
|
}
|
|
#define __sym(name) name
|
|
#else
|
|
extern void *__real_malloc(size_t);
|
|
extern void *__real_calloc(size_t, size_t);
|
|
extern void *__real_realloc(void *, size_t);
|
|
extern void __real_free(void *);
|
|
extern int __real_getopt(int, char *const [], const char *);
|
|
#define __sym(name) __wrap_ ## name
|
|
#endif
|
|
|
|
static int mode = 0;
|
|
static int intercept = 0;
|
|
|
|
static size_t msg_str(char *buf, size_t maxlen, const char *str) {
|
|
size_t offset = snprintf(buf, maxlen, "%p:\"", str);
|
|
for (char ch; (ch = *str) != 0 && offset <= maxlen - 2; str++) {
|
|
if (ch == '\\' || ch == '"') {
|
|
buf[offset++] = '\\';
|
|
buf[offset++] = ch;
|
|
} else if (ch == '\t') {
|
|
buf[offset++] = '\\';
|
|
buf[offset++] = 't';
|
|
} else if (ch == '\n') {
|
|
buf[offset++] = '\\';
|
|
buf[offset++] = 'n';
|
|
} else if (ch == '\r') {
|
|
buf[offset++] = '\\';
|
|
buf[offset++] = 'r';
|
|
} else if ((ch >= 0 && ch < 0x20) || ch == 0x7F) {
|
|
offset += snprintf(buf + offset, maxlen - offset, "\\x%02x", ch);
|
|
} else {
|
|
buf[offset++] = ch;
|
|
}
|
|
}
|
|
if (offset <= maxlen - 2) {
|
|
buf[offset++] = '"';
|
|
buf[offset] = 0;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
static size_t msg_array_str(char *buf, size_t maxlen, char *const array[], int n) {
|
|
size_t offset = snprintf(buf, maxlen, "%p:[", (void *)array);
|
|
for (int i = 0; i < n && offset <= maxlen - 2; i++) {
|
|
if (i > 0) {
|
|
buf[offset++] = ',';
|
|
buf[offset++] = ' ';
|
|
}
|
|
offset += msg_str(buf + offset, maxlen - offset, array[i]);
|
|
}
|
|
if (offset <= maxlen - 2) {
|
|
buf[offset++] = ']';
|
|
buf[offset] = 0;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
static void msg(const char *fmt, ...) {
|
|
if (!intercept) return;
|
|
char buf[1024], sub_fmt[16];
|
|
int sub_fmt_p = 0;
|
|
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
|
|
struct timespec spec;
|
|
clock_gettime(CLOCK_REALTIME, &spec);
|
|
size_t offset = snprintf(buf, sizeof(buf), "%li.%09li ", spec.tv_sec, spec.tv_nsec);
|
|
for (char ch, state = 0; (ch = *fmt) != 0 && offset < sizeof(buf); fmt++) {
|
|
if (state == '%') {
|
|
if (ch == '%') {
|
|
buf[offset++] = ch;
|
|
state = 0;
|
|
continue;
|
|
} else if (ch == 'e') {
|
|
state = 'e';
|
|
continue;
|
|
} else if (ch == 'a') {
|
|
state = 'a';
|
|
continue;
|
|
} else {
|
|
sub_fmt_p = 0;
|
|
state = '_';
|
|
sub_fmt[sub_fmt_p++] = '%';
|
|
sub_fmt[sub_fmt_p] = 0;
|
|
}
|
|
}
|
|
if (state == '_') {
|
|
sub_fmt[sub_fmt_p++] = ch;
|
|
sub_fmt[sub_fmt_p] = 0;
|
|
if (ch == 's' || ch == 'i' || ch == 'd' || ch == 'o' || ch == 'u' || ch == 'x' || ch == 'X' || ch == 'f' || ch == 'p') {
|
|
state = 0;
|
|
offset += snprintf(buf + offset, sizeof(buf) - offset, sub_fmt, va_arg(args, long int));
|
|
}
|
|
} else if (state == 'e') {
|
|
if (ch == 's') {
|
|
// escaped string
|
|
offset += msg_str(buf + offset, sizeof(buf) - offset, va_arg(args, const char *));
|
|
} else {
|
|
// error
|
|
va_end(args);
|
|
return;
|
|
}
|
|
state = 0;
|
|
} else if (state == 'a') {
|
|
if (ch == 's') {
|
|
// string array
|
|
char *const *array = va_arg(args, char *const *);
|
|
const int len = va_arg(args, int);
|
|
offset += msg_array_str(buf + offset, sizeof(buf) - offset, array, len);
|
|
} else {
|
|
// error
|
|
va_end(args);
|
|
return;
|
|
}
|
|
state = 0;
|
|
} else if (ch == '%') {
|
|
state = '%';
|
|
} else {
|
|
buf[offset++] = ch;
|
|
}
|
|
}
|
|
if (offset <= sizeof(buf) - 2) {
|
|
buf[offset++] = '\n';
|
|
buf[offset] = 0;
|
|
}
|
|
const size_t size = offset >= sizeof(buf) ? sizeof(buf) - 1 : offset;
|
|
|
|
ssize_t ret;
|
|
for (size_t written = 0; written < size; written += ret) {
|
|
if ((ret = write(intercept, buf, size)) == -1) {
|
|
if (errno == EINTR) {
|
|
ret = 0;
|
|
continue;
|
|
}
|
|
va_end(args);
|
|
return;
|
|
}
|
|
}
|
|
|
|
va_end(args);
|
|
}
|
|
|
|
static void rcv(char *buf, const int size) {
|
|
if (!intercept) return;
|
|
size_t num = 0;
|
|
for (ssize_t ret; num == 0 || buf[num - 1] != '\n'; num += ret) {
|
|
if ((ret = read(intercept, buf, size)) == -1) {
|
|
if (errno == EINTR) {
|
|
ret = 0;
|
|
continue;
|
|
}
|
|
buf[0] = 0;
|
|
return;
|
|
}
|
|
}
|
|
buf[num - 1] = 0;
|
|
}
|
|
|
|
static void fin(void) {
|
|
if (intercept && mode > 2)
|
|
close(intercept);
|
|
if (mode > 0)
|
|
fprintf(stderr, "intercept: stopped\n");
|
|
mode = 0, intercept = 0;
|
|
}
|
|
|
|
static void init(void) {
|
|
if (mode) return;
|
|
#ifdef INTERCEPT_PRELOAD
|
|
__load(__real_malloc, "malloc");
|
|
__load(__real_calloc, "calloc");
|
|
__load(__real_realloc, "realloc");
|
|
__load(__real_free, "free");
|
|
__load(__real_getopt, "getopt");
|
|
#endif
|
|
atexit(fin);
|
|
const char *val = getenv("INTERCEPT");
|
|
if (val && strcmp(val, "stdout") == 0) {
|
|
mode = 1;
|
|
fprintf(stderr, "intercept: intercepting function/system calls and logging to stdout\n");
|
|
intercept = STDOUT_FILENO;
|
|
} else if (val && strcmp(val, "stderr") == 0) {
|
|
mode = 2;
|
|
fprintf(stderr, "intercept: intercepting function/system calls and logging to stderr\n");
|
|
intercept = STDERR_FILENO;
|
|
} else if (val && strncmp(val, "file:", 5) == 0) {
|
|
mode = 3;
|
|
if ((intercept = open(val + 5, O_CREAT | O_TRUNC | O_WRONLY, 0644)) == -1) {
|
|
fprintf(stderr, "intercept: unable to open log file '%s': %s\nintercept: not logging or manipulating function/system calls\n", val + 5, strerror(errno));
|
|
errno = 0;
|
|
mode = -1;
|
|
return;
|
|
}
|
|
fprintf(stderr, "intercept: intercepting function/system calls and logging to file\n");
|
|
} else if (val && strncmp(val, "unix:", 5) == 0) {
|
|
mode = 4;
|
|
if ((intercept = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
|
fprintf(stderr, "intercept: unable to open unix socket '%s': %s\nintercept: not logging or manipulating function/system calls\n", val + 5, strerror(errno));
|
|
errno = 0;
|
|
mode = -1;
|
|
return;
|
|
}
|
|
struct sockaddr_un addr = {.sun_family = AF_UNIX};
|
|
strncpy(addr.sun_path, val + 5, sizeof(addr.sun_path));
|
|
if (connect(intercept, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
|
|
fprintf(stderr, "intercept: unable to connect to unix socket '%s': %s\nintercept: not logging or manipulating function/system calls\n", val + 5, strerror(errno));
|
|
close(intercept);
|
|
errno = 0;
|
|
mode = -1;
|
|
return;
|
|
}
|
|
fprintf(stderr, "intercept: intercepting function/system calls and logging to unix socket\n");
|
|
msg("PID:%li", getpid());
|
|
} else if (val && strncmp(val, "tcp://", 6) == 0) {
|
|
mode = 5;
|
|
// TODO
|
|
} else {
|
|
mode = -1;
|
|
fprintf(stderr, "intercept: not logging or manipulating function/system calls\n");
|
|
}
|
|
}
|
|
|
|
void *__sym(malloc)(size_t size) {
|
|
init();
|
|
msg("malloc(%li)", size);
|
|
if (mode >= 4) {
|
|
char buf[256];
|
|
rcv(buf, sizeof(buf));
|
|
if (strncmp(buf, "modify ", 7) == 0) {
|
|
// modify <size>
|
|
char *end_ptr = NULL;
|
|
long val = strtol(buf + 7, &end_ptr, 0);
|
|
if (end_ptr != NULL && end_ptr[0] == 0 && end_ptr != buf + 7) {
|
|
size = val;
|
|
} else {
|
|
fprintf(stderr, "intercept: malloc: invalid args in modify command: '%s'\n", buf + 7);
|
|
}
|
|
} else if (strncmp(buf, "return ", 7) == 0) {
|
|
// return <ptr>
|
|
char *end_ptr = NULL;
|
|
long val = strtol(buf + 7, &end_ptr, 0);
|
|
if (end_ptr != NULL && end_ptr[0] == 0 && end_ptr != buf + 7) {
|
|
msg("return %p", val);
|
|
return (void *)val;
|
|
} else {
|
|
fprintf(stderr, "intercept: malloc: invalid args in return command: '%s'\n", buf + 7);
|
|
}
|
|
} else if (strncmp(buf, "fail ", 5) == 0) {
|
|
// fail <error>
|
|
if (strcmp(buf + 5, "ENOMEM") == 0) {
|
|
errno = ENOMEM;
|
|
} else {
|
|
errno = 0;
|
|
fprintf(stderr, "intercept: malloc: invalid error code in fail command: '%s'\n", buf + 5);
|
|
}
|
|
msg("return %p", NULL);
|
|
return NULL;
|
|
} else if (strcmp(buf, "ok") != 0) {
|
|
fprintf(stderr, "intercept: malloc: invalid command: '%s'\n", buf);
|
|
}
|
|
}
|
|
void *ret = __real_malloc(size);
|
|
msg("return %p", ret);
|
|
return ret;
|
|
}
|
|
|
|
void *__sym(calloc)(size_t nmemb, size_t size) {
|
|
init();
|
|
msg("calloc(%li, %li)", nmemb, size);
|
|
if (mode >= 4) {
|
|
char buf[256];
|
|
rcv(buf, sizeof(buf));
|
|
// TODO
|
|
}
|
|
void *ret = __real_calloc(nmemb, size);
|
|
msg("return %p", ret);
|
|
return ret;
|
|
}
|
|
|
|
void *__sym(realloc)(void *ptr, size_t size) {
|
|
init();
|
|
msg("realloc(%p, %li)", ptr, size);
|
|
if (mode >= 4) {
|
|
char buf[256];
|
|
rcv(buf, sizeof(buf));
|
|
// TODO
|
|
}
|
|
void *ret = __real_realloc(ptr, size);
|
|
msg("return %p", ret);
|
|
return ret;
|
|
}
|
|
|
|
void __sym(free)(void *ptr) {
|
|
init();
|
|
msg("free(%p)", ptr);
|
|
if (mode >= 4) {
|
|
char buf[256];
|
|
rcv(buf, sizeof(buf));
|
|
// TODO
|
|
}
|
|
__real_free(ptr);
|
|
msg("return");
|
|
}
|
|
|
|
int __sym(getopt)(const int argc, char *const argv[], const char *shortopts) {
|
|
init();
|
|
msg("getopt(%i, %as, %es)", argc, argv, argc, shortopts);
|
|
if (mode >= 4) {
|
|
char buf[256];
|
|
rcv(buf, sizeof(buf));
|
|
// TODO
|
|
}
|
|
int ret = __real_getopt(argc, argv, shortopts);
|
|
msg("return %i", ret);
|
|
return ret;
|
|
}
|