1 //===-- sanitizer_common_interceptors_scanf.inc -----------------*- C++ -*-===//
3 // This file is distributed under the University of Illinois Open Source
4 // License. See LICENSE.TXT for details.
6 //===----------------------------------------------------------------------===//
8 // Scanf implementation for use in *Sanitizer interceptors.
9 // Follows http://pubs.opengroup.org/onlinepubs/9699919799/functions/fscanf.html
10 // with a few common GNU extensions.
12 //===----------------------------------------------------------------------===//
15 struct ScanfDirective {
16 int argIdx; // argument index, or -1 of not specified ("%n$")
18 bool suppressed; // suppress assignment ("*")
19 bool allocate; // allocate space ("m")
20 char lengthModifier[2];
25 static const char *parse_number(const char *p, int *out) {
26 *out = internal_atoll(p);
27 while (*p >= '0' && *p <= '9')
32 static bool char_is_one_of(char c, const char *s) {
33 return !!internal_strchr(s, c);
36 // Parse scanf format string. If a valid directive in encountered, it is
37 // returned in dir. This function returns the pointer to the first
38 // unprocessed character, or 0 in case of error.
39 // In case of the end-of-string, a pointer to the closing \0 is returned.
40 static const char *scanf_parse_next(const char *p, bool allowGnuMalloc,
41 ScanfDirective *dir) {
42 internal_memset(dir, 0, sizeof(*dir));
60 if (*p >= '0' && *p <= '9') {
62 const char *q = parse_number(p, &number);
67 // Otherwise, do not change p. This will be re-parsed later as the field
72 dir->suppressed = true;
76 if (*p >= '0' && *p <= '9') {
77 p = parse_number(p, &dir->fieldWidth);
78 if (dir->fieldWidth <= 0)
87 if (char_is_one_of(*p, "jztLq")) {
88 dir->lengthModifier[0] = *p;
90 } else if (*p == 'h') {
91 dir->lengthModifier[0] = 'h';
94 dir->lengthModifier[1] = 'h';
97 } else if (*p == 'l') {
98 dir->lengthModifier[0] = 'l';
101 dir->lengthModifier[1] = 'l';
105 // Conversion specifier.
106 dir->convSpecifier = *p++;
107 // Consume %[...] expression.
108 if (dir->convSpecifier == '[') {
113 while (*p && *p != ']')
116 return 0; // unexpected end of string
117 // Consume the closing ']'.
120 // This is unfortunately ambiguous between old GNU extension
121 // of %as, %aS and %a[...] and newer POSIX %a followed by
122 // letters s, S or [.
123 if (allowGnuMalloc && dir->convSpecifier == 'a' &&
124 !dir->lengthModifier[0]) {
125 if (*p == 's' || *p == 'S') {
126 dir->maybeGnuMalloc = true;
128 } else if (*p == '[') {
129 // Watch for %a[h-j%d], if % appears in the
130 // [...] range, then we need to give up, we don't know
131 // if scanf will parse it as POSIX %a [h-j %d ] or
132 // GNU allocation of string with range dh-j plus %.
133 const char *q = p + 1;
138 while (*q && *q != ']' && *q != '%')
140 if (*q == 0 || *q == '%')
142 p = q + 1; // Consume the closing ']'.
143 dir->maybeGnuMalloc = true;
151 // Returns true if the character is an integer conversion specifier.
152 static bool scanf_is_integer_conv(char c) {
153 return char_is_one_of(c, "diouxXn");
156 // Returns true if the character is an floating point conversion specifier.
157 static bool scanf_is_float_conv(char c) {
158 return char_is_one_of(c, "aAeEfFgG");
161 // Returns string output character size for string-like conversions,
162 // or 0 if the conversion is invalid.
163 static int scanf_get_char_size(ScanfDirective *dir) {
164 if (char_is_one_of(dir->convSpecifier, "CS")) {
169 if (char_is_one_of(dir->convSpecifier, "cs[")) {
170 if (dir->lengthModifier[0] == 'l')
173 else if (dir->lengthModifier[0] == 0)
182 enum ScanfStoreSize {
183 // Store size not known in advance; can be calculated as strlen() of the
184 // destination buffer.
186 // Invalid conversion specifier.
190 // Returns the store size of a scanf directive (if >0), or a value of
192 static int scanf_get_store_size(ScanfDirective *dir) {
194 if (!char_is_one_of(dir->convSpecifier, "cCsS["))
196 return sizeof(char *);
199 if (dir->maybeGnuMalloc) {
200 if (dir->convSpecifier != 'a' || dir->lengthModifier[0])
202 // This is ambiguous, so check the smaller size of char * (if it is
203 // a GNU extension of %as, %aS or %a[...]) and float (if it is
204 // POSIX %a followed by s, S or [ letters).
205 return sizeof(char *) < sizeof(float) ? sizeof(char *) : sizeof(float);
208 if (scanf_is_integer_conv(dir->convSpecifier)) {
209 switch (dir->lengthModifier[0]) {
211 return dir->lengthModifier[1] == 'h' ? sizeof(char) : sizeof(short);
213 return dir->lengthModifier[1] == 'l' ? sizeof(long long) : sizeof(long);
215 return sizeof(long long);
217 return sizeof(INTMAX_T);
219 return sizeof(SIZE_T);
221 return sizeof(PTRDIFF_T);
229 if (scanf_is_float_conv(dir->convSpecifier)) {
230 switch (dir->lengthModifier[0]) {
233 return sizeof(long double);
235 return dir->lengthModifier[1] == 'l' ? sizeof(long double)
238 return sizeof(float);
244 if (char_is_one_of(dir->convSpecifier, "sS[")) {
245 unsigned charSize = scanf_get_char_size(dir);
248 if (dir->fieldWidth == 0)
250 return (dir->fieldWidth + 1) * charSize;
253 if (char_is_one_of(dir->convSpecifier, "cC")) {
254 unsigned charSize = scanf_get_char_size(dir);
257 if (dir->fieldWidth == 0)
259 return dir->fieldWidth * charSize;
262 if (dir->convSpecifier == 'p') {
263 if (dir->lengthModifier[1] != 0)
265 return sizeof(void *);
271 // Common part of *scanf interceptors.
272 // Process format string and va_list, and report all store ranges.
273 // Stops when "consuming" n_inputs input items.
274 static void scanf_common(void *ctx, int n_inputs, bool allowGnuMalloc,
275 const char *format, va_list aq) {
276 CHECK_GT(n_inputs, 0);
277 const char *p = format;
279 while (*p && n_inputs) {
281 p = scanf_parse_next(p, allowGnuMalloc, &dir);
284 if (dir.convSpecifier == 0) {
285 // This can only happen at the end of the format string.
289 // Here the directive is valid. Do what it says.
290 if (dir.argIdx != -1) {
296 int size = scanf_get_store_size(&dir);
297 if (size == SSS_INVALID)
299 void *argp = va_arg(aq, void *);
300 if (dir.convSpecifier != 'n')
302 if (size == SSS_STRLEN) {
303 size = internal_strlen((const char *)argp) + 1;
305 COMMON_INTERCEPTOR_WRITE_RANGE(ctx, argp, size);