1/* Test allocation failures with dynamic arrays.
2 Copyright (C) 2017-2022 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. */
34struct heap_filler
35{
36 struct heap_filler *next;
37};
38
39/* Allocate objects until the heap is full. */
40static struct heap_filler *
41fill_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 (size: 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. */
67static void
68free_fill_heap (struct heap_filler *head)
69{
70 while (head != NULL)
71 {
72 struct heap_filler *next = head->next;
73 free (ptr: head);
74 head = next;
75 }
76}
77
78/* Check allocation failures for int arrays (without an element free
79 function). */
80static void
81test_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 (list: &dyn);
92 size_t count = 0;
93 while (true)
94 {
95 if (do_add)
96 {
97 dynarray_int_add (list: &dyn, item: 0);
98 if (dynarray_int_has_failed (list: &dyn))
99 break;
100 }
101 else
102 {
103 int *place = dynarray_int_emplace (list: &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 (format: "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 (list: &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 (list: &dyn);
130 for (unsigned int i = 0; i < 10000; ++i)
131 {
132 if (do_add)
133 {
134 dynarray_int_add (list: &dyn, item: i);
135 TEST_VERIFY_EXIT (!dynarray_int_has_failed (&dyn));
136 }
137 else
138 {
139 int *place = dynarray_int_emplace (list: &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 (head: heap_filler);
152 }
153
154 /* Exercise failure in resize. */
155 {
156 struct dynarray_int dyn;
157 dynarray_int_init (list: &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 (head: heap_filler);
162
163 dynarray_int_init (list: &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 (head: heap_filler);
169
170 dynarray_int_init (list: &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 (head: heap_filler);
176 }
177}
178
179/* Check allocation failures for char * arrays (which automatically
180 free the pointed-to strings). */
181static void
182test_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 (list: &dyn);
193 size_t count = 0;
194 while (true)
195 {
196 char **place;
197 if (do_add)
198 {
199 dynarray_str_add (list: &dyn, NULL);
200 if (dynarray_str_has_failed (list: &dyn))
201 break;
202 else
203 place = dynarray_str_at (list: &dyn, index: dynarray_str_size (list: &dyn) - 1);
204 }
205 else
206 {
207 place = dynarray_str_emplace (list: &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 (s: "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 (list: &dyn, NULL);
223 if (dynarray_str_has_failed (list: &dyn))
224 break;
225 }
226 else
227 {
228 char **place = dynarray_str_emplace (list: &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 (format: "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 (list: &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 (list: &dyn);
262 for (unsigned int i = 0; i < 1000; ++i)
263 {
264 if (do_add)
265 dynarray_str_add (list: &dyn, item: xstrdup ("placeholder"));
266 else
267 {
268 char **place = dynarray_str_emplace (list: &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 (head: heap_filler);
285 }
286
287 /* Exercise failure in resize. */
288 {
289 struct dynarray_str dyn;
290 dynarray_str_init (list: &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 (head: heap_filler);
295
296 dynarray_str_init (list: &dyn);
297 TEST_VERIFY (dynarray_str_resize (&dyn, 1));
298 *dynarray_str_at (list: &dyn, index: 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 (head: heap_filler);
303
304 dynarray_str_init (list: &dyn);
305 TEST_VERIFY (dynarray_str_resize (&dyn, 1000));
306 *dynarray_str_at (list: &dyn, index: 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 (head: 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. */
317static bool
318mmap_works (void)
319{
320 void *ptr = mmap (NULL, len: 1, PROT_READ | PROT_WRITE,
321 MAP_ANONYMOUS | MAP_PRIVATE, fd: -1, offset: 0);
322 if (ptr == MAP_FAILED)
323 return false;
324 xmunmap (addr: ptr, length: 1);
325 return true;
326}
327
328/* Set the RLIMIT_AS limit to the value in *LIMIT. */
329static void
330xsetrlimit_as (const struct rlimit *limit)
331{
332 if (setrlimit (RLIMIT_AS, rlimits: 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. */
339enum { 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. */
346static void
347reduce_rlimit_as (void)
348{
349 struct rlimit limit;
350 if (getrlimit (RLIMIT_AS, rlimits: &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 (format: "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: &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: &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: &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: &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: &limit);
404 printf (format: "info: RLIMIT_AS limit: %lu bytes\n", target);
405}
406
407static int
408do_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

source code of glibc/malloc/tst-dynarray-fail.c