Skip to content

Commit a3a4369

Browse files
committed
[ASAN] Fix validation size for dirent on FreeBSD
Typically the size required to represent a dirent is stored in `d_reclen`. But this not always the case for FreeBSD (for example, when walking a directory over NFS). This leads to ASAN false positives for `scandir` and similar functions. Because ASAN uses `d_reclen` for the range to validate, it can overrun when `d_reclen` is incorrect (too large). This change adds `__sanitizer_dirsiz` which fixes the dirent size calculation for FreeBSD. Other platforms continue to use `d_reclen`. Reviewed By: vitalybuka Differential Revision: https://reviews.llvm.org/D151583
1 parent 2878282 commit a3a4369

File tree

5 files changed

+56
-13
lines changed

5 files changed

+56
-13
lines changed

compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors.inc

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3312,7 +3312,8 @@ INTERCEPTOR(__sanitizer_dirent *, readdir, void *dirp) {
33123312
// its metadata. See
33133313
// https://github.com/google/sanitizers/issues/321.
33143314
__sanitizer_dirent *res = REAL(readdir)(dirp);
3315-
if (res) COMMON_INTERCEPTOR_WRITE_RANGE(ctx, res, res->d_reclen);
3315+
if (res)
3316+
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, res, __sanitizer_dirsiz(res));
33163317
return res;
33173318
}
33183319

@@ -3327,7 +3328,7 @@ INTERCEPTOR(int, readdir_r, void *dirp, __sanitizer_dirent *entry,
33273328
if (!res) {
33283329
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, result, sizeof(*result));
33293330
if (*result)
3330-
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, *result, (*result)->d_reclen);
3331+
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, *result, __sanitizer_dirsiz(*result));
33313332
}
33323333
return res;
33333334
}
@@ -3348,7 +3349,8 @@ INTERCEPTOR(__sanitizer_dirent64 *, readdir64, void *dirp) {
33483349
// its metadata. See
33493350
// https://github.com/google/sanitizers/issues/321.
33503351
__sanitizer_dirent64 *res = REAL(readdir64)(dirp);
3351-
if (res) COMMON_INTERCEPTOR_WRITE_RANGE(ctx, res, res->d_reclen);
3352+
if (res)
3353+
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, res, __sanitizer_dirsiz(res));
33523354
return res;
33533355
}
33543356

@@ -3363,7 +3365,7 @@ INTERCEPTOR(int, readdir64_r, void *dirp, __sanitizer_dirent64 *entry,
33633365
if (!res) {
33643366
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, result, sizeof(*result));
33653367
if (*result)
3366-
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, *result, (*result)->d_reclen);
3368+
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, *result, __sanitizer_dirsiz(*result));
33673369
}
33683370
return res;
33693371
}
@@ -3935,17 +3937,17 @@ static THREADLOCAL scandir_compar_f scandir_compar;
39353937

39363938
static int wrapped_scandir_filter(const struct __sanitizer_dirent *dir) {
39373939
COMMON_INTERCEPTOR_UNPOISON_PARAM(1);
3938-
COMMON_INTERCEPTOR_INITIALIZE_RANGE(dir, dir->d_reclen);
3940+
COMMON_INTERCEPTOR_INITIALIZE_RANGE(dir, __sanitizer_dirsiz(dir));
39393941
return scandir_filter(dir);
39403942
}
39413943

39423944
static int wrapped_scandir_compar(const struct __sanitizer_dirent **a,
39433945
const struct __sanitizer_dirent **b) {
39443946
COMMON_INTERCEPTOR_UNPOISON_PARAM(2);
39453947
COMMON_INTERCEPTOR_INITIALIZE_RANGE(a, sizeof(*a));
3946-
COMMON_INTERCEPTOR_INITIALIZE_RANGE(*a, (*a)->d_reclen);
3948+
COMMON_INTERCEPTOR_INITIALIZE_RANGE(*a, __sanitizer_dirsiz(*a));
39473949
COMMON_INTERCEPTOR_INITIALIZE_RANGE(b, sizeof(*b));
3948-
COMMON_INTERCEPTOR_INITIALIZE_RANGE(*b, (*b)->d_reclen);
3950+
COMMON_INTERCEPTOR_INITIALIZE_RANGE(*b, __sanitizer_dirsiz(*b));
39493951
return scandir_compar(a, b);
39503952
}
39513953

@@ -3969,7 +3971,7 @@ INTERCEPTOR(int, scandir, char *dirp, __sanitizer_dirent ***namelist,
39693971
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, *namelist, sizeof(**namelist) * res);
39703972
for (int i = 0; i < res; ++i)
39713973
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, (*namelist)[i],
3972-
(*namelist)[i]->d_reclen);
3974+
__sanitizer_dirsiz((*namelist)[i]));
39733975
}
39743976
return res;
39753977
}
@@ -3988,17 +3990,17 @@ static THREADLOCAL scandir64_compar_f scandir64_compar;
39883990

39893991
static int wrapped_scandir64_filter(const struct __sanitizer_dirent64 *dir) {
39903992
COMMON_INTERCEPTOR_UNPOISON_PARAM(1);
3991-
COMMON_INTERCEPTOR_INITIALIZE_RANGE(dir, dir->d_reclen);
3993+
COMMON_INTERCEPTOR_INITIALIZE_RANGE(dir, __sanitizer_dirsiz(dir));
39923994
return scandir64_filter(dir);
39933995
}
39943996

39953997
static int wrapped_scandir64_compar(const struct __sanitizer_dirent64 **a,
39963998
const struct __sanitizer_dirent64 **b) {
39973999
COMMON_INTERCEPTOR_UNPOISON_PARAM(2);
39984000
COMMON_INTERCEPTOR_INITIALIZE_RANGE(a, sizeof(*a));
3999-
COMMON_INTERCEPTOR_INITIALIZE_RANGE(*a, (*a)->d_reclen);
4001+
COMMON_INTERCEPTOR_INITIALIZE_RANGE(*a, __sanitizer_dirsiz(*a));
40004002
COMMON_INTERCEPTOR_INITIALIZE_RANGE(b, sizeof(*b));
4001-
COMMON_INTERCEPTOR_INITIALIZE_RANGE(*b, (*b)->d_reclen);
4003+
COMMON_INTERCEPTOR_INITIALIZE_RANGE(*b, __sanitizer_dirsiz(*b));
40024004
return scandir64_compar(a, b);
40034005
}
40044006

@@ -4023,7 +4025,7 @@ INTERCEPTOR(int, scandir64, char *dirp, __sanitizer_dirent64 ***namelist,
40234025
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, *namelist, sizeof(**namelist) * res);
40244026
for (int i = 0; i < res; ++i)
40254027
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, (*namelist)[i],
4026-
(*namelist)[i]->d_reclen);
4028+
__sanitizer_dirsiz((*namelist)[i]));
40274029
}
40284030
return res;
40294031
}

compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_freebsd.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ uptr __sanitizer_in_addr_sz(int af) {
173173
return 0;
174174
}
175175

176+
// For FreeBSD the actual size of a directory entry is not always in d_reclen.
177+
// Use the appropriate macro to get the correct size for all cases (e.g. NFS).
178+
u16 __sanitizer_dirsiz(const __sanitizer_dirent *dp) {
179+
return _GENERIC_DIRSIZ(dp);
180+
}
181+
176182
unsigned struct_ElfW_Phdr_sz = sizeof(Elf_Phdr);
177183
int glob_nomatch = GLOB_NOMATCH;
178184
int glob_altdirfunc = GLOB_ALTDIRFUNC;

compiler-rt/lib/sanitizer_common/sanitizer_platform_limits_freebsd.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,15 @@ struct __sanitizer_dirent {
249249
unsigned int d_fileno;
250250
# endif
251251
unsigned short d_reclen;
252-
// more fields that we don't care about
252+
u8 d_type;
253+
u8 d_pad0;
254+
u16 d_namlen;
255+
u16 d_pad1;
256+
char d_name[256];
253257
};
254258

259+
u16 __sanitizer_dirsiz(const __sanitizer_dirent *dp);
260+
255261
// 'clock_t' is 32 bits wide on x64 FreeBSD
256262
typedef int __sanitizer_clock_t;
257263
typedef int __sanitizer_clockid_t;

compiler-rt/lib/sanitizer_common/sanitizer_posix.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ int GetNamedMappingFd(const char *name, uptr size, int *flags);
120120
// alive at least as long as the mapping exists.
121121
void DecorateMapping(uptr addr, uptr size, const char *name);
122122

123+
# if !SANITIZER_FREEBSD
124+
# define __sanitizer_dirsiz(dp) ((dp)->d_reclen)
125+
# endif
123126

124127
} // namespace __sanitizer
125128

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// REQUIRES (linux && !android) || freebsd
2+
3+
// RUN: rm -rf %t-dir
4+
// RUN: mkdir -p %t-dir
5+
// RUN: touch %t-dir/a %t-dir/b %t-dir/c
6+
7+
// RUN: %clang %s -DTEMP_DIR='"'"%t-dir"'"' -o %t && %run %t 2>&1
8+
9+
#include <dirent.h>
10+
#include <stdio.h>
11+
#include <stdlib.h>
12+
#include <string.h>
13+
14+
int main(int argc, char **argv) {
15+
struct dirent **dirpp = NULL;
16+
int count = scandir(TEMP_DIR, &dirpp, NULL, NULL);
17+
fprintf(stderr, "count is %d\n", count);
18+
if (count >= 0) {
19+
for (int i = 0; i < count; ++i) {
20+
fprintf(stderr, "found %s\n", dirpp[i]->d_name);
21+
free(dirpp[i]);
22+
}
23+
free(dirpp);
24+
}
25+
return 0;
26+
}

0 commit comments

Comments
 (0)