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