|
@@ -0,0 +1,486 @@
|
|
|
+// 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.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
|