/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 | } |