From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-dy1-f179.google.com (mail-dy1-f179.google.com [74.125.82.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9BBCA3D45D0 for ; Fri, 15 May 2026 04:27:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.179 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778819266; cv=none; b=XPg8CVs/on1vzfPrkVKyfIpSgpcz13i4NALrw3eTBq9CuONnlb+SHPG9PFTFMFW4v7EXPidvRP4XwniH1BesCrU2yyfO5iingiIdlpwR8NM944v9z9ZaAKrSFZiNmvNb1OLGbBJiX3Bm1xe4A4SIH7r5n8kgspEs81XsXoGMiJA= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1778819266; c=relaxed/simple; bh=BqMODMnGhv8zZFtc9tO5za+rEDjRP2i6tid/hhV3IG8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=AdOP1lW367+XojwjUPuQw6UV0u/ZQP9pAmwDZMj6iN7rXtebqipNELbHnwYbkJCa/gS84MS57tkxbzv9510wcw7aTxJhdN+g0jm8VWepiXVdUR0FzM7oQpsSgeVOJjCqWPUIR2C0nwTNu0KCV9bWyBLfIIj41IWghhrhYt0QsHI= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Jrp7gvsn; arc=none smtp.client-ip=74.125.82.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Jrp7gvsn" Received: by mail-dy1-f179.google.com with SMTP id 5a478bee46e88-2f7020a928eso12354172eec.1 for ; Thu, 14 May 2026 21:27:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778819264; x=1779424064; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=gw9Caaf8QhnsI+L0BDRVE7f1uC7whTFCyE5y+aVhg2A=; b=Jrp7gvsn7D1EmfK/FRRgjBzfykNuNoB91UrSA9D7oYkvd9d6ZDpzdyNf7ZEkkfiiU4 UzIJ56Yk7lQ482pnYBsRWvCE483xTcdES5uPu44uE/sFfqIxW6JsDUNwU9kgeihJJOqH SHljV4r842+RTvbW0erZ5aycNTL4dbHQpMvxekMFJm8WYI7PDVf1hNCVREIDXTh4dY5b 9Q2FlOfhaE8dxdZwWnujFe2a1QvhlkoR+k2zb5dS4bV1ofoSavF6op3vjA6PYonFZWdt xSMoBYIr7sct9QZECq73BiSLZGJuQ5Le/4lu7FjhrRaOk4aeFQyJ70MFBARTJfj9Fm9A loPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778819264; x=1779424064; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=gw9Caaf8QhnsI+L0BDRVE7f1uC7whTFCyE5y+aVhg2A=; b=Y/Winre+uX/TqN1sT+kxOh0a4Q2DVKkkujwG7MqaraPGS1y6NaBw0mH29Aaa03/mfv JtFws+JgNAiSgKnALK1t2/dTUFTGuhBfH68tOLi4HRh8UpI52Ts6ZO914UDyr3SjMDA4 bQG5YK75LZYTpHvb9d+NndbNOdO8MiWht9E1jJX5mJNXisV4Mm7CcxF3b6hFm8rVTvnm UbMP2r+Xb218xzXiF0qzOfFq+w3ysMBX1ta3Xle9ZR9M8tJnXX06fhMQBjVO6lWKT6YC ZY//VRr+uYElgOB4VX8X7ai8I596X2jGil1+ybDYFMjq0eZfUiAi6ELwkylJ3Nmp7dhK y6fg== X-Forwarded-Encrypted: i=1; AFNElJ9XQbpPdhockad5hS5PfzPwlQ6OyHjgdfTRmjYyU6OXVJlw+Fwgrlr47QCw6pJFbnMw87vkTTAKSEPExMw=@vger.kernel.org X-Gm-Message-State: AOJu0Yy9JEmEI3reLeNRn4kcFHE3pkVkUsrifoNMcckKdqzlh1tEnSJW clnk97BsHINDF81UE6mECQ7vs0CHrrfhhdxI1Beg1q/wkMdfFup4Cd8u X-Gm-Gg: Acq92OG0URlG78nSVvpxEdxgyDDVzWianFHAx5YNIuwoN4pRZxMVdc9ouBvoQ7Gpg3M 1wfofeEuXRl6e3aoXmuH5etQoaMdgpjqnvsssYnEXEFB0QTFeG8igGtGNmfDXV1wtx8qGh83gQv mQIfJku+qk5/SfwvsPtXkbLk4NJzHQ5pV5HRJ/2Y3zrdOFoALLPYOvfLDZIpLBFNrihGnrKLm/O Kpu/AA+aKl03rSdQEgnOqi2NgYGhx4V8wvAo0clCMbfHURS6UReXTrHwMPMEGq/qEVlzmZkxWkU XWGmhT5agzGsfY7afR8MtRbSZHNYe0ooQbSJWnuqPBd1WAr89x9jBodcSaNIlDK3v065BQcf1pn MWOYUX6ux6pWnMpsSjlM57twkjpY4BxnEW91eSQ1s6bGgaKPM5xqJuxvcsd9Un1PvE2kIxJIOJj /yIzVyv0vrsoSXIbToin9cyqQ+S53kd+vsCXAbdHZhegDpaEsPd0kaooFleqvk X-Received: by 2002:a05:7300:5708:b0:2c7:3a7:c792 with SMTP id 5a478bee46e88-3039867717emr1161169eec.20.1778819263666; Thu, 14 May 2026 21:27:43 -0700 (PDT) Received: from pop-os.. ([2601:647:6802:dbc0:5cba:6b6f:e327:bade]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-302944ffb85sm5665521eec.7.2026.05.14.21.27.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 14 May 2026 21:27:42 -0700 (PDT) From: Cong Wang To: Kees Cook , linux-kernel@vger.kernel.org Cc: Andy Lutomirski , Will Drewry , Christian Brauner , Cong Wang Subject: [RFC PATCH v2 2/3] selftests/seccomp: add seccomp_notif_inject coverage Date: Thu, 14 May 2026 21:27:37 -0700 Message-ID: <20260515042738.1723296-3-xiyou.wangcong@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260515042738.1723296-1-xiyou.wangcong@gmail.com> References: <20260515042738.1723296-1-xiyou.wangcong@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Cong Wang Add a standalone selftest binary for SECCOMP_IOCTL_NOTIF_INJECT covering positive end-to-end injection on each of the v1 syscalls (openat, bind, write) and the negative paths (unsupported syscall, trapped/inject syscall mismatch, double inject, CONTINUE+INJECTED conflict, INJECTED without prior attach). The end-to-end tests fork a child that issues a filtered syscall with one set of arguments and verify that, after the supervisor attaches a substitute via SECCOMP_IOCTL_NOTIF_INJECT and replies with SECCOMP_USER_NOTIF_FLAG_INJECTED, the kernel acts on the supervisor-supplied bytes rather than the child's user mm. Lives in its own file rather than seccomp_bpf.c since the feature is unrelated to the BPF filter machinery. Assisted-by: Claude:claude-opus-4.6 Signed-off-by: Cong Wang --- tools/testing/selftests/seccomp/.gitignore | 1 + tools/testing/selftests/seccomp/Makefile | 2 +- .../selftests/seccomp/seccomp_notif_inject.c | 434 ++++++++++++++++++ 3 files changed, 436 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/seccomp/seccomp_notif_inject.c diff --git a/tools/testing/selftests/seccomp/.gitignore b/tools/testing/selftests/seccomp/.gitignore index dec678577f9c..096f77a0b136 100644 --- a/tools/testing/selftests/seccomp/.gitignore +++ b/tools/testing/selftests/seccomp/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only seccomp_bpf seccomp_benchmark +seccomp_notif_inject diff --git a/tools/testing/selftests/seccomp/Makefile b/tools/testing/selftests/seccomp/Makefile index 584fba487037..8a4ca92f1af3 100644 --- a/tools/testing/selftests/seccomp/Makefile +++ b/tools/testing/selftests/seccomp/Makefile @@ -3,5 +3,5 @@ CFLAGS += -Wl,-no-as-needed -Wall $(KHDR_INCLUDES) LDFLAGS += -lpthread LDLIBS += -lcap -TEST_GEN_PROGS := seccomp_bpf seccomp_benchmark +TEST_GEN_PROGS := seccomp_bpf seccomp_benchmark seccomp_notif_inject include ../lib.mk diff --git a/tools/testing/selftests/seccomp/seccomp_notif_inject.c b/tools/testing/selftests/seccomp/seccomp_notif_inject.c new file mode 100644 index 000000000000..3d5bb97b8df9 --- /dev/null +++ b/tools/testing/selftests/seccomp/seccomp_notif_inject.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Selftest for SECCOMP_IOCTL_NOTIF_INJECT. + * + * Exercises end-to-end syscall injection via the listener-fd + * supervisor pattern: the child issues a filtered syscall, the + * supervisor describes a substitute syscall in a kernel buffer, the + * kernel runs the substitute on the child's behalf using kernel-mode + * helpers (filp_open/kernel_bind/kernel_write), and the result lands + * as the child's syscall return value. The trapped task's user mm is + * never re-read for the substituted syscall. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" + +#ifndef __NR_seccomp +#define __NR_seccomp 317 +#endif + +static int seccomp_install(int nr) +{ + struct sock_filter filter[] = { + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, + offsetof(struct seccomp_data, nr)), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, nr, 0, 1), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + }; + struct sock_fprog prog = { + .len = (unsigned short)ARRAY_SIZE(filter), + .filter = filter, + }; + + return syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, + SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog); +} + +/* ---------------------------------------------------------------- + * openat injection. + * ---------------------------------------------------------------- + */ +TEST(notif_inject_openat) +{ + char tmp_real[] = "/tmp/seccomp-inject-XXXXXX"; + int real_fd, listener, status; + pid_t pid; + + real_fd = mkstemp(tmp_real); + ASSERT_GE(real_fd, 0); + ASSERT_EQ(write(real_fd, "real-data", 9), 9); + ASSERT_EQ(close(real_fd), 0); + + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + listener = seccomp_install(__NR_openat); + ASSERT_GE(listener, 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + char readback[16] = {0}; + int fd; + + fd = openat(AT_FDCWD, "/this/path/does/not/exist", O_RDONLY); + if (fd < 0) + _exit(10); + if (read(fd, readback, sizeof(readback) - 1) <= 0) + _exit(11); + _exit(memcmp(readback, "real-data", 9) == 0 ? 0 : 12); + } + + struct seccomp_notif req = {0}; + struct seccomp_notif_resp resp = {0}; + struct seccomp_notif_inject inj = {0}; + + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0); + EXPECT_EQ(req.data.nr, __NR_openat); + + inj.id = req.id; + inj.nr = __NR_openat; + inj.args[0] = AT_FDCWD; + inj.args[1] = 0; + inj.args[2] = O_RDONLY; + inj.args[3] = 0; + inj.buf = (uintptr_t)tmp_real; + inj.buf_size = strlen(tmp_real) + 1; + inj.args_in_buf_mask = 1U << 1; + ASSERT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_INJECT, &inj), 0) { + TH_LOG("INJECT failed: %s", strerror(errno)); + } + + resp.id = req.id; + resp.flags = SECCOMP_USER_NOTIF_FLAG_INJECTED; + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0); + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_TRUE(WIFEXITED(status)); + EXPECT_EQ(WEXITSTATUS(status), 0) { + TH_LOG("child exit status: %d", WEXITSTATUS(status)); + } + + unlink(tmp_real); + close(listener); +} + +/* ---------------------------------------------------------------- + * write injection. + * Child issues write(fd, "agent-data", 10); supervisor injects + * write(fd, "kernel-bytes", 12); verify file content matches the + * kernel-injected bytes. + * ---------------------------------------------------------------- + */ +TEST(notif_inject_write) +{ + char path[] = "/tmp/seccomp-inject-write-XXXXXX"; + static const char inject_bytes[] = "kernel-bytes"; + int file_fd, listener, status; + char file_content[32]; + pid_t pid; + + file_fd = mkstemp(path); + ASSERT_GE(file_fd, 0); + + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + listener = seccomp_install(__NR_write); + ASSERT_GE(listener, 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + ssize_t n; + + n = write(file_fd, "agent-data", 10); + _exit(n > 0 ? 0 : 10); + } + + struct seccomp_notif req = {0}; + struct seccomp_notif_resp resp = {0}; + struct seccomp_notif_inject inj = {0}; + + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0); + EXPECT_EQ(req.data.nr, __NR_write); + + inj.id = req.id; + inj.nr = __NR_write; + inj.args[0] = req.data.args[0]; /* fd, pass-through */ + inj.args[1] = 0; + inj.args[2] = strlen(inject_bytes); + inj.buf = (uintptr_t)inject_bytes; + inj.buf_size = strlen(inject_bytes); + inj.args_in_buf_mask = 1U << 1; + ASSERT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_INJECT, &inj), 0); + + resp.id = req.id; + resp.flags = SECCOMP_USER_NOTIF_FLAG_INJECTED; + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0); + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_TRUE(WIFEXITED(status)); + EXPECT_EQ(WEXITSTATUS(status), 0); + + memset(file_content, 0, sizeof(file_content)); + ASSERT_EQ(lseek(file_fd, 0, SEEK_SET), 0); + ASSERT_EQ(read(file_fd, file_content, sizeof(file_content) - 1), + (ssize_t)strlen(inject_bytes)); + EXPECT_EQ(memcmp(file_content, inject_bytes, strlen(inject_bytes)), 0) { + TH_LOG("file content: '%s'", file_content); + } + + close(file_fd); + unlink(path); + close(listener); +} + +/* ---------------------------------------------------------------- + * bind injection. + * ---------------------------------------------------------------- + */ +TEST(notif_inject_bind) +{ + struct sockaddr_un real_addr = { .sun_family = AF_UNIX }; + char real_path[] = "/tmp/seccomp-inject-bind-XXXXXX"; + int listener, status; + pid_t pid; + + mktemp(real_path); + strcpy(real_addr.sun_path, real_path); + unlink(real_addr.sun_path); + + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + listener = seccomp_install(__NR_bind); + ASSERT_GE(listener, 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + struct sockaddr_un fake = { .sun_family = AF_UNIX }; + int s; + + strcpy(fake.sun_path, "/tmp/seccomp-inject-bind-fake"); + s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s < 0) + _exit(10); + if (bind(s, (struct sockaddr *)&fake, sizeof(fake)) < 0) + _exit(11); + _exit(0); + } + + struct seccomp_notif req = {0}; + struct seccomp_notif_resp resp = {0}; + struct seccomp_notif_inject inj = {0}; + struct stat st; + + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0); + EXPECT_EQ(req.data.nr, __NR_bind); + + inj.id = req.id; + inj.nr = __NR_bind; + inj.args[0] = req.data.args[0]; /* sockfd, pass-through */ + inj.args[1] = 0; + inj.args[2] = sizeof(real_addr); + inj.buf = (uintptr_t)&real_addr; + inj.buf_size = sizeof(real_addr); + inj.args_in_buf_mask = 1U << 1; + ASSERT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_INJECT, &inj), 0); + + resp.id = req.id; + resp.flags = SECCOMP_USER_NOTIF_FLAG_INJECTED; + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0); + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_TRUE(WIFEXITED(status)); + EXPECT_EQ(WEXITSTATUS(status), 0); + + /* + * The kernel-injected path should exist; the agent's intended + * path should not. + */ + EXPECT_EQ(stat(real_path, &st), 0); + EXPECT_EQ(stat("/tmp/seccomp-inject-bind-fake", &st), -1); + + unlink(real_path); + close(listener); +} + +/* ---------------------------------------------------------------- + * Negative paths. + * ---------------------------------------------------------------- + */ +TEST(notif_inject_unsupported_syscall) +{ + int listener, status; + pid_t pid; + + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + listener = seccomp_install(__NR_close); + ASSERT_GE(listener, 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(99); + _exit(0); + } + + struct seccomp_notif req = {0}; + struct seccomp_notif_resp resp = {0}; + struct seccomp_notif_inject inj = {0}; + + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0); + + /* close() is not in the injectable whitelist. */ + inj.id = req.id; + inj.nr = __NR_close; + inj.args[0] = 99; + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_INJECT, &inj), -1); + EXPECT_EQ(errno, EOPNOTSUPP); + + /* Cleanly deny so the child can exit. */ + resp.id = req.id; + resp.error = -EBADF; + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0); + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + close(listener); +} + +TEST(notif_inject_syscall_mismatch) +{ + int listener, status; + pid_t pid; + + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + listener = seccomp_install(__NR_openat); + ASSERT_GE(listener, 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + openat(AT_FDCWD, "/nonexistent", O_RDONLY); + _exit(0); + } + + struct seccomp_notif req = {0}; + struct seccomp_notif_resp resp = {0}; + struct seccomp_notif_inject inj = {0}; + + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0); + + /* Trapped on openat, but supervisor tries to inject bind. */ + inj.id = req.id; + inj.nr = __NR_bind; + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_INJECT, &inj), -1); + EXPECT_EQ(errno, ESRCH); + + resp.id = req.id; + resp.error = -EACCES; + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0); + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + close(listener); +} + +TEST(notif_inject_double) +{ + char tmp_real[] = "/tmp/seccomp-inject-d-XXXXXX"; + int real_fd, listener, status; + pid_t pid; + + real_fd = mkstemp(tmp_real); + ASSERT_GE(real_fd, 0); + close(real_fd); + + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + listener = seccomp_install(__NR_openat); + ASSERT_GE(listener, 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + openat(AT_FDCWD, "/nonexistent", O_RDONLY); + _exit(0); + } + + struct seccomp_notif req = {0}; + struct seccomp_notif_resp resp = {0}; + struct seccomp_notif_inject inj = {0}; + + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0); + + inj.id = req.id; + inj.nr = __NR_openat; + inj.args[0] = AT_FDCWD; + inj.args[1] = 0; + inj.args[2] = O_RDONLY; + inj.buf = (uintptr_t)tmp_real; + inj.buf_size = strlen(tmp_real) + 1; + inj.args_in_buf_mask = 1U << 1; + ASSERT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_INJECT, &inj), 0); + + /* Second INJECT for the same id is rejected. */ + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_INJECT, &inj), -1); + EXPECT_EQ(errno, EEXIST); + + resp.id = req.id; + resp.flags = SECCOMP_USER_NOTIF_FLAG_INJECTED; + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0); + EXPECT_EQ(waitpid(pid, &status, 0), pid); + + unlink(tmp_real); + close(listener); +} + +TEST(notif_inject_continue_pinned_conflict) +{ + int listener, status; + pid_t pid; + + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); + listener = seccomp_install(__NR_openat); + ASSERT_GE(listener, 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + openat(AT_FDCWD, "/nonexistent", O_RDONLY); + _exit(0); + } + + struct seccomp_notif req = {0}; + struct seccomp_notif_resp resp = {0}; + + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0); + + /* CONTINUE | INJECTED is rejected. */ + resp.id = req.id; + resp.flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE | + SECCOMP_USER_NOTIF_FLAG_INJECTED; + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), -1); + EXPECT_EQ(errno, EINVAL); + + /* INJECTED without prior INJECT ioctl is rejected too. */ + resp.flags = SECCOMP_USER_NOTIF_FLAG_INJECTED; + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), -1); + EXPECT_EQ(errno, EINVAL); + + /* Cleanly deny so the child exits. */ + resp.flags = 0; + resp.error = -ENOENT; + EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0); + EXPECT_EQ(waitpid(pid, &status, 0), pid); + close(listener); +} + +TEST_HARNESS_MAIN -- 2.43.0