/* Test regcomp bracket parsing with injected allocation failures (bug 33185). Copyright (C) 2025 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, see . */ /* This test invokes regcomp multiple times, failing one memory allocation in each call. The function call should fail with REG_ESPACE (or succeed if it can recover from the allocation failure). Previously, there was double-free bug. */ #include #include #include #include #include #include #include /* Data structure allocated via MAP_SHARED, so that writes from the subprocess are visible. */ struct shared_data { /* Number of tracked allocations performed so far. */ volatile unsigned int allocation_count; /* If this number is reached, one allocation fails. */ volatile unsigned int failing_allocation; /* The subprocess stores the expected name here. */ char name[100]; }; /* Allocation count in shared mapping. */ static struct shared_data *shared; /* Returns true if a failure should be injected for this allocation. */ static bool fail_this_allocation (void) { if (shared != NULL) { unsigned int count = shared->allocation_count; shared->allocation_count = count + 1; return count == shared->failing_allocation; } else return false; } /* Failure-injecting wrappers for allocation functions used by glibc. */ void * malloc (size_t size) { if (fail_this_allocation ()) { errno = ENOMEM; return NULL; } extern __typeof (malloc) __libc_malloc; return __libc_malloc (size); } void * calloc (size_t a, size_t b) { if (fail_this_allocation ()) { errno = ENOMEM; return NULL; } extern __typeof (calloc) __libc_calloc; return __libc_calloc (a, b); } void * realloc (void *ptr, size_t size) { if (fail_this_allocation ()) { errno = ENOMEM; return NULL; } extern __typeof (realloc) __libc_realloc; return __libc_realloc (ptr, size); } /* No-op subprocess to verify that support_isolate_in_subprocess does not perform any heap allocations. */ static void no_op (void *ignored) { } /* Perform a regcomp call in a subprocess. Used to count its allocations. */ static void initialize (void *regexp1) { const char *regexp = regexp1; shared->allocation_count = 0; regex_t reg; TEST_COMPARE (regcomp (®, regexp, 0), 0); } /* Perform regcomp in a subprocess with fault injection. */ static void test_in_subprocess (void *regexp1) { const char *regexp = regexp1; unsigned int inject_at = shared->failing_allocation; regex_t reg; int ret = regcomp (®, regexp, 0); if (ret != 0) { TEST_COMPARE (ret, REG_ESPACE); printf ("info: allocation %u failure results in return value %d," " error %s (%d)\n", inject_at, ret, strerrorname_np (errno), errno); } } static int do_test (void) { char regexp[] = "[:alpha:]"; shared = support_shared_allocate (sizeof (*shared)); /* Disable fault injection. */ shared->failing_allocation = ~0U; support_isolate_in_subprocess (no_op, NULL); TEST_COMPARE (shared->allocation_count, 0); support_isolate_in_subprocess (initialize, regexp); /* The number of allocations in the successful case, plus some slack. Once the number of expected allocations is exceeded, injecting further failures does not make a difference. */ unsigned int maximum_allocation_count = shared->allocation_count; printf ("info: successful call performs %u allocations\n", maximum_allocation_count); maximum_allocation_count += 10; for (unsigned int inject_at = 0; inject_at <= maximum_allocation_count; ++inject_at) { shared->allocation_count = 0; shared->failing_allocation = inject_at; support_isolate_in_subprocess (test_in_subprocess, regexp); } support_shared_free (shared); return 0; } #include