Skip to content

Commit 07861e9

Browse files
committed
[msan] Intercept qsort, qsort_r.
Summary: This fixes qsort-related false positives with glibc-2.27. I'm not entirely sure why they did not show up with the earlier versions; the code seems similar enough. Reviewers: vitalybuka Subscribers: #sanitizers, llvm-commits Tags: #sanitizers, #llvm Differential Revision: https://reviews.llvm.org/D71740
1 parent c148e2e commit 07861e9

File tree

3 files changed

+146
-0
lines changed

3 files changed

+146
-0
lines changed

compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9639,6 +9639,75 @@ INTERCEPTOR(int, getentropy, void *buf, SIZE_T buflen) {
96399639
#define INIT_GETENTROPY
96409640
#endif
96419641

9642+
#if SANITIZER_INTERCEPT_QSORT
9643+
// Glibc qsort uses a temporary buffer allocated either on stack or on heap.
9644+
// Poisoned memory from there may get copied into the comparator arguments,
9645+
// where it needs to be dealt with. But even that is not enough - the results of
9646+
// the sort may be copied into the input/output array based on the results of
9647+
// the comparator calls, but directly from the temp memory, bypassing the
9648+
// unpoisoning done in wrapped_qsort_compar. We deal with this by, again,
9649+
// unpoisoning the entire array after the sort is done.
9650+
//
9651+
// We can not check that the entire array is initialized at the beginning. IMHO,
9652+
// it's fine for parts of the sorted objects to contain uninitialized memory,
9653+
// ex. as padding in structs.
9654+
typedef int (*qsort_compar_f)(const void *, const void *);
9655+
static THREADLOCAL qsort_compar_f qsort_compar;
9656+
static THREADLOCAL SIZE_T qsort_size;
9657+
int wrapped_qsort_compar(const void *a, const void *b) {
9658+
COMMON_INTERCEPTOR_UNPOISON_PARAM(2);
9659+
COMMON_INTERCEPTOR_INITIALIZE_RANGE(a, qsort_size);
9660+
COMMON_INTERCEPTOR_INITIALIZE_RANGE(b, qsort_size);
9661+
return qsort_compar(a, b);
9662+
}
9663+
9664+
INTERCEPTOR(void, qsort, void *base, SIZE_T nmemb, SIZE_T size,
9665+
qsort_compar_f compar) {
9666+
void *ctx;
9667+
COMMON_INTERCEPTOR_ENTER(ctx, qsort, base, nmemb, size, compar);
9668+
qsort_compar_f old_compar = qsort_compar;
9669+
qsort_compar = compar;
9670+
SIZE_T old_size = qsort_size;
9671+
qsort_size = size;
9672+
REAL(qsort)(base, nmemb, size, wrapped_qsort_compar);
9673+
qsort_compar = old_compar;
9674+
qsort_size = old_size;
9675+
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, base, nmemb * size);
9676+
}
9677+
#define INIT_QSORT COMMON_INTERCEPT_FUNCTION(qsort)
9678+
#else
9679+
#define INIT_QSORT
9680+
#endif
9681+
9682+
#if SANITIZER_INTERCEPT_QSORT_R
9683+
typedef int (*qsort_r_compar_f)(const void *, const void *, void *);
9684+
static THREADLOCAL qsort_r_compar_f qsort_r_compar;
9685+
static THREADLOCAL SIZE_T qsort_r_size;
9686+
int wrapped_qsort_r_compar(const void *a, const void *b, void *arg) {
9687+
COMMON_INTERCEPTOR_UNPOISON_PARAM(3);
9688+
COMMON_INTERCEPTOR_INITIALIZE_RANGE(a, qsort_r_size);
9689+
COMMON_INTERCEPTOR_INITIALIZE_RANGE(b, qsort_r_size);
9690+
return qsort_r_compar(a, b, arg);
9691+
}
9692+
9693+
INTERCEPTOR(void, qsort_r, void *base, SIZE_T nmemb, SIZE_T size,
9694+
qsort_r_compar_f compar, void *arg) {
9695+
void *ctx;
9696+
COMMON_INTERCEPTOR_ENTER(ctx, qsort_r, base, nmemb, size, compar, arg);
9697+
qsort_r_compar_f old_compar = qsort_r_compar;
9698+
qsort_r_compar = compar;
9699+
SIZE_T old_size = qsort_r_size;
9700+
qsort_r_size = size;
9701+
REAL(qsort_r)(base, nmemb, size, wrapped_qsort_r_compar, arg);
9702+
qsort_r_compar = old_compar;
9703+
qsort_r_size = old_size;
9704+
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, base, nmemb * size);
9705+
}
9706+
#define INIT_QSORT_R COMMON_INTERCEPT_FUNCTION(qsort_r)
9707+
#else
9708+
#define INIT_QSORT_R
9709+
#endif
9710+
96429711
static void InitializeCommonInterceptors() {
96439712
#if SI_POSIX
96449713
static u64 metadata_mem[sizeof(MetadataHashMap) / sizeof(u64) + 1];
@@ -9940,6 +10009,8 @@ static void InitializeCommonInterceptors() {
994010009
INIT_CRYPT;
994110010
INIT_CRYPT_R;
994210011
INIT_GETENTROPY;
10012+
INIT_QSORT;
10013+
INIT_QSORT_R;
994310014

994410015
INIT___PRINTF_CHK;
994510016
}

compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,5 +575,7 @@
575575
#define SANITIZER_INTERCEPT_ATEXIT SI_NETBSD
576576
#define SANITIZER_INTERCEPT_PTHREAD_ATFORK SI_NETBSD
577577
#define SANITIZER_INTERCEPT_GETENTROPY SI_FREEBSD
578+
#define SANITIZER_INTERCEPT_QSORT SI_POSIX
579+
#define SANITIZER_INTERCEPT_QSORT_R (SI_LINUX && !SI_ANDROID)
578580

579581
#endif // #ifndef SANITIZER_PLATFORM_INTERCEPTORS_H

compiler-rt/test/msan/qsort.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// RUN: %clangxx_msan -O0 -g %s -o %t && %run %t
2+
3+
#include <assert.h>
4+
#include <errno.h>
5+
#include <glob.h>
6+
#include <stdio.h>
7+
#include <stdlib.h>
8+
#include <string.h>
9+
10+
#include <sanitizer/msan_interface.h>
11+
12+
constexpr size_t kSize1 = 27;
13+
constexpr size_t kSize2 = 7;
14+
15+
bool seen2;
16+
17+
void dummy(long a, long b, long c, long d, long e) {}
18+
19+
void poison_stack_and_param() {
20+
char x[10000];
21+
int y;
22+
dummy(y, y, y, y, y);
23+
}
24+
25+
__attribute__((always_inline)) int cmp(long a, long b) {
26+
if (a < b)
27+
return -1;
28+
else if (a > b)
29+
return 1;
30+
else
31+
return 0;
32+
}
33+
34+
int compar2(const void *a, const void *b) {
35+
assert(a);
36+
assert(b);
37+
__msan_check_mem_is_initialized(a, sizeof(long));
38+
__msan_check_mem_is_initialized(b, sizeof(long));
39+
seen2 = true;
40+
poison_stack_and_param();
41+
return cmp(*(long *)a, *(long *)b);
42+
}
43+
44+
int compar1(const void *a, const void *b) {
45+
assert(a);
46+
assert(b);
47+
__msan_check_mem_is_initialized(a, sizeof(long));
48+
__msan_check_mem_is_initialized(b, sizeof(long));
49+
50+
long *p = new long[kSize2];
51+
// kind of random
52+
for (int i = 0; i < kSize2; ++i)
53+
p[i] = i * 2 + (i % 3 - 1) * 3;
54+
qsort(p, kSize1, sizeof(long), compar2);
55+
__msan_check_mem_is_initialized(p, sizeof(long) * kSize2);
56+
delete[] p;
57+
58+
poison_stack_and_param();
59+
return cmp(*(long *)a, *(long *)b);
60+
}
61+
62+
int main(int argc, char *argv[]) {
63+
long *p = new long[kSize1];
64+
// kind of random
65+
for (int i = 0; i < kSize1; ++i)
66+
p[i] = i * 2 + (i % 3 - 1) * 3;
67+
poison_stack_and_param();
68+
qsort(p, kSize1, sizeof(long), compar1);
69+
__msan_check_mem_is_initialized(p, sizeof(long) * kSize1);
70+
assert(seen2);
71+
delete[] p;
72+
return 0;
73+
}

0 commit comments

Comments
 (0)