1/* Test allocation function behavior on allocation failure.
2 Copyright (C) 2015-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 License as
7 published by the Free Software Foundation; either version 2.1 of the
8 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; see the file COPYING.LIB. If
17 not, see <https://www.gnu.org/licenses/>. */
18
19/* This test case attempts to trigger various unusual conditions
20 related to allocation failures, notably switching to a different
21 arena, and falling back to mmap (via sysmalloc). */
22
23#include <errno.h>
24#include <malloc.h>
25#include <pthread.h>
26#include <stdbool.h>
27#include <stdint.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <sys/resource.h>
31#include <sys/wait.h>
32#include <unistd.h>
33
34/* Wrapper for calloc with an optimization barrier. */
35static void *
36__attribute__ ((noinline, noclone))
37allocate_zeroed (size_t a, size_t b)
38{
39 return calloc (nmemb: a, size: b);
40}
41
42/* System page size, as determined by sysconf (_SC_PAGE_SIZE). */
43static unsigned long page_size;
44
45/* Test parameters. */
46static size_t allocation_size;
47static size_t alignment;
48static enum {
49 with_malloc,
50 with_realloc,
51 with_aligned_alloc,
52 with_memalign,
53 with_posix_memalign,
54 with_valloc,
55 with_pvalloc,
56 with_calloc,
57 last_allocation_function = with_calloc
58} allocation_function;
59
60/* True if an allocation function uses the alignment test
61 parameter. */
62const static bool alignment_sensitive[last_allocation_function + 1] =
63 {
64 [with_aligned_alloc] = true,
65 [with_memalign] = true,
66 [with_posix_memalign] = true,
67 };
68
69/* Combined pointer/expected alignment result of an allocation
70 function. */
71struct allocate_result {
72 void *pointer;
73 size_t alignment;
74};
75
76/* Call the allocation function specified by allocation_function, with
77 allocation_size and alignment (if applicable) as arguments. No
78 alignment check. */
79static struct allocate_result
80allocate_1 (void)
81{
82 switch (allocation_function)
83 {
84 case with_malloc:
85 return (struct allocate_result)
86 {malloc (size: allocation_size), _Alignof (max_align_t)};
87 case with_realloc:
88 {
89 void *p = realloc (NULL, size: 16);
90 void *q;
91 if (p == NULL)
92 q = NULL;
93 else
94 {
95 q = realloc (ptr: p, size: allocation_size);
96 if (q == NULL)
97 free (ptr: p);
98 }
99 return (struct allocate_result) {q, _Alignof (max_align_t)};
100 }
101 case with_aligned_alloc:
102 {
103 void *p = aligned_alloc (alignment: alignment, size: allocation_size);
104 return (struct allocate_result) {p, alignment};
105 }
106 case with_memalign:
107 {
108 void *p = memalign (alignment: alignment, size: allocation_size);
109 return (struct allocate_result) {p, alignment};
110 }
111 case with_posix_memalign:
112 {
113 void *p;
114 if (posix_memalign (memptr: &p, alignment: alignment, size: allocation_size))
115 {
116 if (errno == ENOMEM)
117 p = NULL;
118 else
119 {
120 printf (format: "error: posix_memalign (p, %zu, %zu): %m\n",
121 alignment, allocation_size);
122 abort ();
123 }
124 }
125 return (struct allocate_result) {p, alignment};
126 }
127 case with_valloc:
128 {
129 void *p = valloc (size: allocation_size);
130 return (struct allocate_result) {p, page_size};
131 }
132 case with_pvalloc:
133 {
134 void *p = pvalloc (size: allocation_size);
135 return (struct allocate_result) {p, page_size};
136 }
137 case with_calloc:
138 {
139 char *p = allocate_zeroed (a: 1, b: allocation_size);
140 /* Check for non-zero bytes. */
141 if (p != NULL)
142 for (size_t i = 0; i < allocation_size; ++i)
143 if (p[i] != 0)
144 {
145 printf (format: "error: non-zero byte at offset %zu\n", i);
146 abort ();
147 }
148 return (struct allocate_result) {p, _Alignof (max_align_t)};
149 }
150 }
151 abort ();
152}
153
154/* Call allocate_1 and perform the alignment check on the result. */
155static void *
156allocate (void)
157{
158 struct allocate_result r = allocate_1 ();
159 if ((((uintptr_t) r.pointer) & (r.alignment - 1)) != 0)
160 {
161 printf (format: "error: allocation function %d, size %zu not aligned to %zu\n",
162 (int) allocation_function, allocation_size, r.alignment);
163 abort ();
164 }
165 return r.pointer;
166}
167
168/* Barriers to synchronize thread creation and termination. */
169static pthread_barrier_t start_barrier;
170static pthread_barrier_t end_barrier;
171
172/* Thread function which performs the allocation test. Called by
173 pthread_create and from the main thread. */
174static void *
175allocate_thread (void *closure)
176{
177 /* Wait for the creation of all threads. */
178 {
179 int ret = pthread_barrier_wait (barrier: &start_barrier);
180 if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD)
181 {
182 errno = ret;
183 printf (format: "error: pthread_barrier_wait: %m\n");
184 abort ();
185 }
186 }
187
188 /* Allocate until we run out of memory, creating a single-linked
189 list. */
190 struct list {
191 struct list *next;
192 };
193 struct list *head = NULL;
194 while (true)
195 {
196 struct list *e = allocate ();
197 if (e == NULL)
198 break;
199
200 e->next = head;
201 head = e;
202 }
203
204 /* Wait for the allocation of all available memory. */
205 {
206 int ret = pthread_barrier_wait (barrier: &end_barrier);
207 if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD)
208 {
209 errno = ret;
210 printf (format: "error: pthread_barrier_wait: %m\n");
211 abort ();
212 }
213 }
214
215 /* Free the allocated memory. */
216 while (head != NULL)
217 {
218 struct list *next = head->next;
219 free (ptr: head);
220 head = next;
221 }
222
223 return NULL;
224}
225
226/* Number of threads (plus the main thread. */
227enum { thread_count = 8 };
228
229/* Thread attribute to request creation of threads with a non-default
230 stack size which is rather small. This avoids interfering with the
231 configured address space limit. */
232static pthread_attr_t small_stack;
233
234/* Runs one test in multiple threads, all in a subprocess so that
235 subsequent tests do not interfere with each other. */
236static void
237run_one (void)
238{
239 /* Isolate the tests in a subprocess, so that we can start over
240 from scratch. */
241 pid_t pid = fork ();
242 if (pid == 0)
243 {
244 /* In the child process. Create the allocation threads. */
245 pthread_t threads[thread_count];
246
247 for (unsigned i = 0; i < thread_count; ++i)
248 {
249 int ret = pthread_create (newthread: threads + i, attr: &small_stack, start_routine: allocate_thread, NULL);
250 if (ret != 0)
251 {
252 errno = ret;
253 printf (format: "error: pthread_create: %m\n");
254 abort ();
255 }
256 }
257
258 /* Also run the test on the main thread. */
259 allocate_thread (NULL);
260
261 for (unsigned i = 0; i < thread_count; ++i)
262 {
263 int ret = pthread_join (th: threads[i], NULL);
264 if (ret != 0)
265 {
266 errno = ret;
267 printf (format: "error: pthread_join: %m\n");
268 abort ();
269 }
270 }
271 _exit (0);
272 }
273 else if (pid < 0)
274 {
275 printf (format: "error: fork: %m\n");
276 abort ();
277 }
278
279 /* In the parent process. Wait for the child process to exit. */
280 int status;
281 if (waitpid (pid: pid, stat_loc: &status, options: 0) < 0)
282 {
283 printf (format: "error: waitpid: %m\n");
284 abort ();
285 }
286 if (status != 0)
287 {
288 printf (format: "error: exit status %d from child process\n", status);
289 exit (1);
290 }
291}
292
293/* Run all applicable allocation functions for the current test
294 parameters. */
295static void
296run_allocation_functions (void)
297{
298 for (int af = 0; af <= last_allocation_function; ++af)
299 {
300 /* Run alignment-sensitive functions for non-default
301 alignments. */
302 if (alignment_sensitive[af] != (alignment != 0))
303 continue;
304 allocation_function = af;
305 run_one ();
306 }
307}
308
309int
310do_test (void)
311{
312 /* Limit the number of malloc arenas. We use a very low number so
313 that despute the address space limit configured below, all
314 requested arenas a can be created. */
315 if (mallopt (M_ARENA_MAX, val: 2) == 0)
316 {
317 printf (format: "error: mallopt (M_ARENA_MAX) failed\n");
318 return 1;
319 }
320
321 /* Determine the page size. */
322 {
323 long ret = sysconf (_SC_PAGE_SIZE);
324 if (ret < 0)
325 {
326 printf (format: "error: sysconf (_SC_PAGE_SIZE): %m\n");
327 return 1;
328 }
329 page_size = ret;
330 }
331
332 /* Limit the size of the process, so that memory allocation in
333 allocate_thread will eventually fail, without impacting the
334 entire system. */
335 {
336 struct rlimit limit;
337 if (getrlimit (RLIMIT_AS, rlimits: &limit) != 0)
338 {
339 printf (format: "getrlimit (RLIMIT_AS) failed: %m\n");
340 return 1;
341 }
342 long target = 200 * 1024 * 1024;
343 if (limit.rlim_cur == RLIM_INFINITY || limit.rlim_cur > target)
344 {
345 limit.rlim_cur = target;
346 if (setrlimit (RLIMIT_AS, rlimits: &limit) != 0)
347 {
348 printf (format: "setrlimit (RLIMIT_AS) failed: %m\n");
349 return 1;
350 }
351 }
352 }
353
354 /* Initialize thread attribute with a reduced stack size. */
355 {
356 int ret = pthread_attr_init (attr: &small_stack);
357 if (ret != 0)
358 {
359 errno = ret;
360 printf (format: "error: pthread_attr_init: %m\n");
361 abort ();
362 }
363 unsigned long stack_size = ((256 * 1024) / page_size) * page_size;
364 if (stack_size < 4 * page_size)
365 stack_size = 8 * page_size;
366 ret = pthread_attr_setstacksize (attr: &small_stack, stacksize: stack_size);
367 if (ret != 0)
368 {
369 errno = ret;
370 printf (format: "error: pthread_attr_setstacksize: %m\n");
371 abort ();
372 }
373 }
374
375 /* Initialize the barriers. We run thread_count threads, plus 1 for
376 the main thread. */
377 {
378 int ret = pthread_barrier_init (barrier: &start_barrier, NULL, count: thread_count + 1);
379 if (ret != 0)
380 {
381 errno = ret;
382 printf (format: "error: pthread_barrier_init: %m\n");
383 abort ();
384 }
385
386 ret = pthread_barrier_init (barrier: &end_barrier, NULL, count: thread_count + 1);
387 if (ret != 0)
388 {
389 errno = ret;
390 printf (format: "error: pthread_barrier_init: %m\n");
391 abort ();
392 }
393 }
394
395 allocation_size = 144;
396 run_allocation_functions ();
397 allocation_size = page_size;
398 run_allocation_functions ();
399
400 alignment = 128;
401 allocation_size = 512;
402 run_allocation_functions ();
403
404 allocation_size = page_size;
405 run_allocation_functions ();
406
407 allocation_size = 17 * page_size;
408 run_allocation_functions ();
409
410 /* Deallocation the barriers and the thread attribute. */
411 {
412 int ret = pthread_barrier_destroy (barrier: &end_barrier);
413 if (ret != 0)
414 {
415 errno = ret;
416 printf (format: "error: pthread_barrier_destroy: %m\n");
417 return 1;
418 }
419 ret = pthread_barrier_destroy (barrier: &start_barrier);
420 if (ret != 0)
421 {
422 errno = ret;
423 printf (format: "error: pthread_barrier_destroy: %m\n");
424 return 1;
425 }
426 ret = pthread_attr_destroy (attr: &small_stack);
427 if (ret != 0)
428 {
429 errno = ret;
430 printf (format: "error: pthread_attr_destroy: %m\n");
431 return 1;
432 }
433 }
434
435 return 0;
436}
437
438/* The repeated allocations take some time on slow machines. */
439#define TIMEOUT 100
440
441#define TEST_FUNCTION do_test ()
442#include "../test-skeleton.c"
443

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