Index: lib/asan/lit_tests/TestCases/printf-1.c =================================================================== --- lib/asan/lit_tests/TestCases/printf-1.c +++ lib/asan/lit_tests/TestCases/printf-1.c @@ -0,0 +1,16 @@ +// RUN: %clang_asan -O2 %s -o %t +// RUN: ASAN_OPTIONS=check_printf=1 %t 2>&1 | FileCheck %s +// RUN: ASAN_OPTIONS=check_printf=0 %t 2>&1 | FileCheck %s +// RUN: ASAN_OPTIONS="" %t 2>&1 | FileCheck %s + +#include +int main() { + volatile char c = '0'; + volatile int x = 12; + volatile float f = 1.239; + volatile char s[] = "34"; + printf("%c %d %.3f %s\n", c, x, f, s); + return 0; + // Check that printf works fine under Asan. + // CHECK: 0 12 1.239 34 +} Index: lib/asan/lit_tests/TestCases/printf-2.c =================================================================== --- lib/asan/lit_tests/TestCases/printf-2.c +++ lib/asan/lit_tests/TestCases/printf-2.c @@ -0,0 +1,22 @@ +// RUN: %clang_asan -O2 %s -o %t +// RUN: ASAN_OPTIONS=check_printf=1 not %t 2>&1 | FileCheck --check-prefix=CHECK-ON %s +// RUN: ASAN_OPTIONS=check_printf=0 %t 2>&1 | FileCheck --check-prefix=CHECK-OFF %s +// RUN: ASAN_OPTIONS="" %t 2>&1 | FileCheck --check-prefix=CHECK-OFF %s + +#include +#include +#include +int main() { + volatile char c = '0'; + volatile int x = 12; + volatile float f = 1.239; + volatile char s[] = "34"; + char *p = strdup((const char *)s); + free(p); + printf("%c %d %.3f %s\n", c, x, f, p); + return 0; + // Check that %s is sanitized. + // CHECK-ON: heap-use-after-free + // CHECK-ON-NOT: 0 12 1.239 34 + // CHECK-OFF: 0 12 1.239 +} Index: lib/asan/lit_tests/TestCases/printf-3.c =================================================================== --- lib/asan/lit_tests/TestCases/printf-3.c +++ lib/asan/lit_tests/TestCases/printf-3.c @@ -0,0 +1,19 @@ +// RUN: %clang_asan -O2 %s -o %t +// RUN: ASAN_OPTIONS=check_printf=1 not %t 2>&1 | FileCheck --check-prefix=CHECK-ON %s +// RUN: ASAN_OPTIONS=check_printf=0 %t 2>&1 | FileCheck --check-prefix=CHECK-OFF %s +// RUN: ASAN_OPTIONS="" %t 2>&1 | FileCheck --check-prefix=CHECK-OFF %s + +#include +int main() { + volatile char c = '0'; + volatile int x = 12; + volatile float f = 1.239; + volatile char s[] = "34"; + volatile int n[1]; + printf("%c %d %.3f %s%n\n", c, x, f, s, &n[1]); + return 0; + // Check that %n is sanitized. + // CHECK-ON: stack-buffer-overflow + // CHECK-ON-NOT: 0 12 1.239 34 + // CHECK-OFF: 0 12 1.239 34 +} Index: lib/asan/lit_tests/TestCases/printf-4.c =================================================================== --- lib/asan/lit_tests/TestCases/printf-4.c +++ lib/asan/lit_tests/TestCases/printf-4.c @@ -0,0 +1,20 @@ +// RUN: %clang_asan -O2 %s -o %t +// RUN: ASAN_OPTIONS=check_printf=1 not %t 2>&1 | FileCheck --check-prefix=CHECK-ON %s +// RUN: ASAN_OPTIONS=check_printf=0 %t 2>&1 | FileCheck --check-prefix=CHECK-OFF %s +// RUN: ASAN_OPTIONS="" %t 2>&1 | FileCheck --check-prefix=CHECK-OFF %s + +#include +int main() { + volatile char c = '0'; + volatile int x = 12; + volatile float f = 1.239; + volatile char s[] = "34"; + volatile char buf[2]; + sprintf((char *)buf, "%c %d %.3f %s\n", c, x, f, s); + puts((const char *)buf); + return 0; + // Check that size of output buffer is sanitized. + // CHECK-ON: stack-buffer-overflow + // CHECK-ON-NOT: 0 12 1.239 34 + // CHECK-OFF: 0 12 1.239 34 +} Index: lib/asan/lit_tests/TestCases/printf-5.c =================================================================== --- lib/asan/lit_tests/TestCases/printf-5.c +++ lib/asan/lit_tests/TestCases/printf-5.c @@ -0,0 +1,21 @@ +// RUN: %clang_asan -O2 %s -o %t +// RUN: ASAN_OPTIONS=check_printf=1 not %t 2>&1 | FileCheck --check-prefix=CHECK-ON %s +// RUN: ASAN_OPTIONS=check_printf=0 %t 2>&1 | FileCheck --check-prefix=CHECK-OFF %s +// RUN: ASAN_OPTIONS="" %t 2>&1 | FileCheck --check-prefix=CHECK-OFF %s + +#include +#include +int main() { + volatile char c = '0'; + volatile int x = 12; + volatile float f = 1.239; + volatile char s[] = "34"; + volatile char fmt[2]; + memcpy((char *)fmt, "%c %d %f %s\n", sizeof(fmt)); + printf(fmt, c, x, f, s); + return 0; + // Check that format string is sanitized. + // CHECK-ON: stack-buffer-overflow + // CHECK-ON-NOT: 0 12 1.239 34 + // CHECK-OFF: 0 +} Index: lib/msan/msan_interceptors.cc =================================================================== --- lib/msan/msan_interceptors.cc +++ lib/msan/msan_interceptors.cc @@ -1291,6 +1291,8 @@ } while (false) // FIXME #define COMMON_INTERCEPTOR_BLOCK_REAL(name) REAL(name) #define COMMON_INTERCEPTOR_ON_EXIT(ctx) OnExit() +// FIXME: update Msan to use common printf interceptors +#define SANITIZER_INTERCEPT_PRINTF 0 #include "sanitizer_common/sanitizer_common_interceptors.inc" #define COMMON_SYSCALL_PRE_READ_RANGE(p, s) CHECK_UNPOISONED(p, s) Index: lib/sanitizer_common/sanitizer_common_interceptors.inc =================================================================== --- lib/sanitizer_common/sanitizer_common_interceptors.inc +++ lib/sanitizer_common/sanitizer_common_interceptors.inc @@ -569,9 +569,23 @@ #define INIT_STRPTIME #endif -#if SANITIZER_INTERCEPT_SCANF +#if SANITIZER_INTERCEPT_SCANF || SANITIZER_INTERCEPT_PRINTF +# include "sanitizer_common_interceptors_scanf.inc" -#include "sanitizer_common_interceptors_scanf.inc" +#define FORMAT_INTERCEPTOR_IMPL(name, vname, ...) \ + { \ + void *ctx; \ + va_list ap; \ + va_start(ap, format); \ + COMMON_INTERCEPTOR_ENTER(ctx, vname, __VA_ARGS__, ap); \ + int res = vname(__VA_ARGS__, ap); \ + va_end(ap); \ + return res; \ + } + +#endif + +#if SANITIZER_INTERCEPT_SCANF #define VSCANF_INTERCEPTOR_IMPL(vname, allowGnuMalloc, ...) \ { \ @@ -607,35 +621,24 @@ VSCANF_INTERCEPTOR_IMPL(__isoc99_vfscanf, false, stream, format, ap) #endif // SANITIZER_INTERCEPT_ISOC99_SCANF -#define SCANF_INTERCEPTOR_IMPL(name, vname, ...) \ - { \ - void *ctx; \ - va_list ap; \ - va_start(ap, format); \ - COMMON_INTERCEPTOR_ENTER(ctx, vname, __VA_ARGS__, ap); \ - int res = vname(__VA_ARGS__, ap); \ - va_end(ap); \ - return res; \ - } - INTERCEPTOR(int, scanf, const char *format, ...) -SCANF_INTERCEPTOR_IMPL(scanf, vscanf, format) +FORMAT_INTERCEPTOR_IMPL(scanf, vscanf, format) INTERCEPTOR(int, fscanf, void *stream, const char *format, ...) -SCANF_INTERCEPTOR_IMPL(fscanf, vfscanf, stream, format) +FORMAT_INTERCEPTOR_IMPL(fscanf, vfscanf, stream, format) INTERCEPTOR(int, sscanf, const char *str, const char *format, ...) -SCANF_INTERCEPTOR_IMPL(sscanf, vsscanf, str, format) +FORMAT_INTERCEPTOR_IMPL(sscanf, vsscanf, str, format) #if SANITIZER_INTERCEPT_ISOC99_SCANF INTERCEPTOR(int, __isoc99_scanf, const char *format, ...) -SCANF_INTERCEPTOR_IMPL(__isoc99_scanf, __isoc99_vscanf, format) +FORMAT_INTERCEPTOR_IMPL(__isoc99_scanf, __isoc99_vscanf, format) INTERCEPTOR(int, __isoc99_fscanf, void *stream, const char *format, ...) -SCANF_INTERCEPTOR_IMPL(__isoc99_fscanf, __isoc99_vfscanf, stream, format) +FORMAT_INTERCEPTOR_IMPL(__isoc99_fscanf, __isoc99_vfscanf, stream, format) INTERCEPTOR(int, __isoc99_sscanf, const char *str, const char *format, ...) -SCANF_INTERCEPTOR_IMPL(__isoc99_sscanf, __isoc99_vsscanf, str, format) +FORMAT_INTERCEPTOR_IMPL(__isoc99_sscanf, __isoc99_vsscanf, str, format) #endif #endif @@ -664,6 +667,168 @@ #define INIT_ISOC99_SCANF #endif +#if SANITIZER_INTERCEPT_PRINTF + +#define VPRINTF_INTERCEPTOR_ENTER(vname, ...) \ + void *ctx; \ + COMMON_INTERCEPTOR_ENTER(ctx, vname, __VA_ARGS__); \ + va_list aq; \ + va_copy(aq, ap); + +#define VPRINTF_INTERCEPTOR_RETURN() \ + va_end(aq); + +#define VPRINTF_INTERCEPTOR_IMPL(vname, ...) \ + { \ + VPRINTF_INTERCEPTOR_ENTER(vname, __VA_ARGS__); \ + if (common_flags()->check_printf) \ + printf_common(ctx, format, aq); \ + int res = REAL(vname)(__VA_ARGS__); \ + VPRINTF_INTERCEPTOR_RETURN(); \ + return res; \ + } + +// FIXME: call snprintf(str, 0, ...) to obtain res +// and verify length of str safely +#define VSPRINTF_INTERCEPTOR_IMPL(vname, str, ...) \ + { \ + VPRINTF_INTERCEPTOR_ENTER(vname, str, __VA_ARGS__) \ + if (common_flags()->check_printf) { \ + printf_common(ctx, format, aq); \ + } \ + int res = REAL(vname)(str, __VA_ARGS__); \ + if (res > 0 && common_flags()->check_printf) { \ + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, str, res + 1); \ + } \ + VPRINTF_INTERCEPTOR_RETURN(); \ + return res; \ + } + +#define VSNPRINTF_INTERCEPTOR_IMPL(vname, str, size, ...) \ + { \ + VPRINTF_INTERCEPTOR_ENTER(vname, str, size, __VA_ARGS__) \ + if (common_flags()->check_printf) { \ + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, str, size); \ + printf_common(ctx, format, aq); \ + } \ + int res = REAL(vname)(str, size, __VA_ARGS__); \ + VPRINTF_INTERCEPTOR_RETURN(); \ + return res; \ + } + +#define VASPRINTF_INTERCEPTOR_IMPL(vname, strp, ...) \ + { \ + VPRINTF_INTERCEPTOR_ENTER(vname, strp, __VA_ARGS__) \ + if (common_flags()->check_printf) { \ + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, strp, sizeof(char *)); \ + printf_common(ctx, format, aq); \ + } \ + int res = REAL(vname)(strp, __VA_ARGS__); \ + if (res > 0 && common_flags()->check_printf) { \ + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, *strp, res); \ + } \ + VPRINTF_INTERCEPTOR_RETURN(); \ + return res; \ + } + +INTERCEPTOR(int, vprintf, const char *format, va_list ap) +VPRINTF_INTERCEPTOR_IMPL(vprintf, format, ap) + +INTERCEPTOR(int, vfprintf, void *stream, const char *format, va_list ap) +VPRINTF_INTERCEPTOR_IMPL(vfprintf, stream, format, ap) + +INTERCEPTOR(int, vsprintf, char *str, const char *format, va_list ap) +VSPRINTF_INTERCEPTOR_IMPL(vsprintf, str, format, ap) + +INTERCEPTOR(int, vsnprintf, char *str, SIZE_T size, const char *format, + va_list ap) +VSNPRINTF_INTERCEPTOR_IMPL(vsnprintf, str, size, format, ap) + +INTERCEPTOR(int, vasprintf, char **strp, const char *format, va_list ap) +VASPRINTF_INTERCEPTOR_IMPL(vasprintf, strp, format, ap) + +#if SANITIZER_INTERCEPT_ISOC99_PRINTF +INTERCEPTOR(int, __isoc99_vprintf, const char *format, va_list ap) +VPRINTF_INTERCEPTOR_IMPL(__isoc99_vprintf, format, ap) + +INTERCEPTOR(int, __isoc99_vfprintf, void *stream, const char *format, + va_list ap) +VPRINTF_INTERCEPTOR_IMPL(__isoc99_vfprintf, stream, format, ap) + +INTERCEPTOR(int, __isoc99_vsprintf, char *str, const char *format, + va_list ap) +VSPRINTF_INTERCEPTOR_IMPL(__isoc99_vsprintf, str, format, ap) + +INTERCEPTOR(int, __isoc99_vsnprintf, char *str, SIZE_T size, const char *format, + va_list ap) +VSNPRINTF_INTERCEPTOR_IMPL(__isoc99_vsnprintf, str, size, format, ap) + +#endif // SANITIZER_INTERCEPT_ISOC99_PRINTF + +INTERCEPTOR(int, printf, const char *format, ...) +FORMAT_INTERCEPTOR_IMPL(printf, vprintf, format) + +INTERCEPTOR(int, fprintf, void *stream, const char *format, ...) +FORMAT_INTERCEPTOR_IMPL(fprintf, vfprintf, stream, format) + +INTERCEPTOR(int, sprintf, char *str, const char *format, ...) // NOLINT +FORMAT_INTERCEPTOR_IMPL(sprintf, vsprintf, str, format) // NOLINT + +INTERCEPTOR(int, snprintf, char *str, SIZE_T size, const char *format, ...) +FORMAT_INTERCEPTOR_IMPL(snprintf, vsnprintf, str, size, format) + +INTERCEPTOR(int, asprintf, char **strp, const char *format, ...) +FORMAT_INTERCEPTOR_IMPL(asprintf, vasprintf, strp, format) + +#if SANITIZER_INTERCEPT_ISOC99_PRINTF +INTERCEPTOR(int, __isoc99_printf, const char *format, ...) +FORMAT_INTERCEPTOR_IMPL(__isoc99_printf, __isoc99_vprintf, format) + +INTERCEPTOR(int, __isoc99_fprintf, void *stream, const char *format, ...) +FORMAT_INTERCEPTOR_IMPL(__isoc99_fprintf, __isoc99_vfprintf, stream, format) + +INTERCEPTOR(int, __isoc99_sprintf, char *str, const char *format, ...) +FORMAT_INTERCEPTOR_IMPL(__isoc99_sprintf, __isoc99_vsprintf, str, format) + +INTERCEPTOR(int, __isoc99_snprintf, char *str, SIZE_T size, + const char *format, ...) +FORMAT_INTERCEPTOR_IMPL(__isoc99_snprintf, __isoc99_vsnprintf, str, size, + format) + +#endif // SANITIZER_INTERCEPT_ISOC99_PRINTF + +#endif // SANITIZER_INTERCEPT_PRINTF + +#if SANITIZER_INTERCEPT_PRINTF +#define INIT_PRINTF \ + COMMON_INTERCEPT_FUNCTION(printf); \ + COMMON_INTERCEPT_FUNCTION(sprintf); \ + COMMON_INTERCEPT_FUNCTION(snprintf); \ + COMMON_INTERCEPT_FUNCTION(asprintf); \ + COMMON_INTERCEPT_FUNCTION(fprintf); \ + COMMON_INTERCEPT_FUNCTION(vprintf); \ + COMMON_INTERCEPT_FUNCTION(vsprintf); \ + COMMON_INTERCEPT_FUNCTION(vsnprintf); \ + COMMON_INTERCEPT_FUNCTION(vasprintf); \ + COMMON_INTERCEPT_FUNCTION(vfprintf); +#else +#define INIT_PRINTF +#endif + +#if SANITIZER_INTERCEPT_ISOC99_PRINTF +#define INIT_ISOC99_PRINTF \ + COMMON_INTERCEPT_FUNCTION(__isoc99_printf); \ + COMMON_INTERCEPT_FUNCTION(__isoc99_sprintf); \ + COMMON_INTERCEPT_FUNCTION(__isoc99_snprintf); \ + COMMON_INTERCEPT_FUNCTION(__isoc99_fprintf); \ + COMMON_INTERCEPT_FUNCTION(__isoc99_vprintf); \ + COMMON_INTERCEPT_FUNCTION(__isoc99_vsprintf); \ + COMMON_INTERCEPT_FUNCTION(__isoc99_vsnprintf); \ + COMMON_INTERCEPT_FUNCTION(__isoc99_vfprintf); +#else +#define INIT_ISOC99_PRINTF +#endif + #if SANITIZER_INTERCEPT_IOCTL #include "sanitizer_common_interceptors_ioctl.inc" INTERCEPTOR(int, ioctl, int d, unsigned request, void *arg) { @@ -2924,6 +3089,8 @@ INIT_STRPTIME; \ INIT_SCANF; \ INIT_ISOC99_SCANF; \ + INIT_PRINTF; \ + INIT_ISOC99_PRINTF; \ INIT_FREXP; \ INIT_FREXPF_FREXPL; \ INIT_GETPWNAM_AND_FRIENDS; \ Index: lib/sanitizer_common/sanitizer_common_interceptors_scanf.inc =================================================================== --- lib/sanitizer_common/sanitizer_common_interceptors_scanf.inc +++ lib/sanitizer_common/sanitizer_common_interceptors_scanf.inc @@ -7,18 +7,23 @@ // //===----------------------------------------------------------------------===// // -// Scanf implementation for use in *Sanitizer interceptors. +// Scanf/printf implementation for use in *Sanitizer interceptors. // Follows http://pubs.opengroup.org/onlinepubs/9699919799/functions/fscanf.html +// and http://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html // with a few common GNU extensions. // //===----------------------------------------------------------------------===// #include -struct ScanfDirective { - int argIdx; // argument index, or -1 of not specified ("%n$") +struct FormatDirective { + int argIdx; // argument index, or -1 if not specified ("%n$") int fieldWidth; + int fieldPrecision; + int precisionIdx; // precision argument index, or -1 if not specified (".*n$") bool suppressed; // suppress assignment ("*") - bool allocate; // allocate space ("m") + bool starredPrecision; + bool allocate; // allocate space ("m" in scanf) + bool printErrno; // print value of errno ("m" in printf) char lengthModifier[2]; char convSpecifier; bool maybeGnuMalloc; @@ -35,14 +40,15 @@ return !!internal_strchr(s, c); } -// Parse scanf format string. If a valid directive in encountered, it is +// Parse format string. If a valid directive in encountered, it is // returned in dir. This function returns the pointer to the first // unprocessed character, or 0 in case of error. // In case of the end-of-string, a pointer to the closing \0 is returned. -static const char *scanf_parse_next(const char *p, bool allowGnuMalloc, - ScanfDirective *dir) { +static const char *format_parse_next(const char *p, bool allowGnuMalloc, + FormatDirective *dir, bool output) { internal_memset(dir, 0, sizeof(*dir)); dir->argIdx = -1; + dir->precisionIdx = -1; while (*p) { if (*p != '%') { @@ -58,6 +64,10 @@ if (*p == '\0') { return 0; } + // Flags + while (output && char_is_one_of(*p, "'-+ #0")) { + ++p; + } // %n$ if (*p >= '0' && *p <= '9') { int number; @@ -75,6 +85,8 @@ ++p; } // Field width. + if (*p == '-') + ++p; if (*p >= '0' && *p <= '9') { p = parse_number(p, &dir->fieldWidth); if (dir->fieldWidth <= 0) @@ -82,8 +94,34 @@ } // m if (*p == 'm') { - dir->allocate = true; + if (output) + dir->printErrno = true; + else + dir->allocate = true; + ++p; + } + // Precision. + if (output && *p == '.') { ++p; + if (*p == '*') { + // . * [ param-no $ ] + dir->starredPrecision = true; + ++p; + if (*p >= '0' && *p <= '9') { + p = parse_number(p, &dir->precisionIdx); + if (*p != '$') + return 0; + } + } else { + if (*p == '-') + ++p; + if (*p >= '0' && *p <= '9') { + int tmp; + p = parse_number(p, &tmp); + } else { + return 0; + } + } } // Length modifier. if (char_is_one_of(*p, "jztLq")) { @@ -107,7 +145,7 @@ // Conversion specifier. dir->convSpecifier = *p++; // Consume %[...] expression. - if (dir->convSpecifier == '[') { + if (!output && dir->convSpecifier == '[') { if (*p == '^') ++p; if (*p == ']') @@ -127,7 +165,7 @@ if (*p == 's' || *p == 'S') { dir->maybeGnuMalloc = true; ++p; - } else if (*p == '[') { + } else if (!output && *p == '[') { // Watch for %a[h-j%d], if % appears in the // [...] range, then we need to give up, we don't know // if scanf will parse it as POSIX %a [h-j %d ] or @@ -151,18 +189,18 @@ } // Returns true if the character is an integer conversion specifier. -static bool scanf_is_integer_conv(char c) { +static bool format_is_integer_conv(char c) { return char_is_one_of(c, "diouxXn"); } // Returns true if the character is an floating point conversion specifier. -static bool scanf_is_float_conv(char c) { +static bool format_is_float_conv(char c) { return char_is_one_of(c, "aAeEfFgG"); } // Returns string output character size for string-like conversions, // or 0 if the conversion is invalid. -static int scanf_get_char_size(ScanfDirective *dir) { +static int format_get_char_size(FormatDirective *dir) { if (char_is_one_of(dir->convSpecifier, "CS")) { // wchar_t return 0; @@ -181,33 +219,37 @@ return 0; } -enum ScanfStoreSize { +enum FormatStoreSize { // Store size not known in advance; can be calculated as strlen() of the // destination buffer. - SSS_STRLEN = -1, + FSS_STRLEN = -1, // Invalid conversion specifier. - SSS_INVALID = 0 + FSS_INVALID = 0 }; -// Returns the store size of a scanf directive (if >0), or a value of -// ScanfStoreSize. -static int scanf_get_store_size(ScanfDirective *dir) { +// Returns the memory size of a format directive (if >0), or a value of +// FormatStoreSize. +static int format_get_value_size(FormatDirective *dir, bool output) { if (dir->allocate) { if (!char_is_one_of(dir->convSpecifier, "cCsS[")) - return SSS_INVALID; + return FSS_INVALID; + return sizeof(char *); + } + + if (dir->printErrno) { return sizeof(char *); } if (dir->maybeGnuMalloc) { if (dir->convSpecifier != 'a' || dir->lengthModifier[0]) - return SSS_INVALID; + return FSS_INVALID; // This is ambiguous, so check the smaller size of char * (if it is // a GNU extension of %as, %aS or %a[...]) and float (if it is // POSIX %a followed by s, S or [ letters). return sizeof(char *) < sizeof(float) ? sizeof(char *) : sizeof(float); } - if (scanf_is_integer_conv(dir->convSpecifier)) { + if (format_is_integer_conv(dir->convSpecifier)) { switch (dir->lengthModifier[0]) { case 'h': return dir->lengthModifier[1] == 'h' ? sizeof(char) : sizeof(short); @@ -224,11 +266,11 @@ case 0: return sizeof(int); default: - return SSS_INVALID; + return FSS_INVALID; } } - if (scanf_is_float_conv(dir->convSpecifier)) { + if (format_is_float_conv(dir->convSpecifier)) { switch (dir->lengthModifier[0]) { case 'L': case 'q': @@ -237,25 +279,26 @@ return dir->lengthModifier[1] == 'l' ? sizeof(long double) : sizeof(double); case 0: - return sizeof(float); + // Printf promotes floats to doubles but scanf does not + return output ? sizeof(double) : sizeof(float); default: - return SSS_INVALID; + return FSS_INVALID; } } if (char_is_one_of(dir->convSpecifier, "sS[")) { - unsigned charSize = scanf_get_char_size(dir); + unsigned charSize = format_get_char_size(dir); if (charSize == 0) - return SSS_INVALID; - if (dir->fieldWidth == 0) - return SSS_STRLEN; + return FSS_INVALID; + if (output || dir->fieldWidth == 0) + return FSS_STRLEN; return (dir->fieldWidth + 1) * charSize; } if (char_is_one_of(dir->convSpecifier, "cC")) { - unsigned charSize = scanf_get_char_size(dir); + unsigned charSize = format_get_char_size(dir); if (charSize == 0) - return SSS_INVALID; + return FSS_INVALID; if (dir->fieldWidth == 0) return charSize; return dir->fieldWidth * charSize; @@ -263,11 +306,11 @@ if (dir->convSpecifier == 'p') { if (dir->lengthModifier[1] != 0) - return SSS_INVALID; + return FSS_INVALID; return sizeof(void *); } - return SSS_INVALID; + return FSS_INVALID; } // Common part of *scanf interceptors. @@ -278,9 +321,11 @@ CHECK_GT(n_inputs, 0); const char *p = format; + COMMON_INTERCEPTOR_READ_RANGE(ctx, format, internal_strlen(format) + 1); + while (*p) { - ScanfDirective dir; - p = scanf_parse_next(p, allowGnuMalloc, &dir); + FormatDirective dir; + p = format_parse_next(p, allowGnuMalloc, &dir, false); if (!p) break; if (dir.convSpecifier == 0) { @@ -295,17 +340,103 @@ } if (dir.suppressed) continue; - int size = scanf_get_store_size(&dir); - if (size == SSS_INVALID) + int size = format_get_value_size(&dir, false); + if (size == FSS_INVALID) { + Report("WARNING: unexpected format specifier in scanf interceptor\n"); break; + } void *argp = va_arg(aq, void *); if (dir.convSpecifier != 'n') --n_inputs; if (n_inputs < 0) break; - if (size == SSS_STRLEN) { + if (size == FSS_STRLEN) { size = internal_strlen((const char *)argp) + 1; } COMMON_INTERCEPTOR_WRITE_RANGE(ctx, argp, size); } } + +#define skip_scalar_arg(aq, convSpec, size) \ + do { \ + if (format_is_float_conv(convSpec)) { \ + switch (size) { \ + case 8: \ + va_arg(aq, double); \ + break; \ + case 16: \ + va_arg(aq, long double); \ + break; \ + default: \ + Report("WARNING: unexpected format specifier" \ + " in printf interceptor\n"); \ + return; \ + } \ + } else { \ + switch (size) { \ + case 1: \ + case 2: \ + case 4: \ + va_arg(aq, u32); \ + break; \ + case 8: \ + va_arg(aq, u64); \ + break; \ + default: \ + Report("WARNING: unexpected format specifier" \ + " in printf interceptor\n"); \ + return; \ + } \ + } \ + } while (0) + +// Common part of *printf interceptors. +// Process format string and va_list, and report all load ranges. +static void printf_common(void *ctx, const char *format, va_list aq) { + COMMON_INTERCEPTOR_READ_RANGE(ctx, format, internal_strlen(format) + 1); + + const char *p = format; + + while (*p) { + FormatDirective dir; + p = format_parse_next(p, false, &dir, true); + if (!p) + break; + if (dir.convSpecifier == 0) { + // This can only happen at the end of the format string. + CHECK_EQ(*p, 0); + break; + } + // Here the directive is valid. Do what it says. + if (dir.argIdx != -1 || dir.precisionIdx != -1) { + // Unsupported. + break; + } + if (dir.suppressed) { + // Dynamic width + skip_scalar_arg(aq, 'd', sizeof(int)); + } + if (dir.starredPrecision) { + // Dynamic precision + skip_scalar_arg(aq, 'd', sizeof(int)); + } + int size = format_get_value_size(&dir, true); + if (size == FSS_INVALID) { + Report("WARNING: unexpected format specifier in printf interceptor\n"); + break; + } + if (dir.convSpecifier == 'n') { + void *argp = va_arg(aq, void *); + COMMON_INTERCEPTOR_WRITE_RANGE(ctx, argp, size); + continue; + } else if (size == FSS_STRLEN) { + if (void *argp = va_arg(aq, void *)) { + size = internal_strlen((const char *)argp) + 1; + COMMON_INTERCEPTOR_READ_RANGE(ctx, argp, size); + } + } else { + // Skip non-pointer args + skip_scalar_arg(aq, dir.convSpecifier, size); + } + } +} Index: lib/sanitizer_common/sanitizer_flags.h =================================================================== --- lib/sanitizer_common/sanitizer_flags.h +++ lib/sanitizer_common/sanitizer_flags.h @@ -56,6 +56,8 @@ bool allocator_may_return_null; // If false, disable printing error summaries in addition to error reports. bool print_summary; + // Check printf arguments. + bool check_printf; }; inline CommonFlags *common_flags() { Index: lib/sanitizer_common/sanitizer_flags.cc =================================================================== --- lib/sanitizer_common/sanitizer_flags.cc +++ lib/sanitizer_common/sanitizer_flags.cc @@ -32,6 +32,7 @@ f->leak_check_at_exit = true; f->allocator_may_return_null = false; f->print_summary = true; + f->check_printf = false; } void ParseCommonFlagsFromString(CommonFlags *f, const char *str) { @@ -48,6 +49,7 @@ ParseFlag(str, &f->leak_check_at_exit, "leak_check_at_exit"); ParseFlag(str, &f->allocator_may_return_null, "allocator_may_return_null"); ParseFlag(str, &f->print_summary, "print_summary"); + ParseFlag(str, &f->check_printf, "check_printf"); // Do a sanity check for certain flags. if (f->malloc_context_size < 1) Index: lib/sanitizer_common/sanitizer_platform_interceptors.h =================================================================== --- lib/sanitizer_common/sanitizer_platform_interceptors.h +++ lib/sanitizer_common/sanitizer_platform_interceptors.h @@ -75,6 +75,11 @@ #define SANITIZER_INTERCEPT_SCANF SI_NOT_WINDOWS #define SANITIZER_INTERCEPT_ISOC99_SCANF SI_LINUX +#ifndef SANITIZER_INTERCEPT_PRINTF +# define SANITIZER_INTERCEPT_PRINTF SI_NOT_WINDOWS +# define SANITIZER_INTERCEPT_ISOC99_PRINTF SI_LINUX +#endif + #define SANITIZER_INTERCEPT_FREXP 1 #define SANITIZER_INTERCEPT_FREXPF_FREXPL SI_NOT_WINDOWS Index: lib/sanitizer_common/tests/sanitizer_scanf_interceptor_test.cc =================================================================== --- lib/sanitizer_common/tests/sanitizer_scanf_interceptor_test.cc +++ lib/sanitizer_common/tests/sanitizer_scanf_interceptor_test.cc @@ -15,17 +15,51 @@ #include "interception/interception.h" #include "sanitizer_test_utils.h" #include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_common.h" #include "gtest/gtest.h" using namespace __sanitizer; +#define COMMON_INTERCEPTOR_READ_WRITE_RANGE(ctx, ptr, size) \ + do { \ + ((std::vector *)ctx)->push_back(size); \ + ptr = ptr; \ + } while (0) + +#define COMMON_INTERCEPTOR_READ_RANGE(ctx, ptr, size) \ + COMMON_INTERCEPTOR_READ_WRITE_RANGE(ctx, ptr, size) + #define COMMON_INTERCEPTOR_WRITE_RANGE(ctx, ptr, size) \ - ((std::vector *)ctx)->push_back(size) + COMMON_INTERCEPTOR_READ_WRITE_RANGE(ctx, ptr, size) #include "sanitizer_common/sanitizer_common_interceptors_scanf.inc" -static const char scanf_buf[] = "Test string."; -static size_t scanf_buf_size = sizeof(scanf_buf); +static const unsigned I = sizeof(int); // NOLINT +static const unsigned L = sizeof(long); // NOLINT +static const unsigned LL = sizeof(long long); // NOLINT +static const unsigned S = sizeof(short); // NOLINT +static const unsigned C = sizeof(char); // NOLINT +static const unsigned D = sizeof(double); // NOLINT +static const unsigned LD = sizeof(long double); // NOLINT +static const unsigned F = sizeof(float); // NOLINT +static const unsigned P = sizeof(char *); // NOLINT + +static void verifyFormatResults(const char *format, unsigned n, + const std::vector &computed_sizes, + va_list expected_sizes) { + // "+ 1" because of format string + ASSERT_EQ(n + 1, + computed_sizes.size()) << "Unexpected number of format arguments: '" + << format << "'"; + for (unsigned i = 0; i < n; ++i) + EXPECT_EQ(va_arg(expected_sizes, unsigned), computed_sizes[i + 1]) + << "Unexpect write size for argument " << i << ", format string '" + << format << "'"; +} + +static const char test_buf[] = "Test string."; +static size_t test_buf_size = sizeof(test_buf); + static const unsigned SCANF_ARGS_MAX = 16; static void testScanf3(void *ctx, int result, bool allowGnuMalloc, @@ -42,15 +76,10 @@ std::vector scanf_sizes; // 16 args should be enough. testScanf3((void *)&scanf_sizes, scanf_result, allowGnuMalloc, format, - scanf_buf, scanf_buf, scanf_buf, scanf_buf, scanf_buf, scanf_buf, - scanf_buf, scanf_buf, scanf_buf, scanf_buf, scanf_buf, scanf_buf, - scanf_buf, scanf_buf, scanf_buf, scanf_buf); - ASSERT_EQ(n, scanf_sizes.size()) << "Unexpected number of format arguments: '" - << format << "'"; - for (unsigned i = 0; i < n; ++i) - EXPECT_EQ(va_arg(expected_sizes, unsigned), scanf_sizes[i]) - << "Unexpect write size for argument " << i << ", format string '" - << format << "'"; + test_buf, test_buf, test_buf, test_buf, test_buf, test_buf, + test_buf, test_buf, test_buf, test_buf, test_buf, test_buf, + test_buf, test_buf, test_buf, test_buf); + verifyFormatResults(format, n, scanf_sizes, expected_sizes); } static void testScanf(const char *format, unsigned n, ...) { @@ -76,16 +105,6 @@ } TEST(SanitizerCommonInterceptors, Scanf) { - const unsigned I = sizeof(int); // NOLINT - const unsigned L = sizeof(long); // NOLINT - const unsigned LL = sizeof(long long); // NOLINT - const unsigned S = sizeof(short); // NOLINT - const unsigned C = sizeof(char); // NOLINT - const unsigned D = sizeof(double); // NOLINT - const unsigned LD = sizeof(long double); // NOLINT - const unsigned F = sizeof(float); // NOLINT - const unsigned P = sizeof(char *); // NOLINT - testScanf("%d", 1, I); testScanf("%d%d%d", 3, I, I, I); testScanf("ab%u%dc", 2, I, I); @@ -113,10 +132,10 @@ testScanf("%*d", 0); testScanf("%4d%8f%c", 3, I, F, C); - testScanf("%s%d", 2, scanf_buf_size, I); - testScanf("%[abc]", 1, scanf_buf_size); + testScanf("%s%d", 2, test_buf_size, I); + testScanf("%[abc]", 1, test_buf_size); testScanf("%4[bcdef]", 1, 5); - testScanf("%[]]", 1, scanf_buf_size); + testScanf("%[]]", 1, test_buf_size); testScanf("%8[^]%d0-9-]%c", 2, 9, C); testScanf("%*[^:]%n:%d:%1[ ]%n", 4, I, I, 2, I); @@ -172,7 +191,54 @@ testScanfPartial("%d%n%n%d //1\n", 1, 3, I, I, I); testScanfPartial("%d%n%n%d //2\n", 2, 4, I, I, I, I); - testScanfPartial("%d%n%n%d %s %s", 3, 5, I, I, I, I, scanf_buf_size); - testScanfPartial("%d%n%n%d %s %s", 4, 6, I, I, I, I, scanf_buf_size, - scanf_buf_size); + testScanfPartial("%d%n%n%d %s %s", 3, 5, I, I, I, I, test_buf_size); + testScanfPartial("%d%n%n%d %s %s", 4, 6, I, I, I, I, test_buf_size, + test_buf_size); +} + +static void testPrintf3(void *ctx, const char *format, ...) { + va_list ap; + va_start(ap, format); + printf_common(ctx, format, ap); + va_end(ap); +} + +static void testPrintf2(const char *format, unsigned n, + va_list expected_sizes) { + std::vector printf_sizes; + // 16 args should be enough. + testPrintf3((void *)&printf_sizes, format, + test_buf, test_buf, test_buf, test_buf, test_buf, test_buf, + test_buf, test_buf, test_buf, test_buf, test_buf, test_buf, + test_buf, test_buf, test_buf, test_buf); + verifyFormatResults(format, n, printf_sizes, expected_sizes); +} + +static void testPrintf(const char *format, unsigned n, ...) { + va_list ap; + va_start(ap, n); + testPrintf2(format, n, ap); + va_end(ap); +} + +TEST(SanitizerCommonInterceptors, Printf) { + // Only test functionality which differs from scanf + + // Indexed arguments + testPrintf("%5$d", 0); + testPrintf("%.5$d", 0); + + // errno + testPrintf("%0-m", 0); + + // Dynamic width + testPrintf("%*n", 1, I); + testPrintf("%*.10n", 1, I); + + // Precision + testPrintf("%10.10n", 1, I); + + // Dynamic precision + testPrintf("%.*n", 1, I); + testPrintf("%10.*n", 1, I); } Index: lib/tsan/lit_tests/printf-1.c =================================================================== --- lib/tsan/lit_tests/printf-1.c +++ lib/tsan/lit_tests/printf-1.c @@ -0,0 +1,16 @@ +// RUN: %clang_tsan -O2 %s -o %t +// RUN: ASAN_OPTIONS=check_printf=1 %t 2>&1 | FileCheck %s +// RUN: ASAN_OPTIONS=check_printf=0 %t 2>&1 | FileCheck %s +// RUN: ASAN_OPTIONS="" %t 2>&1 | FileCheck %s + +#include +int main() { + volatile char c = '0'; + volatile int x = 12; + volatile float f = 1.239; + volatile char s[] = "34"; + printf("%c %d %.3f %s\n", c, x, f, s); + return 0; + // Check that printf works fine under Tsan. + // CHECK: 0 12 1.239 34 +} Index: lib/tsan/rtl/tsan_stat.h =================================================================== --- lib/tsan/rtl/tsan_stat.h +++ lib/tsan/rtl/tsan_stat.h @@ -278,6 +278,26 @@ StatInt___isoc99_scanf, StatInt___isoc99_sscanf, StatInt___isoc99_fscanf, + StatInt_vprintf, + StatInt_vsprintf, + StatInt_vsnprintf, + StatInt_vasprintf, + StatInt_vfprintf, + StatInt_printf, + StatInt_sprintf, + StatInt_snprintf, + StatInt_asprintf, + StatInt_fprintf, + StatInt___isoc99_vprintf, + StatInt___isoc99_vsprintf, + StatInt___isoc99_vsnprintf, + StatInt___isoc99_vasprintf, + StatInt___isoc99_vfprintf, + StatInt___isoc99_printf, + StatInt___isoc99_sprintf, + StatInt___isoc99_snprintf, + StatInt___isoc99_asprintf, + StatInt___isoc99_fprintf, StatInt_on_exit, StatInt___cxa_atexit, StatInt_localtime, Index: lib/tsan/rtl/tsan_stat.cc =================================================================== --- lib/tsan/rtl/tsan_stat.cc +++ lib/tsan/rtl/tsan_stat.cc @@ -283,6 +283,24 @@ name[StatInt___isoc99_scanf] = " scanf "; name[StatInt___isoc99_sscanf] = " sscanf "; name[StatInt___isoc99_fscanf] = " fscanf "; + name[StatInt_vprintf] = " vprintf "; + name[StatInt_vsprintf] = " vsprintf "; + name[StatInt_vsnprintf] = " vsnprintf "; + name[StatInt_vasprintf] = " vasprintf "; + name[StatInt_vfprintf] = " vfprintf "; + name[StatInt_printf] = " printf "; + name[StatInt_sprintf] = " sprintf "; + name[StatInt_snprintf] = " snprintf "; + name[StatInt_asprintf] = " asprintf "; + name[StatInt_fprintf] = " fprintf "; + name[StatInt___isoc99_vprintf] = " vprintf "; + name[StatInt___isoc99_vsprintf] = " vsprintf "; + name[StatInt___isoc99_vsnprintf] = " vsnprintf "; + name[StatInt___isoc99_vfprintf] = " vfprintf "; + name[StatInt___isoc99_printf] = " printf "; + name[StatInt___isoc99_sprintf] = " sprintf "; + name[StatInt___isoc99_snprintf] = " snprintf "; + name[StatInt___isoc99_fprintf] = " fprintf "; name[StatInt_on_exit] = " on_exit "; name[StatInt___cxa_atexit] = " __cxa_atexit "; name[StatInt_localtime] = " localtime ";