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