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 
unsigned_value(void)21 static size_t unsigned_value (void)
22 {
23   extern volatile size_t unsigned_value_source;
24   return unsigned_value_source;
25 }
26 
unsigned_range(size_t min,size_t max)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 
test_strncat_array(Dest * pd)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 
test_strncat_array_range(Dest * pd)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 
test_strncat_vla(char * d,unsigned n)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 
test_strncpy_ptr(char * d,const char * s,const char * t,int i)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 is likely buggy but there's no apparent truncation
230        so it's not diagnosed by -Wstringop-truncation.  Instead, it is
231        diagnosed by -Wstringop-overflow (tested elsewhere).  */
232     int n;
233     n = strlen (s) - 1;
234     CPY (d, s, n);
235   }
236 
237   {
238     /* Same as above.  */
239     size_t n;
240     n = strlen (s) - 1;
241     CPY (d, s, n);
242   }
243 
244   {
245     size_t n = strlen (s) - strlen (s);
246     CPY (d, s, n);                  /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
247   }
248 
249   {
250     /* This use of strncpy is dubious but it's probably not worth
251        worrying about (truncation may not actually take place when
252        i is the result).  It is diagnosed with -Wstringop-overflow
253        (although more by accident than by design).
254 
255        size_t n = i < strlen (s) ? i : strlen (s);
256        CPY (d, s, n);
257     */
258   }
259 }
260 
261 
262 /* Verify strncpy warnings for arrays of known bounds.  */
263 
test_strncpy_array(Dest * pd,int i,const char * s)264 void test_strncpy_array (Dest *pd, int i, const char* s)
265 {
266 #undef CPY
267 #define CPY(d, s, len) (strncpy ((d), (s), (len)), sink (d))
268 
269   CPY (dst7, s, 7);                 /* { dg-warning "specified bound 7 equals destination size" } */
270   CPY (dst7, s, sizeof dst7);       /* { dg-warning "specified bound 7 equals destination size" } */
271 
272   CPY (dst2_5[0], s, sizeof dst2_5[0]); /* { dg-warning "specified bound 5 equals destination size" "bug 77293" { xfail *-*-* } } */
273   CPY (dst2_5[1], s, sizeof dst2_5[1]); /* { dg-warning "specified bound 5 equals destination size" } */
274 
275   /* Verify that copies that nul-terminate are not diagnosed.  */
276   CPY (dst7,     "",       sizeof dst7);
277   CPY (dst7 + 6, "",       sizeof dst7 - 6);
278   CPY (dst7,     "1",      sizeof dst7);
279   CPY (dst7 + 1, "1",      sizeof dst7 - 1);
280   CPY (dst7,     "123456", sizeof dst7);
281   CPY (dst7 + 1, "12345",  sizeof dst7 - 1);
282 
283   CPY (dst7 + i, s,        6);
284   CPY (dst7 + i, s,        7);      /* { dg-warning "specified bound 7 equals destination size" } */
285   /* The following two calls are diagnosed by -Wstringop-overflow.  */
286   CPY (dst7 + i, s,        8);
287   CPY (dst7 + i, s,        UR (8, 9));
288 
289   /* No nul-termination here.  */
290   CPY (dst7 + 2, "12345",  sizeof dst7 - 2);    /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
291 
292   /* Because strnlen appends as many NULs as necessary to write the specified
293      number of byts the following doesn't (necessarily) truncate but rather
294      overflow, and so is diagnosed by -Wstringop-overflow.  */
295   CPY (dst7, s, 8);
296 
297   CPY (dst7 + 1, s, 6);             /* { dg-warning "specified bound 6 equals destination size" } */
298   CPY (dst7 + 6, s, 1);             /* { dg-warning "specified bound 1 equals destination size" } */
299 
300   CPY (pd->a5, s, 5);               /* { dg-warning "specified bound 5 equals destination size" } */
301   CPY (pd->a5, s, sizeof pd->a5);   /* { dg-warning "specified bound 5 equals destination size" } */
302 
303   /* The following is not yet handled.  */
304   CPY (pd->a5 + i, s, sizeof pd->a5);   /* { dg-warning "specified bound 5 equals destination size" "member array" { xfail *-*-* } } */
305 
306   /* Verify that a copy that nul-terminates is not diagnosed.  */
307   CPY (pd->a5, "1234", sizeof pd->a5);
308 
309   /* Same above, diagnosed by -Wstringop-overflow.  */
310   CPY (pd->a5, s, 6);
311 
312   /* Exercise destination with attribute "nonstring".  */
313   CPY (pd->c3ns, "", 3);
314   CPY (pd->c3ns, "", 1);
315   /* It could be argued that truncation in the literal case should be
316      diagnosed even for non-strings.  Using strncpy in this case is
317      pointless and should be replaced with memcpy.  But it would likely
318      be viewed as a false positive.  */
319   CPY (pd->c3ns, "12", 1);
320   CPY (pd->c3ns, "12", 2);
321   CPY (pd->c3ns, "12", 3);
322   CPY (pd->c3ns, "123", 3);
323   CPY (pd->c3ns, s, 3);
324   CPY (pd->c3ns, s, sizeof pd->c3ns);
325 
326   /* Verify that the idiom of calling strncpy with a bound equal to
327      the size of the destination (and thus potentially without NUL-
328      terminating it) immediately followed by setting the last element
329      of the array to NUL is not diagnosed.  */
330   {
331     /* This might be better written using memcpy() but it's safe so
332        it probably shouldn't be diagnosed.  It currently triggers
333        a warning because of bug 81704.  */
334     strncpy (dst7, "0123456", sizeof dst7);   /* { dg-bogus "\\\[-Wstringop-truncation]" "bug 81704" { xfail *-*-* } } */
335     dst7[sizeof dst7 - 1] = '\0';
336     sink (dst7);
337   }
338 
339   {
340     const char a[] = "0123456789";
341     strncpy (dst7, a, sizeof dst7);
342     dst7[sizeof dst7 - 1] = '\0';
343     sink (dst7);
344   }
345 
346   {
347     strncpy (dst7, s, sizeof dst7);
348     dst7[sizeof dst7 - 1] = '\0';
349     sink (dst7);
350   }
351 
352   {
353     strncpy (pd->a5, "01234", sizeof pd->a5);   /* { dg-bogus "\\\[-Wstringop-truncation]" "bug 81704" { xfail *-*-* } } */
354     pd->a5[sizeof pd->a5 - 1] = '\0';
355     sink (pd);
356   }
357 
358   {
359     strncpy (pd->a5, s, sizeof pd->a5);
360     pd->a5[sizeof pd->a5 - 1] = '\0';
361     sink (pd);
362   }
363 
364   {
365     unsigned n = 7;
366     char *p = (char*)__builtin_malloc (n);
367     strncpy (p, s, n);
368     p[n - 1] = '\0';
369     sink (p);
370   }
371 
372   {
373     /* This should be diagnosed because the NUL-termination doesn't
374        immediately follow the strncpy call (sink may expect pd->a5
375        to be NUL-terminated).  */
376     strncpy (pd->a5, s, sizeof pd->a5); /* { dg-warning "specified bound 5 equals destination size" } */
377     sink (pd);
378     pd->a5[sizeof pd->a5] = '\0';
379     sink (pd);
380   }
381 }
382 
383 typedef struct Flex
384 {
385   size_t n;
386   char a0[0];
387   char ax[];
388 } Flex;
389 
390 extern char array[];
391 
392 /* Verify that no warning is issued for array of unknown bound, flexible
393    array members, or zero-length arrays, except when the source is definitely
394    truncated.  */
395 
test_strncpy_flexarray(Flex * pf,const char * s)396 void test_strncpy_flexarray (Flex *pf, const char* s)
397 {
398 #undef CPY
399 #define CPY(d, s, len) (strncpy ((d), (s), (len)), sink (d))
400 
401   CPY (array, "12345", 7);
402   CPY (array, "12345", 123);
403 
404   CPY (array, s, 7);
405   CPY (array, s, 123);
406 
407   CPY (pf->a0, s, 1);
408   CPY (pf->a0, s, 1234);
409 
410   CPY (pf->a0, "",      1);
411   CPY (pf->a0, "12345", 5);          /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
412   CPY (pf->a0, "12345", 1234);
413 
414   CPY (pf->ax, s, 5);
415   CPY (pf->ax, s, 12345);
416 
417   CPY (pf->ax, "1234", 5);
418   CPY (pf->ax, "12345", 5);         /* { dg-warning "output truncated before terminating nul copying 5 bytes from a string of the same length" } */
419   CPY (pf->ax, "12345", 12345);
420 }
421 
422 /* Verify warnings for dynamically allocated objects.  */
423 
test_strncpy_alloc(const char * s)424 void test_strncpy_alloc (const char* s)
425 {
426   size_t n = 7;
427   char *d = (char *)__builtin_malloc (n);
428 
429   CPY (d, s, n);                    /* { dg-warning "specified bound 7 equals destination size" "bug 79016" { xfail *-*-* } } */
430 
431   Dest *pd = (Dest *)__builtin_malloc (sizeof *pd * n);
432   CPY (pd->a5, s, 5);               /* { dg-warning "specified bound 5 equals destination size" } */
433   CPY (pd->a5, s, sizeof pd->a5);   /* { dg-warning "specified bound 5 equals destination size" } */
434 }
435 
436 /* Verify warnings for VLAs.  */
437 
test_strncpy_vla(unsigned n,const char * s)438 void test_strncpy_vla (unsigned n, const char* s)
439 {
440   char vla[n];
441   CPY (vla, s, 0);                  /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
442 
443   CPY (vla, s, 1);
444   CPY (vla, s, 2);
445   CPY (vla, s, n);
446 
447   CPY (vla, "", 0);                 /* { dg-warning ".strncpy\[^\n\r\]* destination unchanged after copying no bytes" } */
448   CPY (vla, "", 1);
449   CPY (vla, S4, 3);                 /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying 3 bytes from a string of the same length" } */
450   CPY (vla, S4, n);
451 }
452