// SPDX-License-Identifier: GPL-2.0
|
|
#define _GNU_SOURCE
|
#include <errno.h>
|
#include <fcntl.h>
|
#include <linux/netlink.h>
|
#include <signal.h>
|
#include <stdbool.h>
|
#include <stdio.h>
|
#include <stdlib.h>
|
#include <string.h>
|
#include <sys/prctl.h>
|
#include <sys/socket.h>
|
#include <sched.h>
|
#include <sys/eventfd.h>
|
#include <sys/stat.h>
|
#include <sys/syscall.h>
|
#include <sys/types.h>
|
#include <sys/wait.h>
|
#include <unistd.h>
|
|
#include "../kselftest_harness.h"
|
|
#define __DEV_FULL "/sys/devices/virtual/mem/full/uevent"
|
#define __UEVENT_BUFFER_SIZE (2048 * 2)
|
#define __UEVENT_HEADER "add@/devices/virtual/mem/full"
|
#define __UEVENT_HEADER_LEN sizeof("add@/devices/virtual/mem/full")
|
#define __UEVENT_LISTEN_ALL -1
|
|
ssize_t read_nointr(int fd, void *buf, size_t count)
|
{
|
ssize_t ret;
|
|
again:
|
ret = read(fd, buf, count);
|
if (ret < 0 && errno == EINTR)
|
goto again;
|
|
return ret;
|
}
|
|
ssize_t write_nointr(int fd, const void *buf, size_t count)
|
{
|
ssize_t ret;
|
|
again:
|
ret = write(fd, buf, count);
|
if (ret < 0 && errno == EINTR)
|
goto again;
|
|
return ret;
|
}
|
|
int wait_for_pid(pid_t pid)
|
{
|
int status, ret;
|
|
again:
|
ret = waitpid(pid, &status, 0);
|
if (ret == -1) {
|
if (errno == EINTR)
|
goto again;
|
|
return -1;
|
}
|
|
if (ret != pid)
|
goto again;
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
return -1;
|
|
return 0;
|
}
|
|
static int uevent_listener(unsigned long post_flags, bool expect_uevent,
|
int sync_fd)
|
{
|
int sk_fd, ret;
|
socklen_t sk_addr_len;
|
int fret = -1, rcv_buf_sz = __UEVENT_BUFFER_SIZE;
|
uint64_t sync_add = 1;
|
struct sockaddr_nl sk_addr = { 0 }, rcv_addr = { 0 };
|
char buf[__UEVENT_BUFFER_SIZE] = { 0 };
|
struct iovec iov = { buf, __UEVENT_BUFFER_SIZE };
|
char control[CMSG_SPACE(sizeof(struct ucred))];
|
struct msghdr hdr = {
|
&rcv_addr, sizeof(rcv_addr), &iov, 1,
|
control, sizeof(control), 0,
|
};
|
|
sk_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC,
|
NETLINK_KOBJECT_UEVENT);
|
if (sk_fd < 0) {
|
fprintf(stderr, "%s - Failed to open uevent socket\n", strerror(errno));
|
return -1;
|
}
|
|
ret = setsockopt(sk_fd, SOL_SOCKET, SO_RCVBUF, &rcv_buf_sz,
|
sizeof(rcv_buf_sz));
|
if (ret < 0) {
|
fprintf(stderr, "%s - Failed to set socket options\n", strerror(errno));
|
goto on_error;
|
}
|
|
sk_addr.nl_family = AF_NETLINK;
|
sk_addr.nl_groups = __UEVENT_LISTEN_ALL;
|
|
sk_addr_len = sizeof(sk_addr);
|
ret = bind(sk_fd, (struct sockaddr *)&sk_addr, sk_addr_len);
|
if (ret < 0) {
|
fprintf(stderr, "%s - Failed to bind socket\n", strerror(errno));
|
goto on_error;
|
}
|
|
ret = getsockname(sk_fd, (struct sockaddr *)&sk_addr, &sk_addr_len);
|
if (ret < 0) {
|
fprintf(stderr, "%s - Failed to retrieve socket name\n", strerror(errno));
|
goto on_error;
|
}
|
|
if ((size_t)sk_addr_len != sizeof(sk_addr)) {
|
fprintf(stderr, "Invalid socket address size\n");
|
goto on_error;
|
}
|
|
if (post_flags & CLONE_NEWUSER) {
|
ret = unshare(CLONE_NEWUSER);
|
if (ret < 0) {
|
fprintf(stderr,
|
"%s - Failed to unshare user namespace\n",
|
strerror(errno));
|
goto on_error;
|
}
|
}
|
|
if (post_flags & CLONE_NEWNET) {
|
ret = unshare(CLONE_NEWNET);
|
if (ret < 0) {
|
fprintf(stderr,
|
"%s - Failed to unshare network namespace\n",
|
strerror(errno));
|
goto on_error;
|
}
|
}
|
|
ret = write_nointr(sync_fd, &sync_add, sizeof(sync_add));
|
close(sync_fd);
|
if (ret != sizeof(sync_add)) {
|
fprintf(stderr, "Failed to synchronize with parent process\n");
|
goto on_error;
|
}
|
|
fret = 0;
|
for (;;) {
|
ssize_t r;
|
|
r = recvmsg(sk_fd, &hdr, 0);
|
if (r <= 0) {
|
fprintf(stderr, "%s - Failed to receive uevent\n", strerror(errno));
|
ret = -1;
|
break;
|
}
|
|
/* ignore libudev messages */
|
if (memcmp(buf, "libudev", 8) == 0)
|
continue;
|
|
/* ignore uevents we didn't trigger */
|
if (memcmp(buf, __UEVENT_HEADER, __UEVENT_HEADER_LEN) != 0)
|
continue;
|
|
if (!expect_uevent) {
|
fprintf(stderr, "Received unexpected uevent:\n");
|
ret = -1;
|
}
|
|
if (TH_LOG_ENABLED) {
|
/* If logging is enabled dump the received uevent. */
|
(void)write_nointr(STDERR_FILENO, buf, r);
|
(void)write_nointr(STDERR_FILENO, "\n", 1);
|
}
|
|
break;
|
}
|
|
on_error:
|
close(sk_fd);
|
|
return fret;
|
}
|
|
int trigger_uevent(unsigned int times)
|
{
|
int fd, ret;
|
unsigned int i;
|
|
fd = open(__DEV_FULL, O_RDWR | O_CLOEXEC);
|
if (fd < 0) {
|
if (errno != ENOENT)
|
return -EINVAL;
|
|
return -1;
|
}
|
|
for (i = 0; i < times; i++) {
|
ret = write_nointr(fd, "add\n", sizeof("add\n") - 1);
|
if (ret < 0) {
|
fprintf(stderr, "Failed to trigger uevent\n");
|
break;
|
}
|
}
|
close(fd);
|
|
return ret;
|
}
|
|
int set_death_signal(void)
|
{
|
int ret;
|
pid_t ppid;
|
|
ret = prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
|
|
/* Check whether we have been orphaned. */
|
ppid = getppid();
|
if (ppid == 1) {
|
pid_t self;
|
|
self = getpid();
|
ret = kill(self, SIGKILL);
|
}
|
|
if (ret < 0)
|
return -1;
|
|
return 0;
|
}
|
|
static int do_test(unsigned long pre_flags, unsigned long post_flags,
|
bool expect_uevent, int sync_fd)
|
{
|
int ret;
|
uint64_t wait_val;
|
pid_t pid;
|
sigset_t mask;
|
sigset_t orig_mask;
|
struct timespec timeout;
|
|
sigemptyset(&mask);
|
sigaddset(&mask, SIGCHLD);
|
|
ret = sigprocmask(SIG_BLOCK, &mask, &orig_mask);
|
if (ret < 0) {
|
fprintf(stderr, "%s- Failed to block SIGCHLD\n", strerror(errno));
|
return -1;
|
}
|
|
pid = fork();
|
if (pid < 0) {
|
fprintf(stderr, "%s - Failed to fork() new process\n", strerror(errno));
|
return -1;
|
}
|
|
if (pid == 0) {
|
/* Make sure that we go away when our parent dies. */
|
ret = set_death_signal();
|
if (ret < 0) {
|
fprintf(stderr, "Failed to set PR_SET_PDEATHSIG to SIGKILL\n");
|
_exit(EXIT_FAILURE);
|
}
|
|
if (pre_flags & CLONE_NEWUSER) {
|
ret = unshare(CLONE_NEWUSER);
|
if (ret < 0) {
|
fprintf(stderr,
|
"%s - Failed to unshare user namespace\n",
|
strerror(errno));
|
_exit(EXIT_FAILURE);
|
}
|
}
|
|
if (pre_flags & CLONE_NEWNET) {
|
ret = unshare(CLONE_NEWNET);
|
if (ret < 0) {
|
fprintf(stderr,
|
"%s - Failed to unshare network namespace\n",
|
strerror(errno));
|
_exit(EXIT_FAILURE);
|
}
|
}
|
|
if (uevent_listener(post_flags, expect_uevent, sync_fd) < 0)
|
_exit(EXIT_FAILURE);
|
|
_exit(EXIT_SUCCESS);
|
}
|
|
ret = read_nointr(sync_fd, &wait_val, sizeof(wait_val));
|
if (ret != sizeof(wait_val)) {
|
fprintf(stderr, "Failed to synchronize with child process\n");
|
_exit(EXIT_FAILURE);
|
}
|
|
/* Trigger 10 uevents to account for the case where the kernel might
|
* drop some.
|
*/
|
ret = trigger_uevent(10);
|
if (ret < 0)
|
fprintf(stderr, "Failed triggering uevents\n");
|
|
/* Wait for 2 seconds before considering this failed. This should be
|
* plenty of time for the kernel to deliver the uevent even under heavy
|
* load.
|
*/
|
timeout.tv_sec = 2;
|
timeout.tv_nsec = 0;
|
|
again:
|
ret = sigtimedwait(&mask, NULL, &timeout);
|
if (ret < 0) {
|
if (errno == EINTR)
|
goto again;
|
|
if (!expect_uevent)
|
ret = kill(pid, SIGTERM); /* success */
|
else
|
ret = kill(pid, SIGUSR1); /* error */
|
if (ret < 0)
|
return -1;
|
}
|
|
ret = wait_for_pid(pid);
|
if (ret < 0)
|
return -1;
|
|
return ret;
|
}
|
|
static void signal_handler(int sig)
|
{
|
if (sig == SIGTERM)
|
_exit(EXIT_SUCCESS);
|
|
_exit(EXIT_FAILURE);
|
}
|
|
TEST(uevent_filtering)
|
{
|
int ret, sync_fd;
|
struct sigaction act;
|
|
if (geteuid()) {
|
TH_LOG("Uevent filtering tests require root privileges. Skipping test");
|
_exit(KSFT_SKIP);
|
}
|
|
ret = access(__DEV_FULL, F_OK);
|
EXPECT_EQ(0, ret) {
|
if (errno == ENOENT) {
|
TH_LOG(__DEV_FULL " does not exist. Skipping test");
|
_exit(KSFT_SKIP);
|
}
|
|
_exit(KSFT_FAIL);
|
}
|
|
act.sa_handler = signal_handler;
|
act.sa_flags = 0;
|
sigemptyset(&act.sa_mask);
|
|
ret = sigaction(SIGTERM, &act, NULL);
|
ASSERT_EQ(0, ret);
|
|
sync_fd = eventfd(0, EFD_CLOEXEC);
|
ASSERT_GE(sync_fd, 0);
|
|
/*
|
* Setup:
|
* - Open uevent listening socket in initial network namespace owned by
|
* initial user namespace.
|
* - Trigger uevent in initial network namespace owned by initial user
|
* namespace.
|
* Expected Result:
|
* - uevent listening socket receives uevent
|
*/
|
ret = do_test(0, 0, true, sync_fd);
|
ASSERT_EQ(0, ret) {
|
goto do_cleanup;
|
}
|
|
/*
|
* Setup:
|
* - Open uevent listening socket in non-initial network namespace
|
* owned by initial user namespace.
|
* - Trigger uevent in initial network namespace owned by initial user
|
* namespace.
|
* Expected Result:
|
* - uevent listening socket receives uevent
|
*/
|
ret = do_test(CLONE_NEWNET, 0, true, sync_fd);
|
ASSERT_EQ(0, ret) {
|
goto do_cleanup;
|
}
|
|
/*
|
* Setup:
|
* - unshare user namespace
|
* - Open uevent listening socket in initial network namespace
|
* owned by initial user namespace.
|
* - Trigger uevent in initial network namespace owned by initial user
|
* namespace.
|
* Expected Result:
|
* - uevent listening socket receives uevent
|
*/
|
ret = do_test(CLONE_NEWUSER, 0, true, sync_fd);
|
ASSERT_EQ(0, ret) {
|
goto do_cleanup;
|
}
|
|
/*
|
* Setup:
|
* - Open uevent listening socket in non-initial network namespace
|
* owned by non-initial user namespace.
|
* - Trigger uevent in initial network namespace owned by initial user
|
* namespace.
|
* Expected Result:
|
* - uevent listening socket receives no uevent
|
*/
|
ret = do_test(CLONE_NEWUSER | CLONE_NEWNET, 0, false, sync_fd);
|
ASSERT_EQ(0, ret) {
|
goto do_cleanup;
|
}
|
|
/*
|
* Setup:
|
* - Open uevent listening socket in initial network namespace
|
* owned by initial user namespace.
|
* - unshare network namespace
|
* - Trigger uevent in initial network namespace owned by initial user
|
* namespace.
|
* Expected Result:
|
* - uevent listening socket receives uevent
|
*/
|
ret = do_test(0, CLONE_NEWNET, true, sync_fd);
|
ASSERT_EQ(0, ret) {
|
goto do_cleanup;
|
}
|
|
/*
|
* Setup:
|
* - Open uevent listening socket in initial network namespace
|
* owned by initial user namespace.
|
* - unshare user namespace
|
* - Trigger uevent in initial network namespace owned by initial user
|
* namespace.
|
* Expected Result:
|
* - uevent listening socket receives uevent
|
*/
|
ret = do_test(0, CLONE_NEWUSER, true, sync_fd);
|
ASSERT_EQ(0, ret) {
|
goto do_cleanup;
|
}
|
|
/*
|
* Setup:
|
* - Open uevent listening socket in initial network namespace
|
* owned by initial user namespace.
|
* - unshare user namespace
|
* - unshare network namespace
|
* - Trigger uevent in initial network namespace owned by initial user
|
* namespace.
|
* Expected Result:
|
* - uevent listening socket receives uevent
|
*/
|
ret = do_test(0, CLONE_NEWUSER | CLONE_NEWNET, true, sync_fd);
|
ASSERT_EQ(0, ret) {
|
goto do_cleanup;
|
}
|
|
do_cleanup:
|
close(sync_fd);
|
}
|
|
TEST_HARNESS_MAIN
|