Coverage Report

Created: 2026-08-13 06:31

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) Dovecot authors, see top-level 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
132k
{
41
132k
  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
132k
  } 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
33
    unsigned int i = 0;
52
33
    do {
53
33
      *p += 1;
54
33
      if (++i > 4)
55
0
        return FALSE;
56
33
    } while (**p >= '0' && **p <= '9');
57
33
  }
58
132k
  return TRUE;
59
132k
}
60
61
static const char *
62
printf_format_fix_noalloc(const char *format, size_t *len_r)
63
114k
{
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
114k
  const char printf_flags[] = "#0- +";
77
  /* As a tiny optimization keep the most commonly used conversion
78
     specifiers first, so strchr() stops early. */
79
114k
  static const char *printf_specifiers = "sudcixXpoeEfFgGaA";
80
114k
  const char *ret, *p, *p2;
81
114k
  char *flag;
82
83
114k
  p = ret = format;
84
246k
  while ((p2 = strchr(p, '%')) != NULL) {
85
132k
    const unsigned int start_pos = p2 - format;
86
87
132k
    p = p2+1;
88
132k
    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
132k
    bool printf_flags_seen[N_ELEMENTS(printf_flags)] = { FALSE, };
99
132k
    while (*p != '\0' &&
100
132k
           (flag = strchr(printf_flags, *p)) != NULL) {
101
33
      unsigned int flag_idx = flag - printf_flags;
102
103
33
      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
33
      printf_flags_seen[flag_idx] = TRUE;
108
33
      p++;
109
33
    }
110
111
    /* 2) Optional minimum field width */
112
132k
    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
132k
    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
132k
    switch (*p) {
128
0
    case 'h':
129
0
      if (*++p == 'h')
130
0
        p++;
131
0
      break;
132
6.99k
    case 'l':
133
6.99k
      if (*++p == 'l')
134
6.99k
        p++;
135
6.99k
      break;
136
0
    case 'L':
137
0
    case 'j':
138
0
    case 'z':
139
0
    case 't':
140
0
      p++;
141
0
      break;
142
132k
    }
143
144
    /* 5) conversion specifier */
145
132k
    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
132k
    p++;
163
132k
  }
164
165
114k
  if (ret == format)
166
114k
    *len_r = p - format + strlen(p);
167
114k
  return ret;
168
114k
}
169
170
const char *printf_format_fix_get_len(const char *format, size_t *len_r)
171
105k
{
172
105k
  const char *ret;
173
174
105k
  ret = printf_format_fix_noalloc(format, len_r);
175
105k
  if (ret != format)
176
0
    t_buffer_alloc(*len_r + 1);
177
105k
  return ret;
178
105k
}
179
180
const char *printf_format_fix(const char *format)
181
1.72k
{
182
1.72k
  const char *ret;
183
1.72k
  size_t len;
184
185
1.72k
  ret = printf_format_fix_noalloc(format, &len);
186
1.72k
  if (ret != format)
187
0
    t_buffer_alloc(len + 1);
188
1.72k
  return ret;
189
1.72k
}
190
191
const char *printf_format_fix_unsafe(const char *format)
192
6.99k
{
193
6.99k
  size_t len;
194
195
6.99k
  return printf_format_fix_noalloc(format, &len);
196
6.99k
}