// SPDX-License-Identifier: GPL-2.0-or-later
#define _GNU_SOURCE
#include "../kselftest_harness.h"
#include <asm-generic/mman.h> /* Force the import of the tools version. */
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/userfaultfd.h>
#include <setjmp.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#include <unistd.h>
/*
* Ignore the checkpatch warning, as per the C99 standard, section 7.14.1.1:
*
* "If the signal occurs other than as the result of calling the abort or raise
* function, the behavior is undefined if the signal handler refers to any
* object with static storage duration other than by assigning a value to an
* object declared as volatile sig_atomic_t"
*/
static volatile sig_atomic_t signal_jump_set;
static sigjmp_buf signal_jmp_buf;
/*
* Ignore the checkpatch warning, we must read from x but don't want to do
* anything with it in order to trigger a read page fault. We therefore must use
* volatile to stop the compiler from optimising this away.
*/
#define FORCE_READ(x) (*(volatile typeof(x) *)x)
static int userfaultfd(int flags)
{
return syscall(SYS_userfaultfd, flags);
}
static void handle_fatal(int c)
{
if (!signal_jump_set)
return;
siglongjmp(signal_jmp_buf, c);
}
static int pidfd_open(pid_t pid, unsigned int flags)
{
return syscall(SYS_pidfd_open, pid, flags);
}
/*
* Enable our signal catcher and try to read/write the specified buffer. The
* return value indicates whether the read/write succeeds without a fatal
* signal.
*/
static bool try_access_buf(char *ptr, bool write)
{
bool failed;
/* Tell signal handler to jump back here on fatal signal. */
signal_jump_set = true;
/* If a fatal signal arose, we will jump back here and failed is set. */
failed = sigsetjmp(signal_jmp_buf, 0) != 0;
if (!failed) {
if (write)
*ptr = 'x';
else
FORCE_READ(ptr);
}
signal_jump_set = false;
return !failed;
}
/* Try and read from a buffer, return true if no fatal signal. */
static bool try_read_buf(char *ptr)
{
return try_access_buf(ptr, false);
}
/* Try and write to a buffer, return true if no fatal signal. */
static bool try_write_buf(char *ptr)
{
return try_access_buf(ptr, true);
}
/*
* Try and BOTH read from AND write to a buffer, return true if BOTH operations
* succeed.
*/
static bool try_read_write_buf(char *ptr)
{
return try_read_buf(ptr) && try_write_buf(ptr);
}
FIXTURE(guard_pages)
{
unsigned long page_size;
};
FIXTURE_SETUP(guard_pages)
{
struct sigaction act = {
.sa_handler = &handle_fatal,
.sa_flags = SA_NODEFER,
};
sigemptyset(&act.sa_mask);
if (sigaction(SIGSEGV, &act, NULL))
ksft_exit_fail_perror("sigaction");
self->