I am trying to intercept execve() via execl(). Here's my wrapper call (built as a shared library — libexec.so).
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
static int (*real_exec)(const char*, char *const [], char *const []) = 0;
static void __attribute__((constructor))init(void) {
real_exec = (int (*)(const char*, char *const [], char *const []))dlsym(RTLD_NEXT, "execve");
}
int execve(const char* arg, char *const argv[], char *const envp[]) {
printf ("In wrapped execve\n");
return (*real_exec)(arg, argv, envp);
}
and here's my program that execs.
//run.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
int pid;
pid = fork();
if (pid == 0) {
execl("/usr/bin/date", "date", NULL);
} else {
wait (NULL);
}
return 0;
}
AFAIK, all other exec* calls are wrapper over execve() system call. I also validated the program above by running with strace.
> strace -f -e execve ./run
execve("./run", ["./run"], 0x7ffcefad6a28 /* 61 vars */) = 0
strace: Process 1491914 attached
[pid 1491914] execve("/usr/bin/date", ["date"], 0x7fff28393cc8 /* 61 vars */) = 0
Tuesday 16 February 2021 12:52:16 PM UTC
[pid 1491914] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=1491914, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
But when I run the program the following way,
> LD_PRELOAD=/home/user/libexec.so ./run
the call is not getting intercepted. i.e I don't see In wrapped execve\n getting printed.
What am I missing here? If I instead directly call execve() in run.c, it works.
Secondly, does LD_PRELOAD also follow child processes? Are the calls made by the children and descendents also intercepted?
Short Answer: The issue is that with the above implemented 'libexec' will only intercepted execve calls between the main program to execve. Calls inside 'libc' between execl and execve will not be intercepted to the libexec wrapper.
Long Answer:
With LD_LIBRARY_PATH=libexec.so, the following calling tree is established: "main" -> "libexec.so" -> "libc.so". Calls from "main" will be intercepted by libexec, but calls inside "libc.so" (e.g., execl -> execve) are not intercepted (by default).
The solution is to add a wrapper to "execl" to the libexec.so, following the same structure as the execve wrapper already implemented.
Related
I am currently facing issue with the glibc v2.22 where I am not able to get the proper unwind information.
When there is SIGABRT application, it is calling abort function from glibc. It should be using unwind information which is enabled in the build. However, it is scanning the stack (as indicated by the red line below the address in the screenshot) and providing the misleading information as shown in the screenshot attached (using sentry for analyzing the dump).
Here, do_crash is called which does assert(0) which then aborts the main application. While analyzing the dump, the do_crash function calls the _fini which is never in the main application's stack.
I have enabled unwind for the glibc by using CFLAGS += "-funwind-tables". I also tried with the flags such as -rdynamic and -fno-omit-frame-pointer but it was also of no use.
Am I missing something here? How can I get the complete backtrace of the signals, particularly SIGABRT?
Thanks in advance
When there is SIGABRT application, it is calling abort function from glibc
That is not true, this is not happening, unless you explicitly registered it.
I have enabled unwind for the glibc by using CFLAGS += "-funwind-tables"
It tells the compiler to add the information, it doesn't "enable unwind". What exactly happens when compiling with -funwind-tables?
Here, do_crash is called which does assert(0) which then aborts the main application.
This is not related to receiving SIGABRT signal.
Am I missing something here?
I believe you are making wrong assumptions - that something is called on SIGABRT, that SIGABRT is sent on assert, that abort() is called on SIGABRT. Nothing is called on SIGABRT and the program is terminated when receiving SIGABRT by default (see man 7 signal), assert just terminates the program and doesn't raise SIGABRT, and abort() raises the SIGABRT signal, not receives it.
How can I get the complete backtrace of the signals, particularly SIGABRT?
Register a handler that will do that. See How to automatically generate a stacktrace when my program crashes .
#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void handler(int sig) {
void *array[10];
size_t size;
size = backtrace(array, 10);
backtrace_symbols_fd(array, size, STDERR_FILENO);
_Exit(1);
}
int main(int argc, char **argv) {
signal(SIGABRT, handler); // install our handler
raise(SIGABRT);
}
If you want to print stacktrace on assert() that's completely different and you would overwrite the glibc handler for assert to do that:
#include <assert.h>
#include <execinfo.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
void print_trace(void) {
void *array[10];
size_t size;
size = backtrace(array, 10);
backtrace_symbols_fd(array, size, STDERR_FILENO);
}
// based on https://code.woboq.org/userspace/glibc/assert/assert.c.html
void __assert_fail(const char *assertion, const char *file, unsigned int line, const char *function) {
extern const char *__progname;
fprintf(stderr, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
print_trace();
abort();
}
int main() {
assert(0);
}
Think of this as a continuation of the good advice here:
https://stackoverflow.com/a/56780616/16739703
except that I am hoping not to modify the child process.
Edit: I have written code which minimises to:
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[], char *envp[]) {
int init_flags=fcntl(0, F_GETFL, 0);
if (fcntl(0, F_SETFL, init_flags | O_ASYNC)) {
perror("fcntl...F_SET_FL....O_ASYNC");
exit(1);
}
if (fcntl(0, F_SETOWN, getpid())) {
perror("fcntl...F_SETOWN...)");
exit(1);
}
if (execve(argv[1], argv+1, envp)) {
perror("execve");
exit(1);
}
return 1;
}
and this makefile:
all: morehup
CFLAGS=-g -D_GNU_SOURCE
LDFLAGS=-g
so that, with this procedure:
parent> export TMPDIR="$(mktemp -d)"
parent> mkfifo $TMPDIR/fifo
parent> sh
# you get a new shell, probably with a different prompt
parent> exec 7<>$TMPDIR/fifo
# must be both input and output, or the process stalls
child> TMPDIR=... # as other shell
child> ./morehup <$TMPDIR/fifo /bin/sh -c "while true; do date; sleep 5; done"
# you get a list of dates
parent> exit
child> I/O possible # followed by a prompt, with no more dates
the kernel will kill the child when the parent exits.
The more configurable version is here:
https://github.com/JamesC1/morehup/blob/main/morehup.c
I have two questions:
What are the chances of adding modest amounts of code, so that this will mostly work for most of the common *nix?
Is there a posix utility that already does something like this? ie am I reinventing the wheel, and if so, what is it called?
I am looking for a ptrace() call to observe a process until the process exits.
I have this which compiles with gcc / cc on OSX:
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sys/ptrace.h>
int main(int argc, char *argv[]) {
pid_t pidx = atoi(argv[1]);
printf("pid = %jd\n", (intmax_t) pidx);
ptrace(PT_ATTACHEXC, pidx, 0, 0);
wait(NULL);
}
However, even with a valid/existing pid, this program will still exit immediately. I am trying to only exit this program after pidx dies.
Is this possible somehow?
Ideally I want something that works on both OSX and Linux.
Your problem is probably that the wait call returns immediately, because the traced "inferior" process is suspended, you know, waiting for you to debug it. You're going to need some kind of loop in which you make ptrace requests to inspect the child and then resume execution, and then call wait again to wait for it to suspend on the next breakpoint or whatever. Unfortunately the debugger API is extremely non-portable; you will have to write most of this program twice, once for OSX and once for Linux.
Is it possible to generate a mini core dump for debugging purpose without crashing the process. Let's say if a function receives an unexpected value, just printing logs and returning gracefully might not be sufficient to debug the issue. On the other hand, if i can get a screenshot of memory and look at the stack, i could find more useful information to debug.
Yes,
According to gdb's documentation, once attached with gdb you may issue the following command:
(gdb) gcore
(gdb) q
This will dump the core to "core.pid" without crashing the process.
or this one-liner:
sudo sh -c 'echo gcore <output_core> | gdb -p <pid>'
There is not building function to do that, you could use ptrace() to debug your own process but it would not be easy. Call gcore is the easiest method.
#include <stdio.h>
#include <unistd.h>
#include <inttypes.h>
#include <sys/wait.h>
int main(void) {
pid_t parent = getpid();
pid_t pid = fork();
if (pid < 0) {
// oh dear we are on trouble
} else if (pid == 0) {
char tmp[42];
snprintf(tmp, sizeof tmp, "%" PRIdMAX, (intmax_t)parent);
execvp("gcore", (char *[]){"gcore", tmp, NULL});
} else {
int wstatus;
waitpid(pid, &wstatus, 0);
}
}
I am trying to spawn a new process using execve() from unistd.h on Linux. I have tried passing it the following parameters execve("/bin/ls", "/bin/ls", NULL); but get no result. I do not get an error either, the program just exits. Is there a reason why this is happening? I have tried launching it as root and regular user. The reason I need to use execve() is because I am trying to get it to work in an assembly call like so
program: db "/bin/ls",0
mov eax, 0xb
mov ebx, program
mov ecx, program
mov edx, 0
int 0x80
Thank you!
The arguments that you're passing to execve are wrong. Both the second and third must be an array of char pointers with a NULL sentinel value, not a single pointer.
In other words, something like:
#include <unistd.h>
int main (void) {
char * const argv[] = {"/bin/ls", NULL};
char * const envp[] = {NULL};
int rc = execve ("/bin/ls", argv, envp);
return rc;
}
When I run that, I do indeed get a list of the files in the current directory.
From the man pages,
int execve(const char *filename, char *const argv[], char *const envp[]);
So the problem in your case is that you haven't passed the 2nd and the 3rd argument correctly.
/* execve.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main(int argc, char *argv[])
{
char *newargv[] = { NULL, "hello", "world", NULL };
char *newenviron[] = { NULL };
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);
}
//This is a over-simplified version of the example in the man page
Run this as:
$ cc execve.c -o execve
$ ./execve ls
Try reading man execve again. You are passing the wrong arguments to it. Pay particular attention to what the second argument should be.
Also, running your program under strace could be illuminating.