diff options
Diffstat (limited to 'support')
-rw-r--r-- | support/Makefile | 3 | ||||
-rw-r--r-- | support/capture_subprocess.h | 11 | ||||
-rw-r--r-- | support/check.h | 4 | ||||
-rw-r--r-- | support/check_mem_access.h | 37 | ||||
-rw-r--r-- | support/fuse.h | 1 | ||||
-rw-r--r-- | support/shell-container.c | 8 | ||||
-rw-r--r-- | support/support.h | 9 | ||||
-rw-r--r-- | support/support_capture_subprocess.c | 181 | ||||
-rw-r--r-- | support/support_fuse.c | 6 | ||||
-rw-r--r-- | support/support_mem_access.c | 64 | ||||
-rw-r--r-- | support/support_record_failure.c | 36 | ||||
-rw-r--r-- | support/support_stack_alloc.c | 9 | ||||
-rw-r--r-- | support/support_subprocess.c | 3 | ||||
-rw-r--r-- | support/support_test_main.c | 19 | ||||
-rw-r--r-- | support/test-container.c | 4 | ||||
-rw-r--r-- | support/tst-support_accept_oom.c | 115 | ||||
-rw-r--r-- | support/xfmemopen.c | 31 | ||||
-rw-r--r-- | support/xstdio.h | 1 |
18 files changed, 439 insertions, 103 deletions
diff --git a/support/Makefile b/support/Makefile index d41278e..2043e4e 100644 --- a/support/Makefile +++ b/support/Makefile @@ -66,6 +66,7 @@ libsupport-routines = \ support_format_netent \ support_fuse \ support_isolate_in_subprocess \ + support_mem_access \ support_mutex_pi_monotonic \ support_need_proc \ support_open_and_compare_file_bytes \ @@ -134,6 +135,7 @@ libsupport-routines = \ xfclose \ xfdopendir \ xfgets \ + xfmemopen \ xfopen \ xfork \ xfread \ @@ -332,6 +334,7 @@ tests = \ tst-support-open-dev-null-range \ tst-support-openpty \ tst-support-process_state \ + tst-support_accept_oom \ tst-support_blob_repeat \ tst-support_capture_subprocess \ tst-support_descriptors \ diff --git a/support/capture_subprocess.h b/support/capture_subprocess.h index 91d75e5..b37462d 100644 --- a/support/capture_subprocess.h +++ b/support/capture_subprocess.h @@ -42,11 +42,12 @@ struct support_capture_subprocess support_capture_subprocess struct support_capture_subprocess support_capture_subprogram (const char *file, char *const argv[], char *const envp[]); -/* Copy the running program into a setgid binary and run it with CHILD_ID - argument. If execution is successful, return the exit status of the child - program, otherwise return a non-zero failure exit code. */ -int support_capture_subprogram_self_sgid - (char *child_id); +/* Copy the running program into a setgid binary and run it with + CHILD_ID argument. If the program exits with a non-zero status, + exit with that exit status (or status 1 if the program did not exit + normally). If the test cannot be performed, exit with + EXIT_UNSUPPORTED. */ +void support_capture_subprogram_self_sgid (const char *child_id); /* Deallocate the subprocess data captured by support_capture_subprocess. */ diff --git a/support/check.h b/support/check.h index 49db05a..91fedae 100644 --- a/support/check.h +++ b/support/check.h @@ -196,9 +196,11 @@ void support_test_compare_string_wide (const wchar_t *left, const char *left_expr, const char *right_expr); -/* Internal function called by the test driver. */ +/* Internal functions called by the test driver. */ int support_report_failure (int status) __attribute__ ((weak, warn_unused_result)); +int support_is_oom_accepted (void) + __attribute__ ((weak, warn_unused_result)); /* Internal function used to test the failure recording framework. */ void support_record_failure_reset (void); diff --git a/support/check_mem_access.h b/support/check_mem_access.h new file mode 100644 index 0000000..424fa40 --- /dev/null +++ b/support/check_mem_access.h @@ -0,0 +1,37 @@ +/* Test verification functions for memory access checks. + 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 + <https://www.gnu.org/licenses/>. */ + +#ifndef SUPPORT_CHECK_MEM_ACCESS_H +#define SUPPORT_CHECK_MEM_ACCESS_H + +#include <stdbool.h> +#include <sys/cdefs.h> + +__BEGIN_DECLS + +/* To check if the a memory region is inaccessible, this function tries + read / write on the provided address ADDR and checks if a SIGSEGV is + generated. This function is not thread-safe and it changes signal + handlers for SIGSEGV and SIGBUS. + If WRITE is true, only the write operation is checked, otherwise only + the read operation is checked. */ +bool check_mem_access (const void *addr, bool write); + +__END_DECLS + +#endif // SUPPORT_CHECK_MEM_ACCESS_H diff --git a/support/fuse.h b/support/fuse.h index 7b246de..f379cc7 100644 --- a/support/fuse.h +++ b/support/fuse.h @@ -96,6 +96,7 @@ void *support_fuse_cast_name_internal (struct fuse_in_header *, uint32_t, #define support_fuse_payload_type_READ struct fuse_read_in #define support_fuse_payload_type_SETATTR struct fuse_setattr_in #define support_fuse_payload_type_WRITE struct fuse_write_in +#define support_fuse_payload_type_COPY_FILE_RANGE struct fuse_copy_file_range_in #define support_fuse_cast(typ, inh) \ ((support_fuse_payload_type_##typ *) \ support_fuse_cast_internal ((inh), FUSE_##typ)) diff --git a/support/shell-container.c b/support/shell-container.c index dcf53ad..06f3212 100644 --- a/support/shell-container.c +++ b/support/shell-container.c @@ -237,25 +237,25 @@ run_command_array (char **argv) { if (strcmp (argv[i], "<") == 0 && argv[i + 1]) { - new_stdin = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777); + new_stdin = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0666); ++i; continue; } if (strcmp (argv[i], ">") == 0 && argv[i + 1]) { - new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777); + new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0666); ++i; continue; } if (strcmp (argv[i], ">>") == 0 && argv[i + 1]) { - new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_APPEND, 0777); + new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_APPEND, 0666); ++i; continue; } if (strcmp (argv[i], "2>") == 0 && argv[i + 1]) { - new_stderr = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777); + new_stderr = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0666); ++i; continue; } diff --git a/support/support.h b/support/support.h index 4998a34..4e752de 100644 --- a/support/support.h +++ b/support/support.h @@ -239,6 +239,15 @@ int support_open_dev_null_range (int num, int flags, mode_t mode); /* Check if kernel supports set VMA range name. */ extern bool support_set_vma_name_supported (void); +/* If invoked with a true argument, it instructs the supervising + process to ignore unexpected termination of the test process, + likely due to an OOM error. (This can theoretically mask other + test errors, so it should be used sparingly.) + + If invoked with a false argument, the default behavior is restored, + and OOM-induced errors result in test failure. */ +void support_accept_oom (bool); + __END_DECLS #endif /* SUPPORT_H */ diff --git a/support/support_capture_subprocess.c b/support/support_capture_subprocess.c index c3ef478..c89e65b 100644 --- a/support/support_capture_subprocess.c +++ b/support/support_capture_subprocess.c @@ -21,12 +21,17 @@ #include <errno.h> #include <fcntl.h> +#include <grp.h> +#include <scratch_buffer.h> +#include <stdio_ext.h> #include <stdlib.h> +#include <string.h> #include <support/check.h> #include <support/xunistd.h> #include <support/xsocket.h> #include <support/xspawn.h> #include <support/support.h> +#include <support/temp_file.h> #include <support/test-driver.h> static void @@ -109,111 +114,109 @@ support_capture_subprogram (const char *file, char *const argv[], /* Copies the executable into a restricted directory, so that we can safely make it SGID with the TARGET group ID. Then runs the executable. */ -static int -copy_and_spawn_sgid (char *child_id, gid_t gid) +static void +copy_and_spawn_sgid (const char *child_id, gid_t gid) { - char *dirname = xasprintf ("%s/tst-tunables-setuid.%jd", - test_dir, (intmax_t) getpid ()); + char *dirname = support_create_temp_directory ("tst-glibc-sgid-"); char *execname = xasprintf ("%s/bin", dirname); - int infd = -1; - int outfd = -1; - int ret = 1, status = 1; - - TEST_VERIFY (mkdir (dirname, 0700) == 0); - if (support_record_failure_is_failed ()) - goto err; + add_temp_file (execname); - infd = open ("/proc/self/exe", O_RDONLY); - if (infd < 0) + if (access ("/proc/self/exe", R_OK) != 0) FAIL_UNSUPPORTED ("unsupported: Cannot read binary from procfs\n"); - outfd = open (execname, O_WRONLY | O_CREAT | O_EXCL, 0700); - TEST_VERIFY (outfd >= 0); - if (support_record_failure_is_failed ()) - goto err; + support_copy_file ("/proc/self/exe", execname); + + if (chown (execname, getuid (), gid) != 0) + FAIL_UNSUPPORTED ("cannot change group of \"%s\" to %jd: %m", + execname, (intmax_t) gid); + + if (chmod (execname, 02750) != 0) + FAIL_UNSUPPORTED ("cannot make \"%s\" SGID: %m ", execname); + + /* Now we can drop the privilege of that group. */ + const int count = 64; + gid_t groups[count]; + int ngroups = getgroups(count, groups); + + if (ngroups < 0) + FAIL_UNSUPPORTED ("Could not get group list again for user %jd\n", + (intmax_t) getuid ()); - char buf[4096]; - for (;;) + int n = 0; + for (int i = 0; i < ngroups; i++) { - ssize_t rdcount = read (infd, buf, sizeof (buf)); - TEST_VERIFY (rdcount >= 0); - if (support_record_failure_is_failed ()) - goto err; - if (rdcount == 0) - break; - char *p = buf; - char *end = buf + rdcount; - while (p != end) + if (groups[i] != gid) { - ssize_t wrcount = write (outfd, buf, end - p); - if (wrcount == 0) - errno = ENOSPC; - TEST_VERIFY (wrcount > 0); - if (support_record_failure_is_failed ()) - goto err; - p += wrcount; + if (n != i) + groups[n] = groups[i]; + n++; } } - - bool chowned = false; - TEST_VERIFY ((chowned = fchown (outfd, getuid (), gid) == 0) - || errno == EPERM); - if (support_record_failure_is_failed ()) - goto err; - else if (!chowned) - { - ret = 77; - goto err; - } - - TEST_VERIFY (fchmod (outfd, 02750) == 0); - if (support_record_failure_is_failed ()) - goto err; - TEST_VERIFY (close (outfd) == 0); - if (support_record_failure_is_failed ()) - goto err; - TEST_VERIFY (close (infd) == 0); - if (support_record_failure_is_failed ()) - goto err; + setgroups (n, groups); /* We have the binary, now spawn the subprocess. Avoid using support_subprogram because we only want the program exit status, not the contents. */ - ret = 0; - infd = outfd = -1; - char * const args[] = {execname, child_id, NULL}; + char * const args[] = {execname, (char *) child_id, NULL}; + int status = support_subprogram_wait (args[0], args); - status = support_subprogram_wait (args[0], args); + free (execname); + free (dirname); -err: - if (outfd >= 0) - close (outfd); - if (infd >= 0) - close (infd); - if (execname != NULL) + if (WIFEXITED (status)) { - unlink (execname); - free (execname); + if (WEXITSTATUS (status) == 0) + return; + else + exit (WEXITSTATUS (status)); } - if (dirname != NULL) + else + FAIL_EXIT1 ("subprogram failed with status %d", status); +} + +/* Returns true if a group with NAME has been found, and writes its + GID to *TARGET. */ +static bool +find_sgid_group (gid_t *target, const char *name) +{ + /* Do not use getgrname_r because it does not work in statically + linked binaries if the system libc is different. */ + FILE *fp = fopen ("/etc/group", "rce"); + if (fp == NULL) + return false; + __fsetlocking (fp, FSETLOCKING_BYCALLER); + + bool ok = false; + struct scratch_buffer buf; + scratch_buffer_init (&buf); + while (true) { - rmdir (dirname); - free (dirname); + struct group grp; + struct group *result = NULL; + int status = fgetgrent_r (fp, &grp, buf.data, buf.length, &result); + if (status == 0 && result != NULL) + { + if (strcmp (result->gr_name, name) == 0) + { + *target = result->gr_gid; + ok = true; + break; + } + } + else if (errno != ERANGE) + break; + else if (!scratch_buffer_grow (&buf)) + break; } - - if (ret == 77) - FAIL_UNSUPPORTED ("Failed to make sgid executable for test\n"); - if (ret != 0) - FAIL_EXIT1 ("Failed to make sgid executable for test\n"); - - return status; + scratch_buffer_free (&buf); + fclose (fp); + return ok; } -int -support_capture_subprogram_self_sgid (char *child_id) +void +support_capture_subprogram_self_sgid (const char *child_id) { - gid_t target = 0; const int count = 64; gid_t groups[count]; @@ -225,6 +228,7 @@ support_capture_subprogram_self_sgid (char *child_id) (intmax_t) getuid ()); gid_t current = getgid (); + gid_t target = current; for (int i = 0; i < ret; ++i) { if (groups[i] != current) @@ -234,11 +238,18 @@ support_capture_subprogram_self_sgid (char *child_id) } } - if (target == 0) - FAIL_UNSUPPORTED("Could not find a suitable GID for user %jd\n", - (intmax_t) getuid ()); + if (target == current) + { + /* If running as root, try to find a harmless group for SGID. */ + if (getuid () != 0 + || (!find_sgid_group (&target, "nogroup") + && !find_sgid_group (&target, "bin") + && !find_sgid_group (&target, "daemon"))) + FAIL_UNSUPPORTED("Could not find a suitable GID for user %jd\n", + (intmax_t) getuid ()); + } - return copy_and_spawn_sgid (child_id, target); + copy_and_spawn_sgid (child_id, target); } void diff --git a/support/support_fuse.c b/support/support_fuse.c index a70a74c..a90882e 100644 --- a/support/support_fuse.c +++ b/support/support_fuse.c @@ -212,6 +212,9 @@ support_fuse_handle_directory (struct support_fuse *f) support_fuse_reply_prepared (f); } return true; + case FUSE_GETXATTR: + support_fuse_reply_error (f, ENOSYS); + return true; default: return false; } @@ -222,7 +225,8 @@ support_fuse_handle_mountpoint (struct support_fuse *f) { TEST_VERIFY (f->inh != NULL); /* 1 is the root node. */ - if (f->inh->opcode == FUSE_GETATTR && f->inh->nodeid == 1) + if ((f->inh->opcode == FUSE_GETATTR || f->inh->opcode == FUSE_GETXATTR) + && f->inh->nodeid == 1) return support_fuse_handle_directory (f); return false; } diff --git a/support/support_mem_access.c b/support/support_mem_access.c new file mode 100644 index 0000000..e5a1666 --- /dev/null +++ b/support/support_mem_access.c @@ -0,0 +1,64 @@ +/* Implementation of the test verification functions for memory access + checks. + 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 + <https://www.gnu.org/licenses/>. */ + +#include <setjmp.h> +#include <signal.h> +#include <stddef.h> +#include <support/xsignal.h> + +#include "check_mem_access.h" + +static sigjmp_buf sigsegv_jmp_buf; + +static void +__sigsegv_handler (int signum) +{ + siglongjmp (sigsegv_jmp_buf, signum); +} + +bool check_mem_access (const void *addr, bool write) +{ + /* This is obviously not thread-safe. */ + static bool handler_set_up; + if (!handler_set_up) + { + struct sigaction sa = { + .sa_handler = __sigsegv_handler, + .sa_flags = SA_NODEFER, + }; + sigemptyset (&sa.sa_mask); + xsigaction (SIGSEGV, &sa, NULL); + /* Some system generates SIGBUS accessing the guard area when it is + setup with madvise. */ + xsigaction (SIGBUS, &sa, NULL); + handler_set_up = true; + } + int r = sigsetjmp (sigsegv_jmp_buf, 0); + if (r == 0) + { + if (write) + *(volatile char *)addr = 'x'; + else + *(volatile char *)addr; + return true; + } + if (r == SIGSEGV || r == SIGBUS) + return false; + return true; +} diff --git a/support/support_record_failure.c b/support/support_record_failure.c index c69be20..6ed5ca4 100644 --- a/support/support_record_failure.c +++ b/support/support_record_failure.c @@ -31,6 +31,10 @@ failure is detected, so that even if the counter wraps around to zero, the failure of a test can be detected. + If the accept_oom member is not zero, the supervisor process will + use heuristics to suppress process termination due to OOM + conditions. + The init constructor function below puts *state on a shared anonymous mapping, so that failure reports from subprocesses propagate to the parent process. */ @@ -38,6 +42,7 @@ struct test_failures { unsigned int counter; unsigned int failed; + unsigned int accept_oom; }; static struct test_failures *state; @@ -122,3 +127,34 @@ support_record_failure_barrier (void) exit (1); } } + +void +support_accept_oom (bool onoff) +{ + if (onoff) + { + /* One thread detects the overflow. */ + if (__atomic_fetch_add (&state->accept_oom, 1, __ATOMIC_RELAXED) + == UINT_MAX) + { + puts ("error: OOM acceptance counter overflow"); + exit (1); + } + } + else + { + /* One thread detects the underflow. */ + if (__atomic_fetch_add (&state->accept_oom, -1, __ATOMIC_RELAXED) + == 0) + { + puts ("error: OOM acceptance counter underflow"); + exit (1); + } + } +} + +int +support_is_oom_accepted (void) +{ + return __atomic_load_n (&state->accept_oom, __ATOMIC_RELAXED) != 0; +} diff --git a/support/support_stack_alloc.c b/support/support_stack_alloc.c index 5e576be..132e7b4 100644 --- a/support/support_stack_alloc.c +++ b/support/support_stack_alloc.c @@ -64,11 +64,10 @@ support_stack_alloc (size_t size) MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE|MAP_STACK, -1); /* Some architecture still requires executable stack for the signal return - trampoline, although PF_X could be overridden if PT_GNU_STACK is present. - However since glibc does not export such information with a proper ABI, - it uses the historical permissions. */ - int prot = PROT_READ | PROT_WRITE - | (DEFAULT_STACK_PERMS & PF_X ? PROT_EXEC : 0); + trampoline, although PROT_EXEC could be overridden if PT_GNU_STACK is + present. However since glibc does not export such information with a + proper ABI, it uses the historical permissions. */ + int prot = DEFAULT_STACK_PROT_PERMS; xmprotect (alloc_base + guardsize, stacksize, prot); memset (alloc_base + guardsize, 0xA5, stacksize); return (struct support_stack) { alloc_base + guardsize, stacksize, guardsize }; diff --git a/support/support_subprocess.c b/support/support_subprocess.c index be00dde..8bf9a33 100644 --- a/support/support_subprocess.c +++ b/support/support_subprocess.c @@ -25,6 +25,7 @@ #include <support/check.h> #include <support/xunistd.h> #include <support/subprocess.h> +#include <support/temp_file-internal.h> static struct support_subprocess support_subprocess_init (void) @@ -60,6 +61,8 @@ support_subprocess (void (*callback) (void *), void *closure) xclose (result.stdout_pipe[1]); xclose (result.stderr_pipe[1]); callback (closure); + /* Make sure that temporary files are deleted. */ + support_delete_temp_files (); _exit (0); } xclose (result.stdout_pipe[1]); diff --git a/support/support_test_main.c b/support/support_test_main.c index bd6c728..1558e27 100644 --- a/support/support_test_main.c +++ b/support/support_test_main.c @@ -264,6 +264,20 @@ adjust_exit_status (int status) return status; } +/* Return true if the exit status looks like it may have been + triggered by kernel OOM handling, and support_accept_oom (true) was + active in the test process. This is a very approximate check. + Unfortunately, the SI_KERNEL value for si_code in siginfo_t is not + observable via waitid (it gets translated to CLD_KILLED. */ +static bool +accept_oom_heuristic (int status) +{ + return (WIFSIGNALED (status) + && WTERMSIG (status) == SIGKILL + && support_is_oom_accepted != NULL + && support_is_oom_accepted ()); +} + int support_test_main (int argc, char **argv, const struct test_config *config) { @@ -497,6 +511,11 @@ support_test_main (int argc, char **argv, const struct test_config *config) /* Process was killed by timer or other signal. */ else { + if (accept_oom_heuristic (status)) + { + puts ("Heuristically determined OOM termination; SIGKILL ignored"); + exit (adjust_exit_status (EXIT_UNSUPPORTED)); + } if (config->expected_signal == 0) { printf ("Didn't expect signal from child: got `%s'\n", diff --git a/support/test-container.c b/support/test-container.c index a641250..ae643d3 100644 --- a/support/test-container.c +++ b/support/test-container.c @@ -273,7 +273,7 @@ devmount (const char *new_root_path, const char *which) { int fd; fd = open (concat (new_root_path, "/dev/", which, NULL), - O_CREAT | O_TRUNC | O_RDWR, 0777); + O_CREAT | O_TRUNC | O_RDWR, 0666); xclose (fd); trymount (concat ("/dev/", which, NULL), @@ -740,7 +740,7 @@ main (int argc, char **argv) char *command_basename; char *so_base; int do_postclean = 0; - bool do_ldconfig = false; + bool do_ldconfig = true; char *change_cwd = NULL; int pipes[2]; diff --git a/support/tst-support_accept_oom.c b/support/tst-support_accept_oom.c new file mode 100644 index 0000000..42a4328 --- /dev/null +++ b/support/tst-support_accept_oom.c @@ -0,0 +1,115 @@ +/* Test that OOM error suppression works. + 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 + <https://www.gnu.org/licenses/>. */ + +/* This test reacts to the reject_oom and inject_error environment + variables. It is never executed automatically because it can run + for a very long time on large systems, and is generally stressful + to the system. */ + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <support.h> +#include <support/check.h> +#include <sys/mman.h> +#include <unistd.h> + +/* If true, support_accept_oom is called. */ +static bool accept_oom; + +/* System page size. Allocations are always at least that large. */ +static size_t page_size; + +/* All allocated bytes. */ +static size_t total_bytes; + +/* Try to allocate SIZE bytes of memory, and ensure that is backed by + actual memory. */ +static bool +populate_memory (size_t size) +{ + TEST_COMPARE (size % page_size, 0); + char *ptr = mmap (NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (ptr == MAP_FAILED) + return false; + + if (accept_oom) + support_accept_oom (true); + + /* Ensure that the kernel allocates backing storage. Make the pages + distinct using the total_bytes counter. */ + for (size_t offset = 0; offset < size; offset += page_size) + { + memcpy (ptr + offset, &total_bytes, sizeof (total_bytes)); + total_bytes += page_size; + } + + if (accept_oom) + support_accept_oom (false); + + return true; +} + +static int +do_test (void) +{ + if (getenv ("oom_test_active") == NULL) + { + puts ("info: This test does nothing by default."); + puts ("info: Set the oom_test_active environment variable to enable it."); + puts ("info: Consider testing with inject_error and reject_oom as well."); + return 0; + } + + accept_oom = getenv ("reject_oom") == NULL; + + page_size = sysconf (_SC_PAGESIZE); + size_t size = page_size; + + /* The environment variable can be set to trigger a test failure. + The OOM event should not obscure this error. */ + TEST_COMPARE_STRING (getenv ("inject_error"), NULL); + + /* Grow the allocation until allocation fails. */ + while (true) + { + size_t new_size = 2 * size; + if (new_size == 0 || !populate_memory (new_size)) + break; + size = new_size; + } + + while (true) + { + if (!populate_memory (size)) + { + /* Decrease size and see if the allocation succeeds. */ + size /= 2; + if (size < page_size) + FAIL_UNSUPPORTED ("could not trigger OOM" + " after allocating %zu bytes", + total_bytes); + } + } + + return 0; +} + +#include <support/test-driver.c> diff --git a/support/xfmemopen.c b/support/xfmemopen.c new file mode 100644 index 0000000..f1dbc72 --- /dev/null +++ b/support/xfmemopen.c @@ -0,0 +1,31 @@ +/* fmemopen with error checking. + 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 + <https://www.gnu.org/licenses/>. */ + +#include <support/xstdio.h> + +#include <support/check.h> +#include <stdlib.h> + +FILE * +xfmemopen (void *mem, size_t len, const char *mode) +{ + FILE *fp = fmemopen (mem, len, mode); + if (fp == NULL) + FAIL_EXIT1 ("fmemopen (mode \"%s\"): %m", mode); + return fp; +} diff --git a/support/xstdio.h b/support/xstdio.h index c3fdf94..70b83f1 100644 --- a/support/xstdio.h +++ b/support/xstdio.h @@ -27,6 +27,7 @@ __BEGIN_DECLS FILE *xfopen (const char *path, const char *mode); void xfclose (FILE *); FILE *xfreopen (const char *path, const char *mode, FILE *stream); +FILE *xfmemopen (void *mem, size_t len, const char *mode); void xfread (void *ptr, size_t size, size_t nmemb, FILE *stream); char *xfgets (char *s, int size, FILE *stream); |