diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc --- a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc +++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc @@ -5759,12 +5759,13 @@ COMMON_INTERCEPTOR_ENTER(ctx, capget, hdrp, datap); if (hdrp) COMMON_INTERCEPTOR_READ_RANGE(ctx, hdrp, __user_cap_header_struct_sz); + unsigned datasz = __user_cap_data_struct_sz(hdrp); // FIXME: under ASan the call below may write to freed memory and corrupt // its metadata. See // https://github.com/google/sanitizers/issues/321. int res = REAL(capget)(hdrp, datap); if (res == 0 && datap) - COMMON_INTERCEPTOR_WRITE_RANGE(ctx, datap, __user_cap_data_struct_sz); + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, datap, datasz); // We can also return -1 and write to hdrp->version if the version passed in // hdrp->version is unsupported. But that's not a trivial condition to check, // and anyway COMMON_INTERCEPTOR_READ_RANGE protects us to some extent. @@ -5775,8 +5776,9 @@ COMMON_INTERCEPTOR_ENTER(ctx, capset, hdrp, datap); if (hdrp) COMMON_INTERCEPTOR_READ_RANGE(ctx, hdrp, __user_cap_header_struct_sz); + unsigned datasz = __user_cap_data_struct_sz(hdrp); if (datap) - COMMON_INTERCEPTOR_READ_RANGE(ctx, datap, __user_cap_data_struct_sz); + COMMON_INTERCEPTOR_READ_RANGE(ctx, datap, datasz); return REAL(capset)(hdrp, datap); } #define INIT_CAPGET \ diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common_syscalls.inc b/compiler-rt/lib/sanitizer_common/sanitizer_common_syscalls.inc --- a/compiler-rt/lib/sanitizer_common/sanitizer_common_syscalls.inc +++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_syscalls.inc @@ -417,14 +417,14 @@ POST_SYSCALL(capget)(long res, void *header, void *dataptr) { if (res >= 0) if (dataptr) - POST_WRITE(dataptr, __user_cap_data_struct_sz); + POST_WRITE(dataptr, __user_cap_data_struct_sz(header)); } PRE_SYSCALL(capset)(void *header, const void *data) { if (header) PRE_READ(header, __user_cap_header_struct_sz); if (data) - PRE_READ(data, __user_cap_data_struct_sz); + PRE_READ(data, __user_cap_data_struct_sz(header)); } POST_SYSCALL(capset)(long res, void *header, const void *data) {} diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.h b/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.h --- a/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.h @@ -135,7 +135,7 @@ extern unsigned struct_epoll_event_sz; extern unsigned struct_sysinfo_sz; extern unsigned __user_cap_header_struct_sz; -extern unsigned __user_cap_data_struct_sz; +extern unsigned __user_cap_data_struct_sz(void *hdrp); extern unsigned struct_new_utsname_sz; extern unsigned struct_old_utsname_sz; extern unsigned struct_oldold_utsname_sz; diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.cpp --- a/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_posix.cpp @@ -248,7 +248,23 @@ unsigned struct_sysinfo_sz = sizeof(struct sysinfo); unsigned __user_cap_header_struct_sz = sizeof(struct __user_cap_header_struct); - unsigned __user_cap_data_struct_sz = sizeof(struct __user_cap_data_struct); + unsigned __user_cap_data_struct_sz(void *hdrp) { + int u32s = 0; + if (hdrp) { + switch (((struct __user_cap_header_struct *)hdrp)->version) { + case _LINUX_CAPABILITY_VERSION_1: + u32s = _LINUX_CAPABILITY_U32S_1; + break; + case _LINUX_CAPABILITY_VERSION_2: + u32s = _LINUX_CAPABILITY_U32S_2; + break; + case _LINUX_CAPABILITY_VERSION_3: + u32s = _LINUX_CAPABILITY_U32S_3; + break; + } + } + return sizeof(struct __user_cap_data_struct) * u32s; + } unsigned struct_new_utsname_sz = sizeof(struct new_utsname); unsigned struct_old_utsname_sz = sizeof(struct old_utsname); unsigned struct_oldold_utsname_sz = sizeof(struct oldold_utsname); diff --git a/compiler-rt/test/sanitizer_common/TestCases/Linux/cap.c b/compiler-rt/test/sanitizer_common/TestCases/Linux/cap.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/sanitizer_common/TestCases/Linux/cap.c @@ -0,0 +1,48 @@ +// RUN: %clang %s -o %t && %run %t +// capget() and capset() are not intercepted on Android. +// UNSUPPORTED: android + +#include +#include +#include +#include +#include + +#include "sanitizer_common/sanitizer_specific.h" + +/* Use capget() and capset() from glibc. */ +int capget(cap_user_header_t header, cap_user_data_t data); +int capset(cap_user_header_t header, const cap_user_data_t data); + +static void test(int version, int u32s) { + struct __user_cap_header_struct hdr = { + .version = version, + .pid = 0, + }; + struct __user_cap_data_struct data[u32s]; + if (capget(&hdr, data)) { + assert(errno == EINVAL); + /* Check that memory is not touched. */ +#if __has_feature(memory_sanitizer) + assert(__msan_test_shadow(data, sizeof(data)) == 0); +#endif + hdr.version = version; + int err = capset(&hdr, data); + assert(errno == EINVAL); + } else { + for (int i = 0; i < u32s; i++) + printf("%x %x %x\n", data[i].effective, data[i].permitted, + data[i].inheritable); + int err = capset(&hdr, data); + assert(!err); + } +} + +int main() { + test(0, 1); /* Test an incorrect version. */ + test(_LINUX_CAPABILITY_VERSION_1, _LINUX_CAPABILITY_U32S_1); + test(_LINUX_CAPABILITY_VERSION_2, _LINUX_CAPABILITY_U32S_2); + test(_LINUX_CAPABILITY_VERSION_3, _LINUX_CAPABILITY_U32S_3); + + return EXIT_SUCCESS; +}