Coverage Report

Created: 2026-03-11 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/printf-format-fix.c
Line
Count
Source
1
/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "printf-format-fix.h"
5
6
/* Disable our memcpy() safety wrapper. This file is very performance sensitive
7
   and it's been checked to work correctly with memcpy(). */
8
#undef memcpy
9
10
static const char *
11
fix_format_real(const char *fmt, const char *p, size_t *len_r)
12
0
{
13
0
  const char *errstr;
14
0
  char *buf;
15
0
  size_t len1, len2, len3;
16
17
0
  i_assert((size_t)(p - fmt) < INT_MAX);
18
0
  i_assert(p[0] == '%' && p[1] == 'm');
19
20
0
  errstr = strerror(errno);
21
22
  /* we'll assume that there's only one %m in the format string.
23
     this simplifies the code and there's really no good reason to have
24
     it multiple times. Callers can trap this case themselves. */
25
0
  len1 = p - fmt;
26
0
  len2 = strlen(errstr);
27
0
  len3 = strlen(p + 2);
28
29
  /* @UNSAFE */
30
0
  buf = t_buffer_get(len1 + len2 + len3 + 1);
31
0
  memcpy(buf, fmt, len1);
32
0
  memcpy(buf + len1, errstr, len2);
33
0
  memcpy(buf + len1 + len2, p + 2, len3 + 1);
34
35
0
  *len_r = len1 + len2 + len3;
36
0
  return buf;
37
0
}
38
39
static bool verify_length(const char **p)
40
175k
{
41
175k
  if (**p == '*') {
42
    /* We don't bother supporting "*m$" - it's not used
43
       anywhere and seems a bit dangerous. */
44
0
    *p += 1;
45
175k
  } else if (**p >= '0' && **p <= '9') {
46
    /* Limit to 4 digits - we'll never want more than that.
47
       Some implementations might not handle long digits
48
       correctly, or maybe even could be used for DoS due
49
       to using too much CPU. If you want to express '99'
50
       as '00099', then you lose in this function. */
51
21
    unsigned int i = 0;
52
21
    do {
53
21
      *p += 1;
54
21
      if (++i > 4)
55
0
        return FALSE;
56
21
    } while (**p >= '0' && **p <= '9');
57
21
  }
58
175k
  return TRUE;
59
175k
}
60
61
static const char *
62
printf_format_fix_noalloc(const char *format, size_t *len_r)
63
148k
{
64
  /* NOTE: This function is overly strict in what it accepts. Some
65
     format strings that are valid (and safe) in C99 will cause a panic
66
     here. This is because we don't really need to support the weirdest
67
     special cases, and we're also being extra careful not to pass
68
     anything to the underlying libc printf, which might treat the string
69
     differently than us and unexpectedly handling it as %n. For example
70
     "%**%n" with glibc. */
71
72
  /* Allow only the standard C99 flags. There are also <'> and <I> flags,
73
     but we don't really need them. And at worst if they're not supported
74
     by the underlying printf, they could potentially be used to work
75
     around our restrictions. */
76
148k
  const char printf_flags[] = "#0- +";
77
  /* As a tiny optimization keep the most commonly used conversion
78
     specifiers first, so strchr() stops early. */
79
148k
  static const char *printf_specifiers = "sudcixXpoeEfFgGaA";
80
148k
  const char *ret, *p, *p2;
81
148k
  char *flag;
82
83
148k
  p = ret = format;
84
324k
  while ((p2 = strchr(p, '%')) != NULL) {
85
175k
    const unsigned int start_pos = p2 - format;
86
87
175k
    p = p2+1;
88
175k
    if (*p == '%') {
89
      /* we'll be strict and allow %% only when there are no
90
         optional flags or modifiers. */
91
0
      p++;
92
0
      continue;
93
0
    }
94
    /* 1) zero or more flags. We'll add a further restriction that
95
       each flag can be used only once, since there's no need to
96
       use them more than once, and some implementations might
97
       add their own limits. */
98
175k
    bool printf_flags_seen[N_ELEMENTS(printf_flags)] = { FALSE, };
99
175k
    while (*p != '\0' &&
100
175k
           (flag = strchr(printf_flags, *p)) != NULL) {
101
21
      unsigned int flag_idx = flag - printf_flags;
102
103
21
      if (printf_flags_seen[flag_idx]) {
104
0
        i_panic("Duplicate %% flag '%c' starting at #%u in '%s'",
105
0
          *p, start_pos, format);
106
0
      }
107
21
      printf_flags_seen[flag_idx] = TRUE;
108
21
      p++;
109
21
    }
110
111
    /* 2) Optional minimum field width */
112
175k
    if (!verify_length(&p)) {
113
0
      i_panic("Too large minimum field width starting at #%u in '%s'",
114
0
        start_pos, format);
115
0
    }
116
117
    /* 3) Optional precision */
118
175k
    if (*p == '.') {
119
0
      p++;
120
0
      if (!verify_length(&p)) {
121
0
        i_panic("Too large precision starting at #%u in '%s'",
122
0
          start_pos, format);
123
0
      }
124
0
    }
125
126
    /* 4) Optional length modifier */
127
175k
    switch (*p) {
128
0
    case 'h':
129
0
      if (*++p == 'h')
130
0
        p++;
131
0
      break;
132
8.49k
    case 'l':
133
8.49k
      if (*++p == 'l')
134
8.45k
        p++;
135
8.49k
      break;
136
0
    case 'L':
137
0
    case 'j':
138
0
    case 'z':
139
0
    case 't':
140
0
      p++;
141
0
      break;
142
175k
    }
143
144
    /* 5) conversion specifier */
145
175k
    if (*p == '\0' || strchr(printf_specifiers, *p) == NULL) {
146
0
      switch (*p) {
147
0
      case 'n':
148
0
        i_panic("%%n modifier used");
149
0
      case 'm':
150
0
        if (ret != format)
151
0
          i_panic("%%m used twice");
152
0
        ret = fix_format_real(format, p-1, len_r);
153
0
        break;
154
0
      case '\0':
155
0
        i_panic("Missing %% specifier starting at #%u in '%s'",
156
0
          start_pos, format);
157
0
      default:
158
0
        i_panic("Unsupported 0x%02x specifier starting at #%u in '%s'",
159
0
          *p, start_pos, format);
160
0
      }
161
0
    }
162
175k
    p++;
163
175k
  }
164
165
148k
  if (ret == format)
166
148k
    *len_r = p - format + strlen(p);
167
148k
  return ret;
168
148k
}
169
170
const char *printf_format_fix_get_len(const char *format, size_t *len_r)
171
137k
{
172
137k
  const char *ret;
173
174
137k
  ret = printf_format_fix_noalloc(format, len_r);
175
137k
  if (ret != format)
176
0
    t_buffer_alloc(*len_r + 1);
177
137k
  return ret;
178
137k
}
179
180
const char *printf_format_fix(const char *format)
181
2.40k
{
182
2.40k
  const char *ret;
183
2.40k
  size_t len;
184
185
2.40k
  ret = printf_format_fix_noalloc(format, &len);
186
2.40k
  if (ret != format)
187
0
    t_buffer_alloc(len + 1);
188
2.40k
  return ret;
189
2.40k
}
190
191
const char *printf_format_fix_unsafe(const char *format)
192
8.45k
{
193
8.45k
  size_t len;
194
195
8.45k
  return printf_format_fix_noalloc(format, &len);
196
8.45k
}