1276789Sdim//===-- sanitizer_common_interceptors_format.inc ----------------*- C++ -*-===// 2276789Sdim// 3353358Sdim// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4353358Sdim// See https://llvm.org/LICENSE.txt for license information. 5353358Sdim// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6276789Sdim// 7276789Sdim//===----------------------------------------------------------------------===// 8276789Sdim// 9276789Sdim// Scanf/printf implementation for use in *Sanitizer interceptors. 10276789Sdim// Follows http://pubs.opengroup.org/onlinepubs/9699919799/functions/fscanf.html 11276789Sdim// and http://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html 12276789Sdim// with a few common GNU extensions. 13276789Sdim// 14276789Sdim//===----------------------------------------------------------------------===// 15296417Sdim 16276789Sdim#include <stdarg.h> 17276789Sdim 18276789Sdimstatic const char *parse_number(const char *p, int *out) { 19276789Sdim *out = internal_atoll(p); 20276789Sdim while (*p >= '0' && *p <= '9') 21276789Sdim ++p; 22276789Sdim return p; 23276789Sdim} 24276789Sdim 25276789Sdimstatic const char *maybe_parse_param_index(const char *p, int *out) { 26276789Sdim // n$ 27276789Sdim if (*p >= '0' && *p <= '9') { 28276789Sdim int number; 29276789Sdim const char *q = parse_number(p, &number); 30276789Sdim CHECK(q); 31276789Sdim if (*q == '$') { 32276789Sdim *out = number; 33276789Sdim p = q + 1; 34276789Sdim } 35276789Sdim } 36276789Sdim 37276789Sdim // Otherwise, do not change p. This will be re-parsed later as the field 38276789Sdim // width. 39276789Sdim return p; 40276789Sdim} 41276789Sdim 42276789Sdimstatic bool char_is_one_of(char c, const char *s) { 43276789Sdim return !!internal_strchr(s, c); 44276789Sdim} 45276789Sdim 46276789Sdimstatic const char *maybe_parse_length_modifier(const char *p, char ll[2]) { 47276789Sdim if (char_is_one_of(*p, "jztLq")) { 48276789Sdim ll[0] = *p; 49276789Sdim ++p; 50276789Sdim } else if (*p == 'h') { 51276789Sdim ll[0] = 'h'; 52276789Sdim ++p; 53276789Sdim if (*p == 'h') { 54276789Sdim ll[1] = 'h'; 55276789Sdim ++p; 56276789Sdim } 57276789Sdim } else if (*p == 'l') { 58276789Sdim ll[0] = 'l'; 59276789Sdim ++p; 60276789Sdim if (*p == 'l') { 61276789Sdim ll[1] = 'l'; 62276789Sdim ++p; 63276789Sdim } 64276789Sdim } 65276789Sdim return p; 66276789Sdim} 67276789Sdim 68276789Sdim// Returns true if the character is an integer conversion specifier. 69276789Sdimstatic bool format_is_integer_conv(char c) { 70276789Sdim return char_is_one_of(c, "diouxXn"); 71276789Sdim} 72276789Sdim 73276789Sdim// Returns true if the character is an floating point conversion specifier. 74276789Sdimstatic bool format_is_float_conv(char c) { 75276789Sdim return char_is_one_of(c, "aAeEfFgG"); 76276789Sdim} 77276789Sdim 78276789Sdim// Returns string output character size for string-like conversions, 79276789Sdim// or 0 if the conversion is invalid. 80276789Sdimstatic int format_get_char_size(char convSpecifier, 81276789Sdim const char lengthModifier[2]) { 82276789Sdim if (char_is_one_of(convSpecifier, "CS")) { 83276789Sdim return sizeof(wchar_t); 84276789Sdim } 85276789Sdim 86276789Sdim if (char_is_one_of(convSpecifier, "cs[")) { 87276789Sdim if (lengthModifier[0] == 'l' && lengthModifier[1] == '\0') 88276789Sdim return sizeof(wchar_t); 89276789Sdim else if (lengthModifier[0] == '\0') 90276789Sdim return sizeof(char); 91276789Sdim } 92276789Sdim 93276789Sdim return 0; 94276789Sdim} 95276789Sdim 96276789Sdimenum FormatStoreSize { 97276789Sdim // Store size not known in advance; can be calculated as wcslen() of the 98276789Sdim // destination buffer. 99276789Sdim FSS_WCSLEN = -2, 100276789Sdim // Store size not known in advance; can be calculated as strlen() of the 101276789Sdim // destination buffer. 102276789Sdim FSS_STRLEN = -1, 103276789Sdim // Invalid conversion specifier. 104276789Sdim FSS_INVALID = 0 105276789Sdim}; 106276789Sdim 107276789Sdim// Returns the memory size of a format directive (if >0), or a value of 108276789Sdim// FormatStoreSize. 109276789Sdimstatic int format_get_value_size(char convSpecifier, 110276789Sdim const char lengthModifier[2], 111276789Sdim bool promote_float) { 112276789Sdim if (format_is_integer_conv(convSpecifier)) { 113276789Sdim switch (lengthModifier[0]) { 114276789Sdim case 'h': 115276789Sdim return lengthModifier[1] == 'h' ? sizeof(char) : sizeof(short); 116276789Sdim case 'l': 117276789Sdim return lengthModifier[1] == 'l' ? sizeof(long long) : sizeof(long); 118276789Sdim case 'q': 119276789Sdim return sizeof(long long); 120276789Sdim case 'L': 121276789Sdim return sizeof(long long); 122276789Sdim case 'j': 123276789Sdim return sizeof(INTMAX_T); 124276789Sdim case 'z': 125276789Sdim return sizeof(SIZE_T); 126276789Sdim case 't': 127276789Sdim return sizeof(PTRDIFF_T); 128276789Sdim case 0: 129276789Sdim return sizeof(int); 130276789Sdim default: 131276789Sdim return FSS_INVALID; 132276789Sdim } 133276789Sdim } 134276789Sdim 135276789Sdim if (format_is_float_conv(convSpecifier)) { 136276789Sdim switch (lengthModifier[0]) { 137276789Sdim case 'L': 138276789Sdim case 'q': 139276789Sdim return sizeof(long double); 140276789Sdim case 'l': 141276789Sdim return lengthModifier[1] == 'l' ? sizeof(long double) 142276789Sdim : sizeof(double); 143276789Sdim case 0: 144276789Sdim // Printf promotes floats to doubles but scanf does not 145276789Sdim return promote_float ? sizeof(double) : sizeof(float); 146276789Sdim default: 147276789Sdim return FSS_INVALID; 148276789Sdim } 149276789Sdim } 150276789Sdim 151276789Sdim if (convSpecifier == 'p') { 152276789Sdim if (lengthModifier[0] != 0) 153276789Sdim return FSS_INVALID; 154276789Sdim return sizeof(void *); 155276789Sdim } 156276789Sdim 157276789Sdim return FSS_INVALID; 158276789Sdim} 159276789Sdim 160276789Sdimstruct ScanfDirective { 161276789Sdim int argIdx; // argument index, or -1 if not specified ("%n$") 162276789Sdim int fieldWidth; 163276789Sdim const char *begin; 164276789Sdim const char *end; 165276789Sdim bool suppressed; // suppress assignment ("*") 166276789Sdim bool allocate; // allocate space ("m") 167276789Sdim char lengthModifier[2]; 168276789Sdim char convSpecifier; 169276789Sdim bool maybeGnuMalloc; 170276789Sdim}; 171276789Sdim 172276789Sdim// Parse scanf format string. If a valid directive in encountered, it is 173276789Sdim// returned in dir. This function returns the pointer to the first 174276789Sdim// unprocessed character, or 0 in case of error. 175276789Sdim// In case of the end-of-string, a pointer to the closing \0 is returned. 176276789Sdimstatic const char *scanf_parse_next(const char *p, bool allowGnuMalloc, 177276789Sdim ScanfDirective *dir) { 178276789Sdim internal_memset(dir, 0, sizeof(*dir)); 179276789Sdim dir->argIdx = -1; 180276789Sdim 181276789Sdim while (*p) { 182276789Sdim if (*p != '%') { 183276789Sdim ++p; 184276789Sdim continue; 185276789Sdim } 186276789Sdim dir->begin = p; 187276789Sdim ++p; 188276789Sdim // %% 189276789Sdim if (*p == '%') { 190276789Sdim ++p; 191276789Sdim continue; 192276789Sdim } 193276789Sdim if (*p == '\0') { 194296417Sdim return nullptr; 195276789Sdim } 196276789Sdim // %n$ 197276789Sdim p = maybe_parse_param_index(p, &dir->argIdx); 198276789Sdim CHECK(p); 199276789Sdim // * 200276789Sdim if (*p == '*') { 201276789Sdim dir->suppressed = true; 202276789Sdim ++p; 203276789Sdim } 204276789Sdim // Field width 205276789Sdim if (*p >= '0' && *p <= '9') { 206276789Sdim p = parse_number(p, &dir->fieldWidth); 207276789Sdim CHECK(p); 208276789Sdim if (dir->fieldWidth <= 0) // Width if at all must be non-zero 209296417Sdim return nullptr; 210276789Sdim } 211276789Sdim // m 212276789Sdim if (*p == 'm') { 213276789Sdim dir->allocate = true; 214276789Sdim ++p; 215276789Sdim } 216276789Sdim // Length modifier. 217276789Sdim p = maybe_parse_length_modifier(p, dir->lengthModifier); 218276789Sdim // Conversion specifier. 219276789Sdim dir->convSpecifier = *p++; 220276789Sdim // Consume %[...] expression. 221276789Sdim if (dir->convSpecifier == '[') { 222276789Sdim if (*p == '^') 223276789Sdim ++p; 224276789Sdim if (*p == ']') 225276789Sdim ++p; 226276789Sdim while (*p && *p != ']') 227276789Sdim ++p; 228276789Sdim if (*p == 0) 229296417Sdim return nullptr; // unexpected end of string 230296417Sdim // Consume the closing ']'. 231276789Sdim ++p; 232276789Sdim } 233276789Sdim // This is unfortunately ambiguous between old GNU extension 234276789Sdim // of %as, %aS and %a[...] and newer POSIX %a followed by 235276789Sdim // letters s, S or [. 236276789Sdim if (allowGnuMalloc && dir->convSpecifier == 'a' && 237276789Sdim !dir->lengthModifier[0]) { 238276789Sdim if (*p == 's' || *p == 'S') { 239276789Sdim dir->maybeGnuMalloc = true; 240276789Sdim ++p; 241276789Sdim } else if (*p == '[') { 242276789Sdim // Watch for %a[h-j%d], if % appears in the 243276789Sdim // [...] range, then we need to give up, we don't know 244276789Sdim // if scanf will parse it as POSIX %a [h-j %d ] or 245276789Sdim // GNU allocation of string with range dh-j plus %. 246276789Sdim const char *q = p + 1; 247276789Sdim if (*q == '^') 248276789Sdim ++q; 249276789Sdim if (*q == ']') 250276789Sdim ++q; 251276789Sdim while (*q && *q != ']' && *q != '%') 252276789Sdim ++q; 253276789Sdim if (*q == 0 || *q == '%') 254296417Sdim return nullptr; 255276789Sdim p = q + 1; // Consume the closing ']'. 256276789Sdim dir->maybeGnuMalloc = true; 257276789Sdim } 258276789Sdim } 259276789Sdim dir->end = p; 260276789Sdim break; 261276789Sdim } 262276789Sdim return p; 263276789Sdim} 264276789Sdim 265276789Sdimstatic int scanf_get_value_size(ScanfDirective *dir) { 266276789Sdim if (dir->allocate) { 267276789Sdim if (!char_is_one_of(dir->convSpecifier, "cCsS[")) 268276789Sdim return FSS_INVALID; 269276789Sdim return sizeof(char *); 270276789Sdim } 271276789Sdim 272276789Sdim if (dir->maybeGnuMalloc) { 273276789Sdim if (dir->convSpecifier != 'a' || dir->lengthModifier[0]) 274276789Sdim return FSS_INVALID; 275276789Sdim // This is ambiguous, so check the smaller size of char * (if it is 276276789Sdim // a GNU extension of %as, %aS or %a[...]) and float (if it is 277276789Sdim // POSIX %a followed by s, S or [ letters). 278276789Sdim return sizeof(char *) < sizeof(float) ? sizeof(char *) : sizeof(float); 279276789Sdim } 280276789Sdim 281276789Sdim if (char_is_one_of(dir->convSpecifier, "cCsS[")) { 282276789Sdim bool needsTerminator = char_is_one_of(dir->convSpecifier, "sS["); 283276789Sdim unsigned charSize = 284276789Sdim format_get_char_size(dir->convSpecifier, dir->lengthModifier); 285276789Sdim if (charSize == 0) 286276789Sdim return FSS_INVALID; 287276789Sdim if (dir->fieldWidth == 0) { 288276789Sdim if (!needsTerminator) 289276789Sdim return charSize; 290276789Sdim return (charSize == sizeof(char)) ? FSS_STRLEN : FSS_WCSLEN; 291276789Sdim } 292276789Sdim return (dir->fieldWidth + needsTerminator) * charSize; 293276789Sdim } 294276789Sdim 295276789Sdim return format_get_value_size(dir->convSpecifier, dir->lengthModifier, false); 296276789Sdim} 297276789Sdim 298276789Sdim// Common part of *scanf interceptors. 299276789Sdim// Process format string and va_list, and report all store ranges. 300276789Sdim// Stops when "consuming" n_inputs input items. 301276789Sdimstatic void scanf_common(void *ctx, int n_inputs, bool allowGnuMalloc, 302276789Sdim const char *format, va_list aq) { 303276789Sdim CHECK_GT(n_inputs, 0); 304276789Sdim const char *p = format; 305276789Sdim 306276789Sdim COMMON_INTERCEPTOR_READ_RANGE(ctx, format, internal_strlen(format) + 1); 307276789Sdim 308276789Sdim while (*p) { 309276789Sdim ScanfDirective dir; 310276789Sdim p = scanf_parse_next(p, allowGnuMalloc, &dir); 311276789Sdim if (!p) 312276789Sdim break; 313276789Sdim if (dir.convSpecifier == 0) { 314276789Sdim // This can only happen at the end of the format string. 315276789Sdim CHECK_EQ(*p, 0); 316276789Sdim break; 317276789Sdim } 318276789Sdim // Here the directive is valid. Do what it says. 319276789Sdim if (dir.argIdx != -1) { 320276789Sdim // Unsupported. 321276789Sdim break; 322276789Sdim } 323276789Sdim if (dir.suppressed) 324276789Sdim continue; 325276789Sdim int size = scanf_get_value_size(&dir); 326276789Sdim if (size == FSS_INVALID) { 327321369Sdim Report("%s: WARNING: unexpected format specifier in scanf interceptor: ", 328321369Sdim SanitizerToolName, "%.*s\n", dir.end - dir.begin, dir.begin); 329276789Sdim break; 330276789Sdim } 331276789Sdim void *argp = va_arg(aq, void *); 332276789Sdim if (dir.convSpecifier != 'n') 333276789Sdim --n_inputs; 334276789Sdim if (n_inputs < 0) 335276789Sdim break; 336276789Sdim if (size == FSS_STRLEN) { 337276789Sdim size = internal_strlen((const char *)argp) + 1; 338276789Sdim } else if (size == FSS_WCSLEN) { 339276789Sdim // FIXME: actually use wcslen() to calculate it. 340276789Sdim size = 0; 341276789Sdim } 342276789Sdim COMMON_INTERCEPTOR_WRITE_RANGE(ctx, argp, size); 343276789Sdim } 344276789Sdim} 345276789Sdim 346276789Sdim#if SANITIZER_INTERCEPT_PRINTF 347276789Sdim 348276789Sdimstruct PrintfDirective { 349276789Sdim int fieldWidth; 350276789Sdim int fieldPrecision; 351276789Sdim int argIdx; // width argument index, or -1 if not specified ("%*n$") 352276789Sdim int precisionIdx; // precision argument index, or -1 if not specified (".*n$") 353276789Sdim const char *begin; 354276789Sdim const char *end; 355276789Sdim bool starredWidth; 356276789Sdim bool starredPrecision; 357276789Sdim char lengthModifier[2]; 358276789Sdim char convSpecifier; 359276789Sdim}; 360276789Sdim 361276789Sdimstatic const char *maybe_parse_number(const char *p, int *out) { 362276789Sdim if (*p >= '0' && *p <= '9') 363276789Sdim p = parse_number(p, out); 364276789Sdim return p; 365276789Sdim} 366276789Sdim 367276789Sdimstatic const char *maybe_parse_number_or_star(const char *p, int *out, 368276789Sdim bool *star) { 369276789Sdim if (*p == '*') { 370276789Sdim *star = true; 371276789Sdim ++p; 372276789Sdim } else { 373276789Sdim *star = false; 374276789Sdim p = maybe_parse_number(p, out); 375276789Sdim } 376276789Sdim return p; 377276789Sdim} 378276789Sdim 379276789Sdim// Parse printf format string. Same as scanf_parse_next. 380276789Sdimstatic const char *printf_parse_next(const char *p, PrintfDirective *dir) { 381276789Sdim internal_memset(dir, 0, sizeof(*dir)); 382276789Sdim dir->argIdx = -1; 383276789Sdim dir->precisionIdx = -1; 384276789Sdim 385276789Sdim while (*p) { 386276789Sdim if (*p != '%') { 387276789Sdim ++p; 388276789Sdim continue; 389276789Sdim } 390276789Sdim dir->begin = p; 391276789Sdim ++p; 392276789Sdim // %% 393276789Sdim if (*p == '%') { 394276789Sdim ++p; 395276789Sdim continue; 396276789Sdim } 397276789Sdim if (*p == '\0') { 398296417Sdim return nullptr; 399276789Sdim } 400276789Sdim // %n$ 401276789Sdim p = maybe_parse_param_index(p, &dir->precisionIdx); 402276789Sdim CHECK(p); 403276789Sdim // Flags 404276789Sdim while (char_is_one_of(*p, "'-+ #0")) { 405276789Sdim ++p; 406276789Sdim } 407276789Sdim // Field width 408276789Sdim p = maybe_parse_number_or_star(p, &dir->fieldWidth, 409276789Sdim &dir->starredWidth); 410276789Sdim if (!p) 411296417Sdim return nullptr; 412276789Sdim // Precision 413276789Sdim if (*p == '.') { 414276789Sdim ++p; 415276789Sdim // Actual precision is optional (surprise!) 416276789Sdim p = maybe_parse_number_or_star(p, &dir->fieldPrecision, 417276789Sdim &dir->starredPrecision); 418276789Sdim if (!p) 419296417Sdim return nullptr; 420276789Sdim // m$ 421276789Sdim if (dir->starredPrecision) { 422276789Sdim p = maybe_parse_param_index(p, &dir->precisionIdx); 423276789Sdim CHECK(p); 424276789Sdim } 425276789Sdim } 426276789Sdim // Length modifier. 427276789Sdim p = maybe_parse_length_modifier(p, dir->lengthModifier); 428276789Sdim // Conversion specifier. 429276789Sdim dir->convSpecifier = *p++; 430276789Sdim dir->end = p; 431276789Sdim break; 432276789Sdim } 433276789Sdim return p; 434276789Sdim} 435276789Sdim 436276789Sdimstatic int printf_get_value_size(PrintfDirective *dir) { 437276789Sdim if (char_is_one_of(dir->convSpecifier, "cCsS")) { 438276789Sdim unsigned charSize = 439276789Sdim format_get_char_size(dir->convSpecifier, dir->lengthModifier); 440276789Sdim if (charSize == 0) 441276789Sdim return FSS_INVALID; 442276789Sdim if (char_is_one_of(dir->convSpecifier, "sS")) { 443276789Sdim return (charSize == sizeof(char)) ? FSS_STRLEN : FSS_WCSLEN; 444276789Sdim } 445276789Sdim return charSize; 446276789Sdim } 447276789Sdim 448276789Sdim return format_get_value_size(dir->convSpecifier, dir->lengthModifier, true); 449276789Sdim} 450276789Sdim 451276789Sdim#define SKIP_SCALAR_ARG(aq, convSpecifier, size) \ 452276789Sdim do { \ 453276789Sdim if (format_is_float_conv(convSpecifier)) { \ 454276789Sdim switch (size) { \ 455276789Sdim case 8: \ 456276789Sdim va_arg(*aq, double); \ 457276789Sdim break; \ 458276789Sdim case 12: \ 459276789Sdim va_arg(*aq, long double); \ 460276789Sdim break; \ 461276789Sdim case 16: \ 462276789Sdim va_arg(*aq, long double); \ 463276789Sdim break; \ 464276789Sdim default: \ 465276789Sdim Report("WARNING: unexpected floating-point arg size" \ 466276789Sdim " in printf interceptor: %d\n", size); \ 467276789Sdim return; \ 468276789Sdim } \ 469276789Sdim } else { \ 470276789Sdim switch (size) { \ 471276789Sdim case 1: \ 472276789Sdim case 2: \ 473276789Sdim case 4: \ 474276789Sdim va_arg(*aq, u32); \ 475276789Sdim break; \ 476276789Sdim case 8: \ 477276789Sdim va_arg(*aq, u64); \ 478276789Sdim break; \ 479276789Sdim default: \ 480276789Sdim Report("WARNING: unexpected arg size" \ 481276789Sdim " in printf interceptor: %d\n", size); \ 482276789Sdim return; \ 483276789Sdim } \ 484276789Sdim } \ 485276789Sdim } while (0) 486276789Sdim 487276789Sdim// Common part of *printf interceptors. 488276789Sdim// Process format string and va_list, and report all load ranges. 489276789Sdimstatic void printf_common(void *ctx, const char *format, va_list aq) { 490276789Sdim COMMON_INTERCEPTOR_READ_RANGE(ctx, format, internal_strlen(format) + 1); 491276789Sdim 492276789Sdim const char *p = format; 493276789Sdim 494276789Sdim while (*p) { 495276789Sdim PrintfDirective dir; 496276789Sdim p = printf_parse_next(p, &dir); 497276789Sdim if (!p) 498276789Sdim break; 499276789Sdim if (dir.convSpecifier == 0) { 500276789Sdim // This can only happen at the end of the format string. 501276789Sdim CHECK_EQ(*p, 0); 502276789Sdim break; 503276789Sdim } 504276789Sdim // Here the directive is valid. Do what it says. 505276789Sdim if (dir.argIdx != -1 || dir.precisionIdx != -1) { 506276789Sdim // Unsupported. 507276789Sdim break; 508276789Sdim } 509276789Sdim if (dir.starredWidth) { 510276789Sdim // Dynamic width 511276789Sdim SKIP_SCALAR_ARG(&aq, 'd', sizeof(int)); 512276789Sdim } 513276789Sdim if (dir.starredPrecision) { 514276789Sdim // Dynamic precision 515276789Sdim SKIP_SCALAR_ARG(&aq, 'd', sizeof(int)); 516276789Sdim } 517314564Sdim // %m does not require an argument: strlen(errno). 518314564Sdim if (dir.convSpecifier == 'm') 519314564Sdim continue; 520276789Sdim int size = printf_get_value_size(&dir); 521276789Sdim if (size == FSS_INVALID) { 522321369Sdim static int ReportedOnce; 523321369Sdim if (!ReportedOnce++) 524321369Sdim Report( 525321369Sdim "%s: WARNING: unexpected format specifier in printf " 526321369Sdim "interceptor: %.*s (reported once per process)\n", 527321369Sdim SanitizerToolName, dir.end - dir.begin, dir.begin); 528276789Sdim break; 529276789Sdim } 530276789Sdim if (dir.convSpecifier == 'n') { 531276789Sdim void *argp = va_arg(aq, void *); 532276789Sdim COMMON_INTERCEPTOR_WRITE_RANGE(ctx, argp, size); 533276789Sdim continue; 534276789Sdim } else if (size == FSS_STRLEN) { 535276789Sdim if (void *argp = va_arg(aq, void *)) { 536276789Sdim if (dir.starredPrecision) { 537276789Sdim // FIXME: properly support starred precision for strings. 538276789Sdim size = 0; 539276789Sdim } else if (dir.fieldPrecision > 0) { 540276789Sdim // Won't read more than "precision" symbols. 541276789Sdim size = internal_strnlen((const char *)argp, dir.fieldPrecision); 542276789Sdim if (size < dir.fieldPrecision) size++; 543276789Sdim } else { 544276789Sdim // Whole string will be accessed. 545276789Sdim size = internal_strlen((const char *)argp) + 1; 546276789Sdim } 547276789Sdim COMMON_INTERCEPTOR_READ_RANGE(ctx, argp, size); 548276789Sdim } 549276789Sdim } else if (size == FSS_WCSLEN) { 550276789Sdim if (void *argp = va_arg(aq, void *)) { 551276789Sdim // FIXME: Properly support wide-character strings (via wcsrtombs). 552276789Sdim size = 0; 553276789Sdim COMMON_INTERCEPTOR_READ_RANGE(ctx, argp, size); 554276789Sdim } 555276789Sdim } else { 556276789Sdim // Skip non-pointer args 557276789Sdim SKIP_SCALAR_ARG(&aq, dir.convSpecifier, size); 558276789Sdim } 559276789Sdim } 560276789Sdim} 561276789Sdim 562296417Sdim#endif // SANITIZER_INTERCEPT_PRINTF 563