aboutsummaryrefslogtreecommitdiff
path: root/support
diff options
context:
space:
mode:
Diffstat (limited to 'support')
-rw-r--r--support/Makefile3
-rw-r--r--support/capture_subprocess.h11
-rw-r--r--support/check.h4
-rw-r--r--support/check_mem_access.h37
-rw-r--r--support/fuse.h1
-rw-r--r--support/shell-container.c8
-rw-r--r--support/support.h9
-rw-r--r--support/support_capture_subprocess.c181
-rw-r--r--support/support_fuse.c6
-rw-r--r--support/support_mem_access.c64
-rw-r--r--support/support_record_failure.c36
-rw-r--r--support/support_stack_alloc.c9
-rw-r--r--support/support_subprocess.c3
-rw-r--r--support/support_test_main.c19
-rw-r--r--support/test-container.c4
-rw-r--r--support/tst-support_accept_oom.c115
-rw-r--r--support/xfmemopen.c31
-rw-r--r--support/xstdio.h1
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);