[sanitizer] Intercept glibc's argp_parse()
authorIlya Leoshkevich <iii@linux.ibm.com>
Wed, 8 Mar 2023 14:53:33 +0000 (15:53 +0100)
committerIlya Leoshkevich <iii@linux.ibm.com>
Wed, 8 Mar 2023 15:08:19 +0000 (16:08 +0100)
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

compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc
compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
compiler-rt/test/sanitizer_common/TestCases/Linux/argp_parse.c [new file with mode: 0644]

index 9fc5623..51703a3 100644 (file)
@@ -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;
 }
index 814ff46..eb39fab 100644 (file)
 #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 (file)
index 0000000..ec78f27
--- /dev/null
@@ -0,0 +1,60 @@
+// RUN: %clang %s -o %t && %run %t -o baz
+
+// argp_parse is glibc specific.
+// UNSUPPORTED: android, target={{.*(freebsd|netbsd).*}}
+
+#include <argp.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+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;
+}