diff --git a/compiler-rt/lib/rtsan/rtsan_interceptors_posix.cpp b/compiler-rt/lib/rtsan/rtsan_interceptors_posix.cpp index 4262039e8e1fa..4f7b534ee17a8 100644 --- a/compiler-rt/lib/rtsan/rtsan_interceptors_posix.cpp +++ b/compiler-rt/lib/rtsan/rtsan_interceptors_posix.cpp @@ -815,6 +815,22 @@ INTERCEPTOR(int, mkfifo, const char *pathname, mode_t mode) { return REAL(mkfifo)(pathname, mode); } +INTERCEPTOR(pid_t, fork, void) { + __rtsan_notify_intercepted_call("fork"); + return REAL(fork)(); +} + +INTERCEPTOR(int, execve, const char *filename, char *const argv[], + char *const envp[]) { + __rtsan_notify_intercepted_call("execve"); + return REAL(execve)(filename, argv, envp); +} + +// TODO: the `wait` family of functions is an oddity. In testing, if you +// intercept them, Darwin seemingly ignores them, and linux never returns from +// the test. Revisit this in the future, but hopefully intercepting fork/exec is +// enough to dissuade usage of wait by proxy. + #if SANITIZER_APPLE #define INT_TYPE_SYSCALL int #else @@ -956,6 +972,9 @@ void __rtsan::InitializeInterceptors() { INTERCEPT_FUNCTION(pipe); INTERCEPT_FUNCTION(mkfifo); + INTERCEPT_FUNCTION(fork); + INTERCEPT_FUNCTION(execve); + INTERCEPT_FUNCTION(syscall); } diff --git a/compiler-rt/test/rtsan/fork_exec.cpp b/compiler-rt/test/rtsan/fork_exec.cpp new file mode 100644 index 0000000000000..3b2d2e5ca2f5d --- /dev/null +++ b/compiler-rt/test/rtsan/fork_exec.cpp @@ -0,0 +1,59 @@ +// RUN: %clangxx -fsanitize=realtime -DIS_NONBLOCKING=1 %s -o %t +// RUN: %env_rtsan_opts="halt_on_error=true" not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-HALT +// RUN: %env_rtsan_opts="halt_on_error=false" %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-NOHALT + +// RUN: %clangxx -fsanitize=realtime -DIS_NONBLOCKING=0 %s -o %t +// RUN: %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-OK +// RUN: %env_rtsan_opts="halt_on_error=false" %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-OK + +// UNSUPPORTED: ios + +// Intent: Ensure fork/exec dies when realtime and survives otherwise +// This behavior is difficult to test in a gtest, because the process is +// wiped away with exec. + +#include +#include +#include +#include + +#if IS_NONBLOCKING +# define MAYBE_NONBLOCKING [[clang::nonblocking]] +#else +# define MAYBE_NONBLOCKING +#endif + +int main() MAYBE_NONBLOCKING { + const pid_t pid = fork(); + + if (pid == 0) { + char *args[] = {"/bin/ls", nullptr}; + execve(args[0], args, nullptr); + perror("execve failed"); + return 1; + } else if (pid > 0) { + int status; + waitpid(pid, &status, 0); + usleep(1); + } else { + perror("fork failed"); + return 1; + } + + printf("fork/exec succeeded\n"); + return 0; +} + +// CHECK-NOHALT: Intercepted call to {{.*}} `fork` {{.*}} +// CHECK-NOHALT: Intercepted call to {{.*}} `execve` {{.*}} + +// usleep checks that rtsan is still enabled in the parent process +// See note in our interceptors file for why we don't look for `wait` +// CHECK-NOHALT: Intercepted call to {{.*}} `usleep` {{.*}} + +// CHECK-NOHALT: fork/exec succeeded + +// CHECK-HALT: ==ERROR: RealtimeSanitizer: unsafe-library-call +// CHECK-HALT-NEXT: Intercepted call to {{.*}} `fork` {{.*}} + +// CHECK-OK: fork/exec succeeded