Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/landlock: Test signal scoping

Provide tests for the signal scoping. If the signal is 0, no signal
will be sent, but the permission of a process to send a signal will be
checked. Likewise, this test consider one signal for each signal
category: SIGTRAP, SIGURG, SIGHUP, and SIGTSTP.

Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
Link: https://lore.kernel.org/r/15dc202bb7f0a462ddeaa0c1cd630d2a7c6fa5c5.1725657728.git.fahimitahera@gmail.com
[mic: Fix commit message, use dedicated variables per process, properly
close FDs, extend send_sig_to_parent to make sure scoping works as
expected]
Signed-off-by: Mickaël Salaün <mic@digikod.net>

authored by

Tahera Fahimi and committed by
Mickaël Salaün
ea292363 54a6e6bb

+251
+251
tools/testing/selftests/landlock/scoped_signal_test.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Landlock tests - Signal Scoping 4 + * 5 + * Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com> 6 + */ 7 + 8 + #define _GNU_SOURCE 9 + #include <errno.h> 10 + #include <fcntl.h> 11 + #include <linux/landlock.h> 12 + #include <signal.h> 13 + #include <sys/prctl.h> 14 + #include <sys/types.h> 15 + #include <sys/wait.h> 16 + #include <unistd.h> 17 + 18 + #include "common.h" 19 + #include "scoped_common.h" 20 + 21 + /* This variable is used for handling several signals. */ 22 + static volatile sig_atomic_t is_signaled; 23 + 24 + /* clang-format off */ 25 + FIXTURE(scoping_signals) {}; 26 + /* clang-format on */ 27 + 28 + FIXTURE_VARIANT(scoping_signals) 29 + { 30 + int sig; 31 + }; 32 + 33 + /* clang-format off */ 34 + FIXTURE_VARIANT_ADD(scoping_signals, sigtrap) { 35 + /* clang-format on */ 36 + .sig = SIGTRAP, 37 + }; 38 + 39 + /* clang-format off */ 40 + FIXTURE_VARIANT_ADD(scoping_signals, sigurg) { 41 + /* clang-format on */ 42 + .sig = SIGURG, 43 + }; 44 + 45 + /* clang-format off */ 46 + FIXTURE_VARIANT_ADD(scoping_signals, sighup) { 47 + /* clang-format on */ 48 + .sig = SIGHUP, 49 + }; 50 + 51 + /* clang-format off */ 52 + FIXTURE_VARIANT_ADD(scoping_signals, sigtstp) { 53 + /* clang-format on */ 54 + .sig = SIGTSTP, 55 + }; 56 + 57 + FIXTURE_SETUP(scoping_signals) 58 + { 59 + drop_caps(_metadata); 60 + 61 + is_signaled = 0; 62 + } 63 + 64 + FIXTURE_TEARDOWN(scoping_signals) 65 + { 66 + } 67 + 68 + static void scope_signal_handler(int sig, siginfo_t *info, void *ucontext) 69 + { 70 + if (sig == SIGTRAP || sig == SIGURG || sig == SIGHUP || sig == SIGTSTP) 71 + is_signaled = 1; 72 + } 73 + 74 + /* 75 + * In this test, a child process sends a signal to parent before and 76 + * after getting scoped. 77 + */ 78 + TEST_F(scoping_signals, send_sig_to_parent) 79 + { 80 + int pipe_parent[2]; 81 + int status; 82 + pid_t child; 83 + pid_t parent = getpid(); 84 + struct sigaction action = { 85 + .sa_sigaction = scope_signal_handler, 86 + .sa_flags = SA_SIGINFO, 87 + 88 + }; 89 + 90 + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); 91 + ASSERT_LE(0, sigaction(variant->sig, &action, NULL)); 92 + 93 + /* The process should not have already been signaled. */ 94 + EXPECT_EQ(0, is_signaled); 95 + 96 + child = fork(); 97 + ASSERT_LE(0, child); 98 + if (child == 0) { 99 + char buf_child; 100 + int err; 101 + 102 + EXPECT_EQ(0, close(pipe_parent[1])); 103 + 104 + /* 105 + * The child process can send signal to parent when 106 + * domain is not scoped. 107 + */ 108 + err = kill(parent, variant->sig); 109 + ASSERT_EQ(0, err); 110 + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); 111 + EXPECT_EQ(0, close(pipe_parent[0])); 112 + 113 + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); 114 + 115 + /* 116 + * The child process cannot send signal to the parent 117 + * anymore. 118 + */ 119 + err = kill(parent, variant->sig); 120 + ASSERT_EQ(-1, err); 121 + ASSERT_EQ(EPERM, errno); 122 + 123 + /* 124 + * No matter of the domain, a process should be able to 125 + * send a signal to itself. 126 + */ 127 + ASSERT_EQ(0, is_signaled); 128 + ASSERT_EQ(0, raise(variant->sig)); 129 + ASSERT_EQ(1, is_signaled); 130 + 131 + _exit(_metadata->exit_code); 132 + return; 133 + } 134 + EXPECT_EQ(0, close(pipe_parent[0])); 135 + 136 + /* Waits for a first signal to be received, without race condition. */ 137 + while (!is_signaled && !usleep(1)) 138 + ; 139 + ASSERT_EQ(1, is_signaled); 140 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 141 + EXPECT_EQ(0, close(pipe_parent[1])); 142 + is_signaled = 0; 143 + 144 + ASSERT_EQ(child, waitpid(child, &status, 0)); 145 + if (WIFSIGNALED(status) || !WIFEXITED(status) || 146 + WEXITSTATUS(status) != EXIT_SUCCESS) 147 + _metadata->exit_code = KSFT_FAIL; 148 + 149 + EXPECT_EQ(0, is_signaled); 150 + } 151 + 152 + /* clang-format off */ 153 + FIXTURE(scoped_domains) {}; 154 + /* clang-format on */ 155 + 156 + #include "scoped_base_variants.h" 157 + 158 + FIXTURE_SETUP(scoped_domains) 159 + { 160 + drop_caps(_metadata); 161 + } 162 + 163 + FIXTURE_TEARDOWN(scoped_domains) 164 + { 165 + } 166 + 167 + /* 168 + * This test ensures that a scoped process cannot send signal out of 169 + * scoped domain. 170 + */ 171 + TEST_F(scoped_domains, check_access_signal) 172 + { 173 + pid_t child; 174 + pid_t parent = getpid(); 175 + int status; 176 + bool can_signal_child, can_signal_parent; 177 + int pipe_parent[2], pipe_child[2]; 178 + char buf_parent; 179 + int err; 180 + 181 + can_signal_parent = !variant->domain_child; 182 + can_signal_child = !variant->domain_parent; 183 + 184 + if (variant->domain_both) 185 + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); 186 + 187 + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); 188 + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); 189 + 190 + child = fork(); 191 + ASSERT_LE(0, child); 192 + if (child == 0) { 193 + char buf_child; 194 + 195 + EXPECT_EQ(0, close(pipe_child[0])); 196 + EXPECT_EQ(0, close(pipe_parent[1])); 197 + 198 + if (variant->domain_child) 199 + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); 200 + 201 + ASSERT_EQ(1, write(pipe_child[1], ".", 1)); 202 + EXPECT_EQ(0, close(pipe_child[1])); 203 + 204 + /* Waits for the parent to send signals. */ 205 + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); 206 + EXPECT_EQ(0, close(pipe_parent[0])); 207 + 208 + err = kill(parent, 0); 209 + if (can_signal_parent) { 210 + ASSERT_EQ(0, err); 211 + } else { 212 + ASSERT_EQ(-1, err); 213 + ASSERT_EQ(EPERM, errno); 214 + } 215 + /* 216 + * No matter of the domain, a process should be able to 217 + * send a signal to itself. 218 + */ 219 + ASSERT_EQ(0, raise(0)); 220 + 221 + _exit(_metadata->exit_code); 222 + return; 223 + } 224 + EXPECT_EQ(0, close(pipe_parent[0])); 225 + EXPECT_EQ(0, close(pipe_child[1])); 226 + 227 + if (variant->domain_parent) 228 + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); 229 + 230 + ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1)); 231 + EXPECT_EQ(0, close(pipe_child[0])); 232 + 233 + err = kill(child, 0); 234 + if (can_signal_child) { 235 + ASSERT_EQ(0, err); 236 + } else { 237 + ASSERT_EQ(-1, err); 238 + ASSERT_EQ(EPERM, errno); 239 + } 240 + ASSERT_EQ(0, raise(0)); 241 + 242 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 243 + EXPECT_EQ(0, close(pipe_parent[1])); 244 + ASSERT_EQ(child, waitpid(child, &status, 0)); 245 + 246 + if (WIFSIGNALED(status) || !WIFEXITED(status) || 247 + WEXITSTATUS(status) != EXIT_SUCCESS) 248 + _metadata->exit_code = KSFT_FAIL; 249 + } 250 + 251 + TEST_HARNESS_MAIN