annotate libsanitizer/sanitizer_common/sanitizer_common_interceptors_format.inc @ 145:1830386684a0

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