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. */ |
35 | static void * |
36 | __attribute__ ((noinline, noclone)) |
37 | allocate_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). */ |
43 | static unsigned long page_size; |
44 | |
45 | /* Test parameters. */ |
46 | static size_t allocation_size; |
47 | static size_t alignment; |
48 | static 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. */ |
62 | const 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. */ |
71 | struct 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. */ |
79 | static struct allocate_result |
80 | allocate_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. */ |
155 | static void * |
156 | allocate (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. */ |
169 | static pthread_barrier_t start_barrier; |
170 | static pthread_barrier_t end_barrier; |
171 | |
172 | /* Thread function which performs the allocation test. Called by |
173 | pthread_create and from the main thread. */ |
174 | static void * |
175 | allocate_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. */ |
227 | enum { 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. */ |
232 | static 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. */ |
236 | static void |
237 | run_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. */ |
295 | static void |
296 | run_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 | |
309 | int |
310 | do_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 | |