1//===-- sanitizer_common_interceptors_format.inc ----------------*- C++ -*-===// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// 10// Scanf/printf implementation for use in *Sanitizer interceptors. 11// Follows http://pubs.opengroup.org/onlinepubs/9699919799/functions/fscanf.html 12// and http://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html 13// with a few common GNU extensions. 14// 15//===----------------------------------------------------------------------===// 16 17#include <stdarg.h> 18 19static const char *parse_number(const char *p, int *out) { 20 *out = internal_atoll(p); 21 while (*p >= '0' && *p <= '9') 22 ++p; 23 return p; 24} 25 26static const char *maybe_parse_param_index(const char *p, int *out) { 27 // n$ 28 if (*p >= '0' && *p <= '9') { 29 int number; 30 const char *q = parse_number(p, &number); 31 CHECK(q); 32 if (*q == '$') { 33 *out = number; 34 p = q + 1; 35 } 36 } 37 38 // Otherwise, do not change p. This will be re-parsed later as the field 39 // width. 40 return p; 41} 42 43static bool char_is_one_of(char c, const char *s) { 44 return !!internal_strchr(s, c); 45} 46 47static const char *maybe_parse_length_modifier(const char *p, char ll[2]) { 48 if (char_is_one_of(*p, "jztLq")) { 49 ll[0] = *p; 50 ++p; 51 } else if (*p == 'h') { 52 ll[0] = 'h'; 53 ++p; 54 if (*p == 'h') { 55 ll[1] = 'h'; 56 ++p; 57 } 58 } else if (*p == 'l') { 59 ll[0] = 'l'; 60 ++p; 61 if (*p == 'l') { 62 ll[1] = 'l'; 63 ++p; 64 } 65 } 66 return p; 67} 68 69// Returns true if the character is an integer conversion specifier. 70static bool format_is_integer_conv(char c) { 71 return char_is_one_of(c, "diouxXn"); 72} 73 74// Returns true if the character is an floating point conversion specifier. 75static bool format_is_float_conv(char c) { 76 return char_is_one_of(c, "aAeEfFgG"); 77} 78 79// Returns string output character size for string-like conversions, 80// or 0 if the conversion is invalid. 81static int format_get_char_size(char convSpecifier, 82 const char lengthModifier[2]) { 83 if (char_is_one_of(convSpecifier, "CS")) { 84 return sizeof(wchar_t); 85 } 86 87 if (char_is_one_of(convSpecifier, "cs[")) { 88 if (lengthModifier[0] == 'l' && lengthModifier[1] == '\0') 89 return sizeof(wchar_t); 90 else if (lengthModifier[0] == '\0') 91 return sizeof(char); 92 } 93 94 return 0; 95} 96 97enum FormatStoreSize { 98 // Store size not known in advance; can be calculated as wcslen() of the 99 // destination buffer. 100 FSS_WCSLEN = -2, 101 // Store size not known in advance; can be calculated as strlen() of the 102 // destination buffer. 103 FSS_STRLEN = -1, 104 // Invalid conversion specifier. 105 FSS_INVALID = 0 106}; 107 108// Returns the memory size of a format directive (if >0), or a value of 109// FormatStoreSize. 110static int format_get_value_size(char convSpecifier, 111 const char lengthModifier[2], 112 bool promote_float) { 113 if (format_is_integer_conv(convSpecifier)) { 114 switch (lengthModifier[0]) { 115 case 'h': 116 return lengthModifier[1] == 'h' ? sizeof(char) : sizeof(short); 117 case 'l': 118 return lengthModifier[1] == 'l' ? sizeof(long long) : sizeof(long); 119 case 'q': 120 return sizeof(long long); 121 case 'L': 122 return sizeof(long long); 123 case 'j': 124 return sizeof(INTMAX_T); 125 case 'z': 126 return sizeof(SIZE_T); 127 case 't': 128 return sizeof(PTRDIFF_T); 129 case 0: 130 return sizeof(int); 131 default: 132 return FSS_INVALID; 133 } 134 } 135 136 if (format_is_float_conv(convSpecifier)) { 137 switch (lengthModifier[0]) { 138 case 'L': 139 case 'q': 140 return sizeof(long double); 141 case 'l': 142 return lengthModifier[1] == 'l' ? sizeof(long double) 143 : sizeof(double); 144 case 0: 145 // Printf promotes floats to doubles but scanf does not 146 return promote_float ? sizeof(double) : sizeof(float); 147 default: 148 return FSS_INVALID; 149 } 150 } 151 152 if (convSpecifier == 'p') { 153 if (lengthModifier[0] != 0) 154 return FSS_INVALID; 155 return sizeof(void *); 156 } 157 158 return FSS_INVALID; 159} 160 161struct ScanfDirective { 162 int argIdx; // argument index, or -1 if not specified ("%n$") 163 int fieldWidth; 164 const char *begin; 165 const char *end; 166 bool suppressed; // suppress assignment ("*") 167 bool allocate; // allocate space ("m") 168 char lengthModifier[2]; 169 char convSpecifier; 170 bool maybeGnuMalloc; 171}; 172 173// Parse scanf format string. If a valid directive in encountered, it is 174// returned in dir. This function returns the pointer to the first 175// unprocessed character, or 0 in case of error. 176// In case of the end-of-string, a pointer to the closing \0 is returned. 177static const char *scanf_parse_next(const char *p, bool allowGnuMalloc, 178 ScanfDirective *dir) { 179 internal_memset(dir, 0, sizeof(*dir)); 180 dir->argIdx = -1; 181 182 while (*p) { 183 if (*p != '%') { 184 ++p; 185 continue; 186 } 187 dir->begin = p; 188 ++p; 189 // %% 190 if (*p == '%') { 191 ++p; 192 continue; 193 } 194 if (*p == '\0') { 195 return nullptr; 196 } 197 // %n$ 198 p = maybe_parse_param_index(p, &dir->argIdx); 199 CHECK(p); 200 // * 201 if (*p == '*') { 202 dir->suppressed = true; 203 ++p; 204 } 205 // Field width 206 if (*p >= '0' && *p <= '9') { 207 p = parse_number(p, &dir->fieldWidth); 208 CHECK(p); 209 if (dir->fieldWidth <= 0) // Width if at all must be non-zero 210 return nullptr; 211 } 212 // m 213 if (*p == 'm') { 214 dir->allocate = true; 215 ++p; 216 } 217 // Length modifier. 218 p = maybe_parse_length_modifier(p, dir->lengthModifier); 219 // Conversion specifier. 220 dir->convSpecifier = *p++; 221 // Consume %[...] expression. 222 if (dir->convSpecifier == '[') { 223 if (*p == '^') 224 ++p; 225 if (*p == ']') 226 ++p; 227 while (*p && *p != ']') 228 ++p; 229 if (*p == 0) 230 return nullptr; // unexpected end of string 231 // Consume the closing ']'. 232 ++p; 233 } 234 // This is unfortunately ambiguous between old GNU extension 235 // of %as, %aS and %a[...] and newer POSIX %a followed by 236 // letters s, S or [. 237 if (allowGnuMalloc && dir->convSpecifier == 'a' && 238 !dir->lengthModifier[0]) { 239 if (*p == 's' || *p == 'S') { 240 dir->maybeGnuMalloc = true; 241 ++p; 242 } else if (*p == '[') { 243 // Watch for %a[h-j%d], if % appears in the 244 // [...] range, then we need to give up, we don't know 245 // if scanf will parse it as POSIX %a [h-j %d ] or 246 // GNU allocation of string with range dh-j plus %. 247 const char *q = p + 1; 248 if (*q == '^') 249 ++q; 250 if (*q == ']') 251 ++q; 252 while (*q && *q != ']' && *q != '%') 253 ++q; 254 if (*q == 0 || *q == '%') 255 return nullptr; 256 p = q + 1; // Consume the closing ']'. 257 dir->maybeGnuMalloc = true; 258 } 259 } 260 dir->end = p; 261 break; 262 } 263 return p; 264} 265 266static int scanf_get_value_size(ScanfDirective *dir) { 267 if (dir->allocate) { 268 if (!char_is_one_of(dir->convSpecifier, "cCsS[")) 269 return FSS_INVALID; 270 return sizeof(char *); 271 } 272 273 if (dir->maybeGnuMalloc) { 274 if (dir->convSpecifier != 'a' || dir->lengthModifier[0]) 275 return FSS_INVALID; 276 // This is ambiguous, so check the smaller size of char * (if it is 277 // a GNU extension of %as, %aS or %a[...]) and float (if it is 278 // POSIX %a followed by s, S or [ letters). 279 return sizeof(char *) < sizeof(float) ? sizeof(char *) : sizeof(float); 280 } 281 282 if (char_is_one_of(dir->convSpecifier, "cCsS[")) { 283 bool needsTerminator = char_is_one_of(dir->convSpecifier, "sS["); 284 unsigned charSize = 285 format_get_char_size(dir->convSpecifier, dir->lengthModifier); 286 if (charSize == 0) 287 return FSS_INVALID; 288 if (dir->fieldWidth == 0) { 289 if (!needsTerminator) 290 return charSize; 291 return (charSize == sizeof(char)) ? FSS_STRLEN : FSS_WCSLEN; 292 } 293 return (dir->fieldWidth + needsTerminator) * charSize; 294 } 295 296 return format_get_value_size(dir->convSpecifier, dir->lengthModifier, false); 297} 298 299// Common part of *scanf interceptors. 300// Process format string and va_list, and report all store ranges. 301// Stops when "consuming" n_inputs input items. 302static void scanf_common(void *ctx, int n_inputs, bool allowGnuMalloc, 303 const char *format, va_list aq) { 304 CHECK_GT(n_inputs, 0); 305 const char *p = format; 306 307 COMMON_INTERCEPTOR_READ_RANGE(ctx, format, internal_strlen(format) + 1); 308 309 while (*p) { 310 ScanfDirective dir; 311 p = scanf_parse_next(p, allowGnuMalloc, &dir); 312 if (!p) 313 break; 314 if (dir.convSpecifier == 0) { 315 // This can only happen at the end of the format string. 316 CHECK_EQ(*p, 0); 317 break; 318 } 319 // Here the directive is valid. Do what it says. 320 if (dir.argIdx != -1) { 321 // Unsupported. 322 break; 323 } 324 if (dir.suppressed) 325 continue; 326 int size = scanf_get_value_size(&dir); 327 if (size == FSS_INVALID) { 328 Report("WARNING: unexpected format specifier in scanf interceptor: " 329 "%.*s\n", dir.end - dir.begin, dir.begin); 330 break; 331 } 332 void *argp = va_arg(aq, void *); 333 if (dir.convSpecifier != 'n') 334 --n_inputs; 335 if (n_inputs < 0) 336 break; 337 if (size == FSS_STRLEN) { 338 size = internal_strlen((const char *)argp) + 1; 339 } else if (size == FSS_WCSLEN) { 340 // FIXME: actually use wcslen() to calculate it. 341 size = 0; 342 } 343 COMMON_INTERCEPTOR_WRITE_RANGE(ctx, argp, size); 344 } 345} 346 347#if SANITIZER_INTERCEPT_PRINTF 348 349struct PrintfDirective { 350 int fieldWidth; 351 int fieldPrecision; 352 int argIdx; // width argument index, or -1 if not specified ("%*n$") 353 int precisionIdx; // precision argument index, or -1 if not specified (".*n$") 354 const char *begin; 355 const char *end; 356 bool starredWidth; 357 bool starredPrecision; 358 char lengthModifier[2]; 359 char convSpecifier; 360}; 361 362static const char *maybe_parse_number(const char *p, int *out) { 363 if (*p >= '0' && *p <= '9') 364 p = parse_number(p, out); 365 return p; 366} 367 368static const char *maybe_parse_number_or_star(const char *p, int *out, 369 bool *star) { 370 if (*p == '*') { 371 *star = true; 372 ++p; 373 } else { 374 *star = false; 375 p = maybe_parse_number(p, out); 376 } 377 return p; 378} 379 380// Parse printf format string. Same as scanf_parse_next. 381static const char *printf_parse_next(const char *p, PrintfDirective *dir) { 382 internal_memset(dir, 0, sizeof(*dir)); 383 dir->argIdx = -1; 384 dir->precisionIdx = -1; 385 386 while (*p) { 387 if (*p != '%') { 388 ++p; 389 continue; 390 } 391 dir->begin = p; 392 ++p; 393 // %% 394 if (*p == '%') { 395 ++p; 396 continue; 397 } 398 if (*p == '\0') { 399 return nullptr; 400 } 401 // %n$ 402 p = maybe_parse_param_index(p, &dir->precisionIdx); 403 CHECK(p); 404 // Flags 405 while (char_is_one_of(*p, "'-+ #0")) { 406 ++p; 407 } 408 // Field width 409 p = maybe_parse_number_or_star(p, &dir->fieldWidth, 410 &dir->starredWidth); 411 if (!p) 412 return nullptr; 413 // Precision 414 if (*p == '.') { 415 ++p; 416 // Actual precision is optional (surprise!) 417 p = maybe_parse_number_or_star(p, &dir->fieldPrecision, 418 &dir->starredPrecision); 419 if (!p) 420 return nullptr; 421 // m$ 422 if (dir->starredPrecision) { 423 p = maybe_parse_param_index(p, &dir->precisionIdx); 424 CHECK(p); 425 } 426 } 427 // Length modifier. 428 p = maybe_parse_length_modifier(p, dir->lengthModifier); 429 // Conversion specifier. 430 dir->convSpecifier = *p++; 431 dir->end = p; 432 break; 433 } 434 return p; 435} 436 437static int printf_get_value_size(PrintfDirective *dir) { 438 if (dir->convSpecifier == 'm') { 439 return sizeof(char *); 440 } 441 442 if (char_is_one_of(dir->convSpecifier, "cCsS")) { 443 unsigned charSize = 444 format_get_char_size(dir->convSpecifier, dir->lengthModifier); 445 if (charSize == 0) 446 return FSS_INVALID; 447 if (char_is_one_of(dir->convSpecifier, "sS")) { 448 return (charSize == sizeof(char)) ? FSS_STRLEN : FSS_WCSLEN; 449 } 450 return charSize; 451 } 452 453 return format_get_value_size(dir->convSpecifier, dir->lengthModifier, true); 454} 455 456#define SKIP_SCALAR_ARG(aq, convSpecifier, size) \ 457 do { \ 458 if (format_is_float_conv(convSpecifier)) { \ 459 switch (size) { \ 460 case 8: \ 461 va_arg(*aq, double); \ 462 break; \ 463 case 12: \ 464 va_arg(*aq, long double); \ 465 break; \ 466 case 16: \ 467 va_arg(*aq, long double); \ 468 break; \ 469 default: \ 470 Report("WARNING: unexpected floating-point arg size" \ 471 " in printf interceptor: %d\n", size); \ 472 return; \ 473 } \ 474 } else { \ 475 switch (size) { \ 476 case 1: \ 477 case 2: \ 478 case 4: \ 479 va_arg(*aq, u32); \ 480 break; \ 481 case 8: \ 482 va_arg(*aq, u64); \ 483 break; \ 484 default: \ 485 Report("WARNING: unexpected arg size" \ 486 " in printf interceptor: %d\n", size); \ 487 return; \ 488 } \ 489 } \ 490 } while (0) 491 492// Common part of *printf interceptors. 493// Process format string and va_list, and report all load ranges. 494static void printf_common(void *ctx, const char *format, va_list aq) { 495 COMMON_INTERCEPTOR_READ_RANGE(ctx, format, internal_strlen(format) + 1); 496 497 const char *p = format; 498 499 while (*p) { 500 PrintfDirective dir; 501 p = printf_parse_next(p, &dir); 502 if (!p) 503 break; 504 if (dir.convSpecifier == 0) { 505 // This can only happen at the end of the format string. 506 CHECK_EQ(*p, 0); 507 break; 508 } 509 // Here the directive is valid. Do what it says. 510 if (dir.argIdx != -1 || dir.precisionIdx != -1) { 511 // Unsupported. 512 break; 513 } 514 if (dir.starredWidth) { 515 // Dynamic width 516 SKIP_SCALAR_ARG(&aq, 'd', sizeof(int)); 517 } 518 if (dir.starredPrecision) { 519 // Dynamic precision 520 SKIP_SCALAR_ARG(&aq, 'd', sizeof(int)); 521 } 522 int size = printf_get_value_size(&dir); 523 if (size == FSS_INVALID) { 524 Report("WARNING: unexpected format specifier in printf " 525 "interceptor: %.*s\n", dir.end - dir.begin, dir.begin); 526 break; 527 } 528 if (dir.convSpecifier == 'n') { 529 void *argp = va_arg(aq, void *); 530 COMMON_INTERCEPTOR_WRITE_RANGE(ctx, argp, size); 531 continue; 532 } else if (size == FSS_STRLEN) { 533 if (void *argp = va_arg(aq, void *)) { 534 if (dir.starredPrecision) { 535 // FIXME: properly support starred precision for strings. 536 size = 0; 537 } else if (dir.fieldPrecision > 0) { 538 // Won't read more than "precision" symbols. 539 size = internal_strnlen((const char *)argp, dir.fieldPrecision); 540 if (size < dir.fieldPrecision) size++; 541 } else { 542 // Whole string will be accessed. 543 size = internal_strlen((const char *)argp) + 1; 544 } 545 COMMON_INTERCEPTOR_READ_RANGE(ctx, argp, size); 546 } 547 } else if (size == FSS_WCSLEN) { 548 if (void *argp = va_arg(aq, void *)) { 549 // FIXME: Properly support wide-character strings (via wcsrtombs). 550 size = 0; 551 COMMON_INTERCEPTOR_READ_RANGE(ctx, argp, size); 552 } 553 } else { 554 // Skip non-pointer args 555 SKIP_SCALAR_ARG(&aq, dir.convSpecifier, size); 556 } 557 } 558} 559 560#endif // SANITIZER_INTERCEPT_PRINTF 561