1 /* PR middle-end/81117 - Improve buffer overflow checking in strncpy
2 { dg-do compile }
3 { dg-options "-O2 -Wstringop-truncation -Wno-stringop-overflow -ftrack-macro-expansion=0" }
4 { dg-require-effective-target alloca } */
5
6
7 typedef __SIZE_TYPE__ size_t;
8
9 #if __cplusplus
10 extern "C" {
11 #endif
12
13 size_t strlen (const char*);
14 char* strncat (char*, const char*, size_t);
15 char* strncpy (char*, const char*, size_t);
16
17 #if __cplusplus
18 }
19 #endif
20
21 static size_t unsigned_value (void)
22 {
23 extern volatile size_t unsigned_value_source;
24 return unsigned_value_source;
25 }
26
27 static size_t unsigned_range (size_t min, size_t max)
28 {
29 size_t val = unsigned_value ();
30 return val < min || max < val ? min : val;
31 }
32
33 #define UR(min, max) unsigned_range (min, max)
34
35 void sink (void*);
36
37 #define S4 "123"
38 const char a4[] = "123";
39
40 #define CHOOSE(a, b) (unsigned_value () & 1 ? a : b)
41
42
43 typedef struct Dest
44 {
45 char a5[5];
46 char b7[7];
47 char c3ns[3] __attribute__ ((nonstring));
48 } Dest;
49
50 char dst7[7];
51 char dst2_5[2][5];
52
53 /* Verify strncat warnings for arrays of known bounds. */
54
55 void test_strncat_array (Dest *pd)
56 {
57 #define CAT(d, s, len) (strncat ((d), (s), (len)), sink (d))
58
59 CAT (dst7, S4, 2); /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
60
61 CAT (dst7, a4, 1); /* { dg-warning "output truncated copying 1 byte from a string of length 3" } */
62
63 /* There is no truncation here but possible overflow so these
64 are diagnosed by -Wstringop-overflow:
65 CAT (dst7, S4, 3);
66 CAT (dst7, a4, 3);
67 */
68
69 CAT (pd->a5, S4, 2); /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
70 CAT (pd->a5, S4, 1); /* { dg-warning "output truncated copying 1 byte from a string of length 3" } */
71 }
72
73 /* Verify strncat warnings for arrays of known bounds and a non-const
74 character count in some range. */
75
76 void test_strncat_array_range (Dest *pd)
77 {
78 CAT (dst7, S4, UR (0, 1)); /* { dg-warning "output truncated copying between 0 and 1 bytes from a string of length 3" } */
79 CAT (dst7, S4, UR (0, 2)); /* { dg-warning "output truncated copying between 0 and 2 bytes from a string of length 3" } */
80 CAT (dst7, S4, UR (1, 3)); /* { dg-warning "output truncated copying between 1 and 3 bytes from a string of length 3" } */
81 CAT (dst7, S4, UR (2, 4)); /* { dg-warning "output may be truncated copying between 2 and 4 bytes from a string of length 3" } */
82
83 CAT (dst7, S4, UR (0, 7));
84 CAT (dst7, S4, UR (1, 7));
85 CAT (dst7, S4, UR (6, 7));
86
87 CAT (dst7, S4, UR (0, 99));
88
89 CAT (dst7, S4, UR (0, 99));
90 }
91
92 /* Verify strncat warnings for arrays of unknown bounds. */
93
94 void test_strncat_vla (char *d, unsigned n)
95 {
96 CAT (d, S4, 2); /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
97 CAT (d, S4, 4);
98
99 CAT (d, a4, 2); /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
100
101 /* There is no truncation here but possible overflow so these
102 are diagnosed by -Wstringop-overflow:
103 CAT (d, S4, 3);
104 CAT (d, a4, 3);
105 */
106 CAT (d, a4, 4);
107
108 char vla[n];
109
110 CAT (vla, S4, 2); /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
111
112 CAT (vla, S4, 4);
113 CAT (vla, S4, n);
114
115 CAT (vla, a4, 2); /* { dg-warning "output truncated copying 2 bytes from a string of length 3" } */
116
117 CAT (vla, a4, 4);
118 CAT (vla, a4, n);
119
120 CAT (d, vla, 1);
121 CAT (d, vla, 3);
122 CAT (d, vla, 4);
123 CAT (d, vla, n);
124
125 /* There is no truncation here but possible overflow so these
126 are diagnosed by -Wstringop-overflow:
127 CAT (vla, S4, 3);
128 CAT (vla, a4, 3);
129 */
130 }
131
132 /* Verify strncpy warnings with at least one pointer to an object
133 or string of unknown size (destination) or length (source). */
134
135 void test_strncpy_ptr (char *d, const char* s, const char *t, int i)
136 {
137 #define CPY(d, s, len) (strncpy ((d), (s), (len)), sink (d))
138
139 /* Strncpy doesn't nul-terminate so the following is diagnosed. */
140 CPY (d, "", 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
141 CPY (d, s, 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
142
143 /* This is safe. */
144 CPY (d, "", 1);
145 CPY (d, "", 2);
146
147 /* This could be safe. */
148 CPY (d, s, 1);
149 CPY (d, s, 2);
150
151 /* Truncation. */
152 CPY (d, "123", 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 3" } */
153 CPY (d, "123", 2); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 2 bytes from a string of length 3" } */
154 CPY (d, "123", 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
155 CPY (d, "123", 4);
156 CPY (d, "123", 9);
157
158 CPY (d, S4, sizeof S4); /* Covered by -Wsizeof-pointer-memaccess. */
159 CPY (d, S4, sizeof S4 - 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
160
161 CPY (d, a4, sizeof a4); /* Covered by -Wsizeof-pointer-memaccess. */
162 CPY (d, a4, sizeof a4 - 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
163 CPY (d, a4, sizeof a4 - 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 3" } */
164 CPY (d, a4, sizeof a4 - 4); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes from a string of length 3" } */
165
166 CPY (d, S4, strlen (S4)); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
167 /* Likely buggy but no truncation. Diagnosed by -Wstringop-overflow. */
168 CPY (d, a4, strlen (a4) + 1);
169 CPY (d, S4, strlen (S4) + i);
170
171 CPY (d, a4, strlen (a4)); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
172 /* As above, buggy but no evidence of truncation. */
173 CPY (d, S4, strlen (S4) + 1);
174
175 CPY (d, CHOOSE ("", "1"), 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
176 CPY (d, CHOOSE ("1", "12"), 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
177
178 CPY (d, CHOOSE ("", "1"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output may be truncated copying 1 byte from a string of length 1" } */
179 CPY (d, CHOOSE ("1", ""), 1); /* { dg-warning ".strncpy\[^\n\r\]* output may be truncated copying 1 byte from a string of length 1" } */
180 CPY (d, CHOOSE (s, "1"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output may be truncated copying 1 byte from a string of length 1" } */
181 CPY (d, CHOOSE (s, t), 1);
182
183 CPY (d, CHOOSE ("", "1"), 2);
184 CPY (d, CHOOSE ("1", ""), 2);
185 CPY (d, CHOOSE ("1", "2"), 2);
186 CPY (d, CHOOSE ("1", s), 2);
187 CPY (d, CHOOSE (s, "1"), 2);
188 CPY (d, CHOOSE (s, t), 2);
189
190 CPY (d, CHOOSE ("", "123"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output may be truncated copying 1 byte from a string of length 3" } */
191 CPY (d, CHOOSE ("1", "123"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 1 byte from a string of the same length" } */
192 CPY (d, CHOOSE ("12", "123"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 2" } */
193 CPY (d, CHOOSE ("123", "12"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 2" } */
194
195 {
196 signed char n = strlen (s); /* { dg-message "length computed here" } */
197 CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
198 }
199
200 {
201 short n = strlen (s); /* { dg-message "length computed here" } */
202 CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
203 }
204
205 {
206 int n = strlen (s); /* { dg-message "length computed here" } */
207 CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
208 }
209
210 {
211 unsigned n = strlen (s); /* { dg-message "length computed here" } */
212 CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
213 }
214
215 {
216 size_t n;
217 n = strlen (s); /* { dg-message "length computed here" } */
218 CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
219 }
220
221 {
222 size_t n;
223 char *dp2 = d + 1;
224 n = strlen (s); /* { dg-message "length computed here" } */
225 CPY (dp2, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
226 }
227
228 {
229 /* The following truncates the terminating nul. The warning should
230 say that but doesn't. */
231 int n;
232 n = strlen (s) - 1;
233 CPY (d, s, n); /* { dg-warning "\\\[-Wstringop-truncation" } */
234 }
235
236 {
237 /* Same as above. */
238 size_t n;
239 n = strlen (s) - 1;
240 CPY (d, s, n); /* { dg-warning "\\\[-Wstringop-truncation" } */
241 }
242
243 {
244 size_t n = strlen (s) - strlen (s);
245 CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
246 }
247
248 {
249 /* This use of strncpy is dubious but it's probably not worth
250 worrying about (truncation may not actually take place when
251 i is the result). It is diagnosed with -Wstringop-overflow
252 (although more by accident than by design).
253
254 size_t n = i < strlen (s) ? i : strlen (s);
255 CPY (d, s, n);
256 */
257 }
258 }
259
260
261 /* Verify strncpy warnings for arrays of known bounds. */
262
263 void test_strncpy_array (Dest *pd, int i, const char* s)
264 {
265 #undef CPY
266 #define CPY(d, s, len) (strncpy ((d), (s), (len)), sink (d))
267
268 CPY (dst7, s, 7); /* { dg-warning "specified bound 7 equals destination size" } */
269 CPY (dst7, s, sizeof dst7); /* { dg-warning "specified bound 7 equals destination size" } */
270
271 CPY (dst2_5[0], s, sizeof dst2_5[0]); /* { dg-warning "specified bound 5 equals destination size" "bug 77293" } */
272 CPY (dst2_5[1], s, sizeof dst2_5[1]); /* { dg-warning "specified bound 5 equals destination size" } */
273
274 /* Verify that copies that nul-terminate are not diagnosed. */
275 CPY (dst7, "", sizeof dst7);
276 CPY (dst7 + 6, "", sizeof dst7 - 6);
277 CPY (dst7, "1", sizeof dst7);
278 CPY (dst7 + 1, "1", sizeof dst7 - 1);
279 CPY (dst7, "123456", sizeof dst7);
280 CPY (dst7 + 1, "12345", sizeof dst7 - 1);
281
282 CPY (dst7 + i, s, 6);
283 CPY (dst7 + i, s, 7); /* { dg-warning "specified bound 7 equals destination size" } */
284 /* The following two calls are diagnosed by -Wstringop-overflow. */
285 CPY (dst7 + i, s, 8);
286 CPY (dst7 + i, s, UR (8, 9));
287
288 /* No nul-termination here. */
289 CPY (dst7 + 2, "12345", sizeof dst7 - 2); /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
290
291 /* Because strnlen appends as many NULs as necessary to write the specified
292 number of byts the following doesn't (necessarily) truncate but rather
293 overflow, and so is diagnosed by -Wstringop-overflow. */
294 CPY (dst7, s, 8);
295
296 CPY (dst7 + 1, s, 6); /* { dg-warning "specified bound 6 equals destination size" } */
297 CPY (dst7 + 6, s, 1); /* { dg-warning "specified bound 1 equals destination size" } */
298
299 CPY (pd->a5, s, 5); /* { dg-warning "specified bound 5 equals destination size" } */
300 CPY (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
301
302 CPY (pd->a5 + i, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" "member array" } */
303
304 /* Verify that a copy that nul-terminates is not diagnosed. */
305 CPY (pd->a5, "1234", sizeof pd->a5);
306
307 /* Same above, diagnosed by -Wstringop-overflow. */
308 CPY (pd->a5, s, 6);
309
310 /* Exercise destination with attribute "nonstring". */
311 CPY (pd->c3ns, "", 3);
312 CPY (pd->c3ns, "", 1);
313 /* It could be argued that truncation in the literal case should be
314 diagnosed even for non-strings. Using strncpy in this case is
315 pointless and should be replaced with memcpy. But it would likely
316 be viewed as a false positive. */
317 CPY (pd->c3ns, "12", 1);
318 CPY (pd->c3ns, "12", 2);
319 CPY (pd->c3ns, "12", 3);
320 CPY (pd->c3ns, "123", 3);
321 CPY (pd->c3ns, s, 3);
322 CPY (pd->c3ns, s, sizeof pd->c3ns);
323
324 /* Verify that the idiom of calling strncpy with a bound equal to
325 the size of the destination (and thus potentially without NUL-
326 terminating it) immediately followed by setting the last element
327 of the array to NUL is not diagnosed. */
328 {
329 /* This might be better written using memcpy() but it's safe so
330 it shouldn't be diagnosed. */
331 strncpy (dst7, "0123456", sizeof dst7); /* { dg-bogus "\\\[-Wstringop-truncation]" } */
332 dst7[sizeof dst7 - 1] = '\0';
333 sink (dst7);
334 }
335
336 {
337 const char a[] = "0123456789";
338 strncpy (dst7, a, sizeof dst7);
339 dst7[sizeof dst7 - 1] = '\0';
340 sink (dst7);
341 }
342
343 {
344 strncpy (dst7, s, sizeof dst7);
345 dst7[sizeof dst7 - 1] = '\0';
346 sink (dst7);
347 }
348
349 {
350 strncpy (pd->a5, "01234", sizeof pd->a5); /* { dg-bogus "\\\[-Wstringop-truncation]" } */
351 pd->a5[sizeof pd->a5 - 1] = '\0';
352 sink (pd);
353 }
354
355 {
356 strncpy (pd->a5, s, sizeof pd->a5);
357 pd->a5[sizeof pd->a5 - 1] = '\0';
358 sink (pd);
359 }
360
361 {
362 unsigned n = 7;
363 char *p = (char*)__builtin_malloc (n);
364 strncpy (p, s, n);
365 p[n - 1] = '\0';
366 sink (p);
367 }
368
369 {
370 /* This should be diagnosed because the NUL-termination doesn't
371 immediately follow the strncpy call (sink may expect pd->a5
372 to be NUL-terminated). */
373 strncpy (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
374 sink (pd);
375 pd->a5[sizeof pd->a5] = '\0';
376 sink (pd);
377 }
378 }
379
380 typedef struct Flex
381 {
382 size_t n;
383 char a0[0];
384 char ax[];
385 } Flex;
386
387 extern char array[];
388
389 /* Verify that no warning is issued for array of unknown bound, flexible
390 array members, or zero-length arrays, except when the source is definitely
391 truncated. */
392
393 void test_strncpy_flexarray (Flex *pf, const char* s)
394 {
395 #undef CPY
396 #define CPY(d, s, len) (strncpy ((d), (s), (len)), sink (d))
397
398 CPY (array, "12345", 7);
399 CPY (array, "12345", 123);
400
401 CPY (array, s, 7);
402 CPY (array, s, 123);
403
404 CPY (pf->a0, s, 1);
405 CPY (pf->a0, s, 1234);
406
407 CPY (pf->a0, "", 1);
408 CPY (pf->a0, "12345", 5); /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
409 CPY (pf->a0, "12345", 1234);
410
411 CPY (pf->ax, s, 5);
412 CPY (pf->ax, s, 12345);
413
414 CPY (pf->ax, "1234", 5);
415 CPY (pf->ax, "12345", 5); /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
416 CPY (pf->ax, "12345", 12345);
417 }
418
419 /* Verify warnings for dynamically allocated objects. */
420
421 void test_strncpy_alloc (const char* s)
422 {
423 size_t n = 7;
424 char *d = (char *)__builtin_malloc (n);
425
426 CPY (d, s, n); /* { dg-warning "specified bound 7 equals destination size" } */
427
428 Dest *pd = (Dest *)__builtin_malloc (sizeof *pd * n);
429 CPY (pd->a5, s, 5); /* { dg-warning "specified bound 5 equals destination size" } */
430 CPY (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
431 }
432
433 /* Verify warnings for VLAs. */
434
435 void test_strncpy_vla (unsigned n, const char* s)
436 {
437 char vla[n];
438 CPY (vla, s, 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
439
440 CPY (vla, s, 1);
441 CPY (vla, s, 2);
442 CPY (vla, s, n);
443
444 CPY (vla, "", 0); /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
445 CPY (vla, "", 1);
446 CPY (vla, S4, 3); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
447 CPY (vla, S4, n);
448 }