1 /* Test allocation failures with dynamic arrays.
2    Copyright (C) 2017-2021 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 /* This test is separate from tst-dynarray because it cannot run under
20    valgrind.  */
21 
22 #include "tst-dynarray-shared.h"
23 
24 #include <mcheck.h>
25 #include <stdio.h>
26 #include <support/check.h>
27 #include <support/support.h>
28 #include <support/xunistd.h>
29 #include <sys/mman.h>
30 #include <sys/resource.h>
31 #include <unistd.h>
32 
33 /* Data structure to fill up the heap.  */
34 struct heap_filler
35 {
36   struct heap_filler *next;
37 };
38 
39 /* Allocate objects until the heap is full.  */
40 static struct heap_filler *
fill_heap(void)41 fill_heap (void)
42 {
43   size_t pad = 4096;
44   struct heap_filler *head = NULL;
45   while (true)
46     {
47       struct heap_filler *new_head = malloc (sizeof (*new_head) + pad);
48       if (new_head == NULL)
49         {
50           if (pad > 0)
51             {
52               /* Try again with smaller allocations.  */
53               pad = 0;
54               continue;
55             }
56           else
57             break;
58         }
59       new_head->next = head;
60       head = new_head;
61     }
62   return head;
63 }
64 
65 /* Free the heap-filling allocations, so that we can continue testing
66    and detect memory leaks elsewhere.  */
67 static void
free_fill_heap(struct heap_filler * head)68 free_fill_heap (struct heap_filler *head)
69 {
70   while (head != NULL)
71     {
72       struct heap_filler *next = head->next;
73       free (head);
74       head = next;
75     }
76 }
77 
78 /* Check allocation failures for int arrays (without an element free
79    function).  */
80 static void
test_int_fail(void)81 test_int_fail (void)
82 {
83   /* Exercise failure in add/emplace.
84 
85      do_add: Use emplace (false) or add (true) to add elements.
86      do_finalize: Perform finalization at the end (instead of free).  */
87   for (int do_add = 0; do_add < 2; ++do_add)
88     for (int do_finalize = 0; do_finalize < 2; ++do_finalize)
89       {
90         struct dynarray_int dyn;
91         dynarray_int_init (&dyn);
92         size_t count = 0;
93         while (true)
94           {
95             if (do_add)
96               {
97                 dynarray_int_add (&dyn, 0);
98                 if (dynarray_int_has_failed (&dyn))
99                   break;
100               }
101             else
102               {
103                 int *place = dynarray_int_emplace (&dyn);
104                 if (place == NULL)
105                   break;
106                 TEST_VERIFY_EXIT (!dynarray_int_has_failed (&dyn));
107                 *place = 0;
108               }
109             ++count;
110           }
111         printf ("info: %s: failure after %zu elements\n", __func__, count);
112         TEST_VERIFY_EXIT (dynarray_int_has_failed (&dyn));
113         if (do_finalize)
114           {
115             struct int_array result = { (int *) (uintptr_t) -1, -1 };
116             TEST_VERIFY_EXIT (!dynarray_int_finalize (&dyn, &result));
117             TEST_VERIFY_EXIT (result.array == (int *) (uintptr_t) -1);
118             TEST_VERIFY_EXIT (result.length == (size_t) -1);
119           }
120         else
121           dynarray_int_free (&dyn);
122         CHECK_INIT_STATE (int, &dyn);
123       }
124 
125   /* Exercise failure in finalize.  */
126   for (int do_add = 0; do_add < 2; ++do_add)
127     {
128       struct dynarray_int dyn;
129       dynarray_int_init (&dyn);
130       for (unsigned int i = 0; i < 10000; ++i)
131         {
132           if (do_add)
133             {
134               dynarray_int_add (&dyn, i);
135               TEST_VERIFY_EXIT (!dynarray_int_has_failed (&dyn));
136             }
137           else
138             {
139               int *place = dynarray_int_emplace (&dyn);
140               TEST_VERIFY_EXIT (place != NULL);
141               *place = i;
142             }
143         }
144       TEST_VERIFY_EXIT (!dynarray_int_has_failed (&dyn));
145       struct heap_filler *heap_filler = fill_heap ();
146       struct int_array result = { (int *) (uintptr_t) -1, -1 };
147       TEST_VERIFY_EXIT (!dynarray_int_finalize (&dyn, &result));
148       TEST_VERIFY_EXIT (result.array == (int *) (uintptr_t) -1);
149       TEST_VERIFY_EXIT (result.length == (size_t) -1);
150       CHECK_INIT_STATE (int, &dyn);
151       free_fill_heap (heap_filler);
152     }
153 
154   /* Exercise failure in resize.  */
155   {
156     struct dynarray_int dyn;
157     dynarray_int_init (&dyn);
158     struct heap_filler *heap_filler = fill_heap ();
159     TEST_VERIFY (!dynarray_int_resize (&dyn, 1000));
160     TEST_VERIFY (dynarray_int_has_failed (&dyn));
161     free_fill_heap (heap_filler);
162 
163     dynarray_int_init (&dyn);
164     TEST_VERIFY (dynarray_int_resize (&dyn, 1));
165     heap_filler = fill_heap ();
166     TEST_VERIFY (!dynarray_int_resize (&dyn, 1000));
167     TEST_VERIFY (dynarray_int_has_failed (&dyn));
168     free_fill_heap (heap_filler);
169 
170     dynarray_int_init (&dyn);
171     TEST_VERIFY (dynarray_int_resize (&dyn, 1000));
172     heap_filler = fill_heap ();
173     TEST_VERIFY (!dynarray_int_resize (&dyn, 2000));
174     TEST_VERIFY (dynarray_int_has_failed (&dyn));
175     free_fill_heap (heap_filler);
176   }
177 }
178 
179 /* Check allocation failures for char * arrays (which automatically
180    free the pointed-to strings).  */
181 static void
test_str_fail(void)182 test_str_fail (void)
183 {
184   /* Exercise failure in add/emplace.
185 
186      do_add: Use emplace (false) or add (true) to add elements.
187      do_finalize: Perform finalization at the end (instead of free).  */
188   for (int do_add = 0; do_add < 2; ++do_add)
189     for (int do_finalize = 0; do_finalize < 2; ++do_finalize)
190       {
191         struct dynarray_str dyn;
192         dynarray_str_init (&dyn);
193         size_t count = 0;
194         while (true)
195           {
196             char **place;
197             if (do_add)
198               {
199                 dynarray_str_add (&dyn, NULL);
200                 if (dynarray_str_has_failed (&dyn))
201                   break;
202                 else
203                   place = dynarray_str_at (&dyn, dynarray_str_size (&dyn) - 1);
204               }
205             else
206               {
207                 place = dynarray_str_emplace (&dyn);
208                 if (place == NULL)
209                   break;
210               }
211             TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn));
212             TEST_VERIFY_EXIT (*place == NULL);
213             *place = strdup ("placeholder");
214             if (*place == NULL)
215               {
216                 /* Second loop to wait for failure of
217                    dynarray_str_emplace.  */
218                 while (true)
219                   {
220                     if (do_add)
221                       {
222                         dynarray_str_add (&dyn, NULL);
223                         if (dynarray_str_has_failed (&dyn))
224                           break;
225                       }
226                     else
227                       {
228                         char **place = dynarray_str_emplace (&dyn);
229                         if (place == NULL)
230                           break;
231                         TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn));
232                         *place = NULL;
233                       }
234                     ++count;
235                   }
236                 break;
237               }
238             ++count;
239           }
240         printf ("info: %s: failure after %zu elements\n", __func__, count);
241         TEST_VERIFY_EXIT (dynarray_str_has_failed (&dyn));
242         if (do_finalize)
243           {
244             struct str_array result = { (char **) (uintptr_t) -1, -1 };
245             TEST_VERIFY_EXIT (!dynarray_str_finalize (&dyn, &result));
246             TEST_VERIFY_EXIT (result.array == (char **) (uintptr_t) -1);
247             TEST_VERIFY_EXIT (result.length == (size_t) -1);
248           }
249         else
250           dynarray_str_free (&dyn);
251         TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn));
252         TEST_VERIFY_EXIT (dyn.u.dynarray_header.array == dyn.scratch);
253         TEST_VERIFY_EXIT (dynarray_str_size (&dyn) == 0);
254         TEST_VERIFY_EXIT (dyn.u.dynarray_header.allocated > 0);
255       }
256 
257   /* Exercise failure in finalize.  */
258   for (int do_add = 0; do_add < 2; ++do_add)
259     {
260       struct dynarray_str dyn;
261       dynarray_str_init (&dyn);
262       for (unsigned int i = 0; i < 1000; ++i)
263         {
264           if (do_add)
265             dynarray_str_add (&dyn, xstrdup ("placeholder"));
266           else
267             {
268               char **place = dynarray_str_emplace (&dyn);
269               TEST_VERIFY_EXIT (place != NULL);
270               TEST_VERIFY_EXIT (*place == NULL);
271               *place = xstrdup ("placeholder");
272             }
273         }
274       TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn));
275       struct heap_filler *heap_filler = fill_heap ();
276       struct str_array result = { (char **) (uintptr_t) -1, -1 };
277       TEST_VERIFY_EXIT (!dynarray_str_finalize (&dyn, &result));
278       TEST_VERIFY_EXIT (result.array == (char **) (uintptr_t) -1);
279       TEST_VERIFY_EXIT (result.length == (size_t) -1);
280       TEST_VERIFY_EXIT (!dynarray_str_has_failed (&dyn));
281       TEST_VERIFY_EXIT (dyn.u.dynarray_header.array == dyn.scratch);
282       TEST_VERIFY_EXIT (dynarray_str_size (&dyn) == 0);
283       TEST_VERIFY_EXIT (dyn.u.dynarray_header.allocated > 0);
284       free_fill_heap (heap_filler);
285     }
286 
287   /* Exercise failure in resize.  */
288   {
289     struct dynarray_str dyn;
290     dynarray_str_init (&dyn);
291     struct heap_filler *heap_filler = fill_heap ();
292     TEST_VERIFY (!dynarray_str_resize (&dyn, 1000));
293     TEST_VERIFY (dynarray_str_has_failed (&dyn));
294     free_fill_heap (heap_filler);
295 
296     dynarray_str_init (&dyn);
297     TEST_VERIFY (dynarray_str_resize (&dyn, 1));
298     *dynarray_str_at (&dyn, 0) = xstrdup ("allocated");
299     heap_filler = fill_heap ();
300     TEST_VERIFY (!dynarray_str_resize (&dyn, 1000));
301     TEST_VERIFY (dynarray_str_has_failed (&dyn));
302     free_fill_heap (heap_filler);
303 
304     dynarray_str_init (&dyn);
305     TEST_VERIFY (dynarray_str_resize (&dyn, 1000));
306     *dynarray_str_at (&dyn, 0) = xstrdup ("allocated");
307     heap_filler = fill_heap ();
308     TEST_VERIFY (!dynarray_str_resize (&dyn, 2000));
309     TEST_VERIFY (dynarray_str_has_failed (&dyn));
310     free_fill_heap (heap_filler);
311   }
312 }
313 
314 /* Test if mmap can allocate a page.  This is necessary because
315    setrlimit does not fail even if it reduces the RLIMIT_AS limit
316    below what is currently needed by the process.  */
317 static bool
mmap_works(void)318 mmap_works (void)
319 {
320   void *ptr =  mmap (NULL, 1, PROT_READ | PROT_WRITE,
321                      MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
322   if (ptr == MAP_FAILED)
323     return false;
324   xmunmap (ptr, 1);
325   return true;
326 }
327 
328 /* Set the RLIMIT_AS limit to the value in *LIMIT.  */
329 static void
xsetrlimit_as(const struct rlimit * limit)330 xsetrlimit_as (const struct rlimit *limit)
331 {
332   if (setrlimit (RLIMIT_AS, limit) != 0)
333     FAIL_EXIT1 ("setrlimit (RLIMIT_AS, %lu): %m",
334                 (unsigned long) limit->rlim_cur);
335 }
336 
337 /* Approximately this many bytes can be allocated after
338    reduce_rlimit_as has run.  */
339 enum { as_limit_reserve = 2 * 1024 * 1024 };
340 
341 /* Limit the size of the process, so that memory allocation in
342    allocate_thread will eventually fail, without impacting the entire
343    system.  By default, a dynamic limit which leaves room for 2 MiB is
344    activated.  The TEST_RLIMIT_AS environment variable overrides
345    it.  */
346 static void
reduce_rlimit_as(void)347 reduce_rlimit_as (void)
348 {
349   struct rlimit limit;
350   if (getrlimit (RLIMIT_AS, &limit) != 0)
351     FAIL_EXIT1 ("getrlimit (RLIMIT_AS) failed: %m");
352 
353   /* Use the TEST_RLIMIT_AS setting if available.  */
354   {
355     long target = 0;
356     const char *variable = "TEST_RLIMIT_AS";
357     const char *target_str = getenv (variable);
358     if (target_str != NULL)
359       {
360         target = atoi (target_str);
361         if (target <= 0)
362           FAIL_EXIT1 ("invalid %s value: \"%s\"", variable, target_str);
363         printf ("info: setting RLIMIT_AS to %ld MiB\n", target);
364         target *= 1024 * 1024;      /* Convert to megabytes.  */
365         limit.rlim_cur = target;
366         xsetrlimit_as (&limit);
367         return;
368       }
369   }
370 
371   /* Otherwise, try to find the limit with a binary search.  */
372   unsigned long low = 1 << 20;
373   limit.rlim_cur = low;
374   xsetrlimit_as (&limit);
375 
376   /* Find working upper limit.  */
377   unsigned long high = 1 << 30;
378   while (true)
379     {
380       limit.rlim_cur = high;
381       xsetrlimit_as (&limit);
382       if (mmap_works ())
383         break;
384       if (2 * high < high)
385         FAIL_EXIT1 ("cannot find upper AS limit");
386       high *= 2;
387     }
388 
389   /* Perform binary search.  */
390   while ((high - low) > 128 * 1024)
391     {
392       unsigned long middle = (low + high) / 2;
393       limit.rlim_cur = middle;
394       xsetrlimit_as (&limit);
395       if (mmap_works ())
396         high = middle;
397       else
398         low = middle;
399     }
400 
401   unsigned long target = high + as_limit_reserve;
402   limit.rlim_cur = target;
403   xsetrlimit_as (&limit);
404   printf ("info: RLIMIT_AS limit: %lu bytes\n", target);
405 }
406 
407 static int
do_test(void)408 do_test (void)
409 {
410   mtrace ();
411   reduce_rlimit_as ();
412   test_int_fail ();
413   test_str_fail ();
414   return 0;
415 }
416 
417 #define TIMEOUT 90
418 #include <support/test-driver.c>
419