brintos

brintos / llvm-project-archived public Read only

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