os-wrappers-test: Make syscall intercepts work with sanitizers
authorFergus Dall <sidereal@google.com>
Fri, 9 Jul 2021 14:13:35 +0000 (00:13 +1000)
committerDaniel Stone <daniels@collabora.com>
Thu, 22 Jul 2021 22:27:45 +0000 (22:27 +0000)
Sanitizers need to intercept syscalls in the compiler run-time library, as
do these tests. We try to make this work by using dlsym(RTLD_NEXT) to find
the next definition in the chain, but here this approach won't work because
the compiler run-time library is linked into the same elf object as the test
interceptors are.

The sanitizer library supports this by giving the intercept functions a
prefix and making them only weakly alias the real names, so our interceptors
can call the sanitizers interceptors explicitly, which will then use dlsym
to call the real function.

By making our declarations of the sanitizer interceptor function weak we can
handle any combination of intercepts (including none, if there is no
sanitizer). If our declaration is resolves to a NULL pointer, we just use
dlsym.

Signed-off-by: Fergus Dall <sidereal@google.com>
tests/os-wrappers-test.c

index 4d5de31..0fd7853 100644 (file)
 
 static int fall_back;
 
-static int (*real_socket)(int, int, int);
-static int wrapped_calls_socket;
+/* Play nice with sanitizers
+ *
+ * Sanitizers need to intercept syscalls in the compiler run-time library. As
+ * this isn't a separate ELF object, the usual dlsym(RTLD_NEXT) approach won't
+ * work: there can only be one function named "socket" etc. To support this, the
+ * sanitizer library names its interceptors with the prefix __interceptor_ ("__"
+ * being reserved for the implementation) and then weakly aliases it to the real
+ * function. The functions we define below will override the weak alias, and we
+ * can call them by the __interceptor_ name directly. This allows the sanitizer
+ * to do its work before calling the next version of the function via dlsym.
+ *
+ * However! We also don't know which of these functions the sanitizer actually
+ * wants to override, so we have to declare our own weak symbols for
+ * __interceptor_ and check at run time if they linked to anything or not.
+*/
 
-static int (*real_fcntl)(int, int, ...);
-static int wrapped_calls_fcntl;
+#define DECL(ret_type, func, ...) \
+       ret_type __interceptor_ ## func(__VA_ARGS__) __attribute__((weak)); \
+       static ret_type (*real_ ## func)(__VA_ARGS__);                  \
+       static int wrapped_calls_ ## func;
 
-static ssize_t (*real_recvmsg)(int, struct msghdr *, int);
-static int wrapped_calls_recvmsg;
+#define REAL(func) (__interceptor_ ## func) ?                          \
+       __interceptor_ ## func :                                        \
+       (typeof(&__interceptor_ ## func))dlsym(RTLD_NEXT, #func)
 
-static int (*real_epoll_create1)(int);
-static int wrapped_calls_epoll_create1;
+DECL(int, socket, int, int, int);
+DECL(int, fcntl, int, int, ...);
+DECL(ssize_t, recvmsg, int, struct msghdr *, int);
+DECL(int, epoll_create1, int);
 
 static void
 init_fallbacks(int do_fallbacks)
 {
        fall_back = do_fallbacks;
-       real_socket = dlsym(RTLD_NEXT, "socket");
-       real_fcntl = dlsym(RTLD_NEXT, "fcntl");
-       real_recvmsg = dlsym(RTLD_NEXT, "recvmsg");
-       real_epoll_create1 = dlsym(RTLD_NEXT, "epoll_create1");
+       real_socket = REAL(socket);
+       real_fcntl = REAL(fcntl);
+       real_recvmsg = REAL(recvmsg);
+       real_epoll_create1 = REAL(epoll_create1);
 }
 
 __attribute__ ((visibility("default"))) int