From cc6b86e175da1cf4fa7de98ee99bb9e55e16ac7d Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Wed, 8 Mar 2023 15:53:33 +0100 Subject: [PATCH] [sanitizer] Intercept glibc's argp_parse() Glibc provides the argp_parse() function for parsing command line arguments [1]. Indicate that argc/argv are read from and arg_index is written to. Strictly speaking, we also need to indicate that argp is read from, but this would require describing its layout, and most people use a static initializer there, so it's not worth the effort. [1] https://www.gnu.org/software/libc/manual/html_node/Argp.html Reviewed By: vitalybuka Differential Revision: https://reviews.llvm.org/D143330 --- .../sanitizer_common_interceptors.inc | 20 ++++++++ .../sanitizer_platform_interceptors.h | 1 + .../sanitizer_common/TestCases/Linux/argp_parse.c | 60 ++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 compiler-rt/test/sanitizer_common/TestCases/Linux/argp_parse.c diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc index 9fc5623..51703a3 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc +++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc @@ -10375,6 +10375,25 @@ INTERCEPTOR(void, hexdump, const void *ptr, int length, const char *header, int #define INIT_HEXDUMP #endif +#if SANITIZER_INTERCEPT_ARGP_PARSE +INTERCEPTOR(int, argp_parse, const struct argp *argp, int argc, char **argv, + unsigned flags, int *arg_index, void *input) { + void *ctx; + COMMON_INTERCEPTOR_ENTER(ctx, argp_parse, argp, argc, argv, flags, arg_index, + input); + for (int i = 0; i < argc; i++) + COMMON_INTERCEPTOR_READ_RANGE(ctx, argv[i], internal_strlen(argv[i]) + 1); + int res = REAL(argp_parse)(argp, argc, argv, flags, arg_index, input); + if (!res && arg_index) + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, arg_index, sizeof(int)); + return res; +} + +#define INIT_ARGP_PARSE COMMON_INTERCEPT_FUNCTION(argp_parse); +#else +#define INIT_ARGP_PARSE +#endif + #include "sanitizer_common_interceptors_netbsd_compat.inc" static void InitializeCommonInterceptors() { @@ -10694,6 +10713,7 @@ static void InitializeCommonInterceptors() { INIT_UNAME; INIT___XUNAME; INIT_HEXDUMP; + INIT_ARGP_PARSE; INIT___PRINTF_CHK; } diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h index 814ff46..eb39fab 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h @@ -592,6 +592,7 @@ #define SANITIZER_INTERCEPT_FLOPEN SI_FREEBSD #define SANITIZER_INTERCEPT_PROCCTL SI_FREEBSD #define SANITIZER_INTERCEPT_HEXDUMP SI_FREEBSD +#define SANITIZER_INTERCEPT_ARGP_PARSE SI_GLIBC // This macro gives a way for downstream users to override the above // interceptor macros irrespective of the platform they are on. They have diff --git a/compiler-rt/test/sanitizer_common/TestCases/Linux/argp_parse.c b/compiler-rt/test/sanitizer_common/TestCases/Linux/argp_parse.c new file mode 100644 index 0000000..ec78f27 --- /dev/null +++ b/compiler-rt/test/sanitizer_common/TestCases/Linux/argp_parse.c @@ -0,0 +1,60 @@ +// RUN: %clang %s -o %t && %run %t -o baz + +// argp_parse is glibc specific. +// UNSUPPORTED: android, target={{.*(freebsd|netbsd).*}} + +#include +#include +#include +#include + +struct test { + const char *option_value; +}; + +static const struct argp_option options[] = { + {"option", 'o', "OPTION", 0, "Option", 0}, + {NULL, 0, NULL, 0, NULL, 0}, +}; + +static error_t parser(int key, char *arg, struct argp_state *state) { + if (key == 'o') { + ((struct test *)(state->input))->option_value = arg; + return 0; + } + return ARGP_ERR_UNKNOWN; +} + +static struct argp argp = {.options = options, .parser = parser}; + +void test_nulls(char *argv0) { + char *argv[] = {argv0, NULL}; + int res = argp_parse(NULL, 1, argv, 0, NULL, NULL); + assert(res == 0); +} + +void test_synthetic(char *argv0) { + char *argv[] = {argv0, "-o", "foo", "bar", NULL}; + struct test t = {NULL}; + int arg_index; + int res = argp_parse(&argp, 4, argv, 0, &arg_index, &t); + assert(res == 0); + assert(arg_index == 3); + assert(strcmp(t.option_value, "foo") == 0); +} + +void test_real(int argc, char **argv) { + struct test t = {NULL}; + int arg_index; + int res = argp_parse(&argp, argc, argv, 0, &arg_index, &t); + assert(res == 0); + assert(arg_index == 3); + assert(strcmp(t.option_value, "baz") == 0); +} + +int main(int argc, char **argv) { + test_nulls(argv[0]); + test_synthetic(argv[0]); + test_real(argc, argv); + return EXIT_SUCCESS; +} -- 2.7.4