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

selftests/landlock: Test abstract UNIX socket scoping

Add three tests that examine different scenarios for abstract UNIX
socket:

1) scoped_domains: Base tests of the abstract socket scoping mechanism
for a landlocked process, same as the ptrace test.

2) scoped_vs_unscoped: Generates three processes with different domains
and tests if a process with a non-scoped domain can connect to other
processes.

3) outside_socket: Since the socket's creator credentials are used
for scoping sockets, this test examines the cases where the socket's
credentials are different from the process using it.

Move protocol_variant, service_fixture, and sys_gettid() from net_test.c
to common.h, and factor out code into a new set_unix_address() helper.

Signed-off-by: Tahera Fahimi <fahimitahera@gmail.com>
Link: https://lore.kernel.org/r/9321c3d3bcd9212ceb4b50693e29349f8d625e16.1725494372.git.fahimitahera@gmail.com
[mic: Fix commit message, remove useless clang-format tags, move
drop_caps() calls, move and rename variables, rename variants, use more
EXPECT, improve comments, simplify the outside_socket test]
Signed-off-by: Mickaël Salaün <mic@digikod.net>

authored by

Tahera Fahimi and committed by
Mickaël Salaün
fefcf0f7 5b6b63cd

+998 -30
+37
tools/testing/selftests/landlock/common.h
··· 7 7 * Copyright © 2021 Microsoft Corporation 8 8 */ 9 9 10 + #include <arpa/inet.h> 10 11 #include <errno.h> 11 12 #include <linux/landlock.h> 12 13 #include <linux/securebits.h> ··· 15 14 #include <sys/socket.h> 16 15 #include <sys/syscall.h> 17 16 #include <sys/types.h> 17 + #include <sys/un.h> 18 18 #include <sys/wait.h> 19 19 #include <unistd.h> 20 20 ··· 227 225 { 228 226 TH_LOG("Failed to enforce ruleset: %s", strerror(errno)); 229 227 } 228 + } 229 + 230 + struct protocol_variant { 231 + int domain; 232 + int type; 233 + }; 234 + 235 + struct service_fixture { 236 + struct protocol_variant protocol; 237 + /* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */ 238 + unsigned short port; 239 + union { 240 + struct sockaddr_in ipv4_addr; 241 + struct sockaddr_in6 ipv6_addr; 242 + struct { 243 + struct sockaddr_un unix_addr; 244 + socklen_t unix_addr_len; 245 + }; 246 + }; 247 + }; 248 + 249 + static pid_t __maybe_unused sys_gettid(void) 250 + { 251 + return syscall(__NR_gettid); 252 + } 253 + 254 + static void __maybe_unused set_unix_address(struct service_fixture *const srv, 255 + const unsigned short index) 256 + { 257 + srv->unix_addr.sun_family = AF_UNIX; 258 + sprintf(srv->unix_addr.sun_path, 259 + "_selftests-landlock-abstract-unix-tid%d-index%d", sys_gettid(), 260 + index); 261 + srv->unix_addr_len = SUN_LEN(&srv->unix_addr); 262 + srv->unix_addr.sun_path[0] = '\0'; 230 263 }
+1 -30
tools/testing/selftests/landlock/net_test.c
··· 36 36 TCP_SANDBOX, 37 37 }; 38 38 39 - struct protocol_variant { 40 - int domain; 41 - int type; 42 - }; 43 - 44 - struct service_fixture { 45 - struct protocol_variant protocol; 46 - /* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */ 47 - unsigned short port; 48 - union { 49 - struct sockaddr_in ipv4_addr; 50 - struct sockaddr_in6 ipv6_addr; 51 - struct { 52 - struct sockaddr_un unix_addr; 53 - socklen_t unix_addr_len; 54 - }; 55 - }; 56 - }; 57 - 58 - static pid_t sys_gettid(void) 59 - { 60 - return syscall(__NR_gettid); 61 - } 62 - 63 39 static int set_service(struct service_fixture *const srv, 64 40 const struct protocol_variant prot, 65 41 const unsigned short index) ··· 68 92 return 0; 69 93 70 94 case AF_UNIX: 71 - srv->unix_addr.sun_family = prot.domain; 72 - sprintf(srv->unix_addr.sun_path, 73 - "_selftests-landlock-net-tid%d-index%d", sys_gettid(), 74 - index); 75 - srv->unix_addr_len = SUN_LEN(&srv->unix_addr); 76 - srv->unix_addr.sun_path[0] = '\0'; 95 + set_unix_address(srv, index); 77 96 return 0; 78 97 } 79 98 return 1;
+624
tools/testing/selftests/landlock/scoped_abstract_unix_test.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Landlock tests - Abstract UNIX socket 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 <sched.h> 13 + #include <signal.h> 14 + #include <stddef.h> 15 + #include <sys/prctl.h> 16 + #include <sys/socket.h> 17 + #include <sys/stat.h> 18 + #include <sys/types.h> 19 + #include <sys/un.h> 20 + #include <sys/wait.h> 21 + #include <unistd.h> 22 + 23 + #include "common.h" 24 + #include "scoped_common.h" 25 + 26 + /* Number of pending connections queue to be hold. */ 27 + const short backlog = 10; 28 + 29 + static void create_fs_domain(struct __test_metadata *const _metadata) 30 + { 31 + int ruleset_fd; 32 + struct landlock_ruleset_attr ruleset_attr = { 33 + .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 34 + }; 35 + 36 + ruleset_fd = 37 + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); 38 + EXPECT_LE(0, ruleset_fd) 39 + { 40 + TH_LOG("Failed to create a ruleset: %s", strerror(errno)); 41 + } 42 + EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); 43 + EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0)); 44 + EXPECT_EQ(0, close(ruleset_fd)); 45 + } 46 + 47 + FIXTURE(scoped_domains) 48 + { 49 + struct service_fixture stream_address, dgram_address; 50 + }; 51 + 52 + #include "scoped_base_variants.h" 53 + 54 + FIXTURE_SETUP(scoped_domains) 55 + { 56 + drop_caps(_metadata); 57 + 58 + memset(&self->stream_address, 0, sizeof(self->stream_address)); 59 + memset(&self->dgram_address, 0, sizeof(self->dgram_address)); 60 + set_unix_address(&self->stream_address, 0); 61 + set_unix_address(&self->dgram_address, 1); 62 + } 63 + 64 + FIXTURE_TEARDOWN(scoped_domains) 65 + { 66 + } 67 + 68 + /* 69 + * Test unix_stream_connect() and unix_may_send() for a child connecting to its 70 + * parent, when they have scoped domain or no domain. 71 + */ 72 + TEST_F(scoped_domains, connect_to_parent) 73 + { 74 + pid_t child; 75 + bool can_connect_to_parent; 76 + int status; 77 + int pipe_parent[2]; 78 + int stream_server, dgram_server; 79 + 80 + /* 81 + * can_connect_to_parent is true if a child process can connect to its 82 + * parent process. This depends on the child process not being isolated 83 + * from the parent with a dedicated Landlock domain. 84 + */ 85 + can_connect_to_parent = !variant->domain_child; 86 + 87 + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); 88 + if (variant->domain_both) { 89 + create_scoped_domain(_metadata, 90 + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 91 + if (!__test_passed(_metadata)) 92 + return; 93 + } 94 + 95 + child = fork(); 96 + ASSERT_LE(0, child); 97 + if (child == 0) { 98 + int err; 99 + int stream_client, dgram_client; 100 + char buf_child; 101 + 102 + EXPECT_EQ(0, close(pipe_parent[1])); 103 + if (variant->domain_child) 104 + create_scoped_domain( 105 + _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 106 + 107 + stream_client = socket(AF_UNIX, SOCK_STREAM, 0); 108 + ASSERT_LE(0, stream_client); 109 + dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0); 110 + ASSERT_LE(0, dgram_client); 111 + 112 + /* Waits for the server. */ 113 + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); 114 + 115 + err = connect(stream_client, &self->stream_address.unix_addr, 116 + self->stream_address.unix_addr_len); 117 + if (can_connect_to_parent) { 118 + EXPECT_EQ(0, err); 119 + } else { 120 + EXPECT_EQ(-1, err); 121 + EXPECT_EQ(EPERM, errno); 122 + } 123 + EXPECT_EQ(0, close(stream_client)); 124 + 125 + err = connect(dgram_client, &self->dgram_address.unix_addr, 126 + self->dgram_address.unix_addr_len); 127 + if (can_connect_to_parent) { 128 + EXPECT_EQ(0, err); 129 + } else { 130 + EXPECT_EQ(-1, err); 131 + EXPECT_EQ(EPERM, errno); 132 + } 133 + EXPECT_EQ(0, close(dgram_client)); 134 + _exit(_metadata->exit_code); 135 + return; 136 + } 137 + EXPECT_EQ(0, close(pipe_parent[0])); 138 + if (variant->domain_parent) 139 + create_scoped_domain(_metadata, 140 + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 141 + 142 + stream_server = socket(AF_UNIX, SOCK_STREAM, 0); 143 + ASSERT_LE(0, stream_server); 144 + dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0); 145 + ASSERT_LE(0, dgram_server); 146 + ASSERT_EQ(0, bind(stream_server, &self->stream_address.unix_addr, 147 + self->stream_address.unix_addr_len)); 148 + ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr, 149 + self->dgram_address.unix_addr_len)); 150 + ASSERT_EQ(0, listen(stream_server, backlog)); 151 + 152 + /* Signals to child that the parent is listening. */ 153 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 154 + 155 + ASSERT_EQ(child, waitpid(child, &status, 0)); 156 + EXPECT_EQ(0, close(stream_server)); 157 + EXPECT_EQ(0, close(dgram_server)); 158 + 159 + if (WIFSIGNALED(status) || !WIFEXITED(status) || 160 + WEXITSTATUS(status) != EXIT_SUCCESS) 161 + _metadata->exit_code = KSFT_FAIL; 162 + } 163 + 164 + /* 165 + * Test unix_stream_connect() and unix_may_send() for a parent connecting to 166 + * its child, when they have scoped domain or no domain. 167 + */ 168 + TEST_F(scoped_domains, connect_to_child) 169 + { 170 + pid_t child; 171 + bool can_connect_to_child; 172 + int err_stream, err_dgram, errno_stream, errno_dgram, status; 173 + int pipe_child[2], pipe_parent[2]; 174 + char buf; 175 + int stream_client, dgram_client; 176 + 177 + /* 178 + * can_connect_to_child is true if a parent process can connect to its 179 + * child process. The parent process is not isolated from the child 180 + * with a dedicated Landlock domain. 181 + */ 182 + can_connect_to_child = !variant->domain_parent; 183 + 184 + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); 185 + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); 186 + if (variant->domain_both) { 187 + create_scoped_domain(_metadata, 188 + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 189 + if (!__test_passed(_metadata)) 190 + return; 191 + } 192 + 193 + child = fork(); 194 + ASSERT_LE(0, child); 195 + if (child == 0) { 196 + int stream_server, dgram_server; 197 + 198 + EXPECT_EQ(0, close(pipe_parent[1])); 199 + EXPECT_EQ(0, close(pipe_child[0])); 200 + if (variant->domain_child) 201 + create_scoped_domain( 202 + _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 203 + 204 + /* Waits for the parent to be in a domain, if any. */ 205 + ASSERT_EQ(1, read(pipe_parent[0], &buf, 1)); 206 + 207 + stream_server = socket(AF_UNIX, SOCK_STREAM, 0); 208 + ASSERT_LE(0, stream_server); 209 + dgram_server = socket(AF_UNIX, SOCK_DGRAM, 0); 210 + ASSERT_LE(0, dgram_server); 211 + ASSERT_EQ(0, 212 + bind(stream_server, &self->stream_address.unix_addr, 213 + self->stream_address.unix_addr_len)); 214 + ASSERT_EQ(0, bind(dgram_server, &self->dgram_address.unix_addr, 215 + self->dgram_address.unix_addr_len)); 216 + ASSERT_EQ(0, listen(stream_server, backlog)); 217 + 218 + /* Signals to the parent that child is listening. */ 219 + ASSERT_EQ(1, write(pipe_child[1], ".", 1)); 220 + 221 + /* Waits to connect. */ 222 + ASSERT_EQ(1, read(pipe_parent[0], &buf, 1)); 223 + EXPECT_EQ(0, close(stream_server)); 224 + EXPECT_EQ(0, close(dgram_server)); 225 + _exit(_metadata->exit_code); 226 + return; 227 + } 228 + EXPECT_EQ(0, close(pipe_child[1])); 229 + EXPECT_EQ(0, close(pipe_parent[0])); 230 + 231 + if (variant->domain_parent) 232 + create_scoped_domain(_metadata, 233 + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 234 + 235 + /* Signals that the parent is in a domain, if any. */ 236 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 237 + 238 + stream_client = socket(AF_UNIX, SOCK_STREAM, 0); 239 + ASSERT_LE(0, stream_client); 240 + dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0); 241 + ASSERT_LE(0, dgram_client); 242 + 243 + /* Waits for the child to listen */ 244 + ASSERT_EQ(1, read(pipe_child[0], &buf, 1)); 245 + err_stream = connect(stream_client, &self->stream_address.unix_addr, 246 + self->stream_address.unix_addr_len); 247 + errno_stream = errno; 248 + err_dgram = connect(dgram_client, &self->dgram_address.unix_addr, 249 + self->dgram_address.unix_addr_len); 250 + errno_dgram = errno; 251 + if (can_connect_to_child) { 252 + EXPECT_EQ(0, err_stream); 253 + EXPECT_EQ(0, err_dgram); 254 + } else { 255 + EXPECT_EQ(-1, err_stream); 256 + EXPECT_EQ(-1, err_dgram); 257 + EXPECT_EQ(EPERM, errno_stream); 258 + EXPECT_EQ(EPERM, errno_dgram); 259 + } 260 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 261 + EXPECT_EQ(0, close(stream_client)); 262 + EXPECT_EQ(0, close(dgram_client)); 263 + 264 + ASSERT_EQ(child, waitpid(child, &status, 0)); 265 + if (WIFSIGNALED(status) || !WIFEXITED(status) || 266 + WEXITSTATUS(status) != EXIT_SUCCESS) 267 + _metadata->exit_code = KSFT_FAIL; 268 + } 269 + 270 + FIXTURE(scoped_vs_unscoped) 271 + { 272 + struct service_fixture parent_stream_address, parent_dgram_address, 273 + child_stream_address, child_dgram_address; 274 + }; 275 + 276 + #include "scoped_multiple_domain_variants.h" 277 + 278 + FIXTURE_SETUP(scoped_vs_unscoped) 279 + { 280 + drop_caps(_metadata); 281 + 282 + memset(&self->parent_stream_address, 0, 283 + sizeof(self->parent_stream_address)); 284 + set_unix_address(&self->parent_stream_address, 0); 285 + memset(&self->parent_dgram_address, 0, 286 + sizeof(self->parent_dgram_address)); 287 + set_unix_address(&self->parent_dgram_address, 1); 288 + memset(&self->child_stream_address, 0, 289 + sizeof(self->child_stream_address)); 290 + set_unix_address(&self->child_stream_address, 2); 291 + memset(&self->child_dgram_address, 0, 292 + sizeof(self->child_dgram_address)); 293 + set_unix_address(&self->child_dgram_address, 3); 294 + } 295 + 296 + FIXTURE_TEARDOWN(scoped_vs_unscoped) 297 + { 298 + } 299 + 300 + /* 301 + * Test unix_stream_connect and unix_may_send for parent, child and 302 + * grand child processes when they can have scoped or non-scoped domains. 303 + */ 304 + TEST_F(scoped_vs_unscoped, unix_scoping) 305 + { 306 + pid_t child; 307 + int status; 308 + bool can_connect_to_parent, can_connect_to_child; 309 + int pipe_parent[2]; 310 + int stream_server_parent, dgram_server_parent; 311 + 312 + can_connect_to_child = (variant->domain_grand_child != SCOPE_SANDBOX); 313 + can_connect_to_parent = (can_connect_to_child && 314 + (variant->domain_children != SCOPE_SANDBOX)); 315 + 316 + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); 317 + 318 + if (variant->domain_all == OTHER_SANDBOX) 319 + create_fs_domain(_metadata); 320 + else if (variant->domain_all == SCOPE_SANDBOX) 321 + create_scoped_domain(_metadata, 322 + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 323 + 324 + child = fork(); 325 + ASSERT_LE(0, child); 326 + if (child == 0) { 327 + int stream_server_child, dgram_server_child; 328 + int pipe_child[2]; 329 + pid_t grand_child; 330 + 331 + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); 332 + 333 + if (variant->domain_children == OTHER_SANDBOX) 334 + create_fs_domain(_metadata); 335 + else if (variant->domain_children == SCOPE_SANDBOX) 336 + create_scoped_domain( 337 + _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 338 + 339 + grand_child = fork(); 340 + ASSERT_LE(0, grand_child); 341 + if (grand_child == 0) { 342 + char buf; 343 + int stream_err, dgram_err, stream_errno, dgram_errno; 344 + int stream_client, dgram_client; 345 + 346 + EXPECT_EQ(0, close(pipe_parent[1])); 347 + EXPECT_EQ(0, close(pipe_child[1])); 348 + 349 + if (variant->domain_grand_child == OTHER_SANDBOX) 350 + create_fs_domain(_metadata); 351 + else if (variant->domain_grand_child == SCOPE_SANDBOX) 352 + create_scoped_domain( 353 + _metadata, 354 + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 355 + 356 + stream_client = socket(AF_UNIX, SOCK_STREAM, 0); 357 + ASSERT_LE(0, stream_client); 358 + dgram_client = socket(AF_UNIX, SOCK_DGRAM, 0); 359 + ASSERT_LE(0, dgram_client); 360 + 361 + ASSERT_EQ(1, read(pipe_child[0], &buf, 1)); 362 + stream_err = connect( 363 + stream_client, 364 + &self->child_stream_address.unix_addr, 365 + self->child_stream_address.unix_addr_len); 366 + stream_errno = errno; 367 + dgram_err = connect( 368 + dgram_client, 369 + &self->child_dgram_address.unix_addr, 370 + self->child_dgram_address.unix_addr_len); 371 + dgram_errno = errno; 372 + if (can_connect_to_child) { 373 + EXPECT_EQ(0, stream_err); 374 + EXPECT_EQ(0, dgram_err); 375 + } else { 376 + EXPECT_EQ(-1, stream_err); 377 + EXPECT_EQ(-1, dgram_err); 378 + EXPECT_EQ(EPERM, stream_errno); 379 + EXPECT_EQ(EPERM, dgram_errno); 380 + } 381 + 382 + EXPECT_EQ(0, close(stream_client)); 383 + stream_client = socket(AF_UNIX, SOCK_STREAM, 0); 384 + ASSERT_LE(0, stream_client); 385 + /* Datagram sockets can "reconnect". */ 386 + 387 + ASSERT_EQ(1, read(pipe_parent[0], &buf, 1)); 388 + stream_err = connect( 389 + stream_client, 390 + &self->parent_stream_address.unix_addr, 391 + self->parent_stream_address.unix_addr_len); 392 + stream_errno = errno; 393 + dgram_err = connect( 394 + dgram_client, 395 + &self->parent_dgram_address.unix_addr, 396 + self->parent_dgram_address.unix_addr_len); 397 + dgram_errno = errno; 398 + if (can_connect_to_parent) { 399 + EXPECT_EQ(0, stream_err); 400 + EXPECT_EQ(0, dgram_err); 401 + } else { 402 + EXPECT_EQ(-1, stream_err); 403 + EXPECT_EQ(-1, dgram_err); 404 + EXPECT_EQ(EPERM, stream_errno); 405 + EXPECT_EQ(EPERM, dgram_errno); 406 + } 407 + EXPECT_EQ(0, close(stream_client)); 408 + EXPECT_EQ(0, close(dgram_client)); 409 + 410 + _exit(_metadata->exit_code); 411 + return; 412 + } 413 + EXPECT_EQ(0, close(pipe_child[0])); 414 + if (variant->domain_child == OTHER_SANDBOX) 415 + create_fs_domain(_metadata); 416 + else if (variant->domain_child == SCOPE_SANDBOX) 417 + create_scoped_domain( 418 + _metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 419 + 420 + stream_server_child = socket(AF_UNIX, SOCK_STREAM, 0); 421 + ASSERT_LE(0, stream_server_child); 422 + dgram_server_child = socket(AF_UNIX, SOCK_DGRAM, 0); 423 + ASSERT_LE(0, dgram_server_child); 424 + 425 + ASSERT_EQ(0, bind(stream_server_child, 426 + &self->child_stream_address.unix_addr, 427 + self->child_stream_address.unix_addr_len)); 428 + ASSERT_EQ(0, bind(dgram_server_child, 429 + &self->child_dgram_address.unix_addr, 430 + self->child_dgram_address.unix_addr_len)); 431 + ASSERT_EQ(0, listen(stream_server_child, backlog)); 432 + 433 + ASSERT_EQ(1, write(pipe_child[1], ".", 1)); 434 + ASSERT_EQ(grand_child, waitpid(grand_child, &status, 0)); 435 + EXPECT_EQ(0, close(stream_server_child)) 436 + EXPECT_EQ(0, close(dgram_server_child)); 437 + return; 438 + } 439 + EXPECT_EQ(0, close(pipe_parent[0])); 440 + 441 + if (variant->domain_parent == OTHER_SANDBOX) 442 + create_fs_domain(_metadata); 443 + else if (variant->domain_parent == SCOPE_SANDBOX) 444 + create_scoped_domain(_metadata, 445 + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 446 + 447 + stream_server_parent = socket(AF_UNIX, SOCK_STREAM, 0); 448 + ASSERT_LE(0, stream_server_parent); 449 + dgram_server_parent = socket(AF_UNIX, SOCK_DGRAM, 0); 450 + ASSERT_LE(0, dgram_server_parent); 451 + ASSERT_EQ(0, bind(stream_server_parent, 452 + &self->parent_stream_address.unix_addr, 453 + self->parent_stream_address.unix_addr_len)); 454 + ASSERT_EQ(0, bind(dgram_server_parent, 455 + &self->parent_dgram_address.unix_addr, 456 + self->parent_dgram_address.unix_addr_len)); 457 + 458 + ASSERT_EQ(0, listen(stream_server_parent, backlog)); 459 + 460 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 461 + ASSERT_EQ(child, waitpid(child, &status, 0)); 462 + EXPECT_EQ(0, close(stream_server_parent)); 463 + EXPECT_EQ(0, close(dgram_server_parent)); 464 + 465 + if (WIFSIGNALED(status) || !WIFEXITED(status) || 466 + WEXITSTATUS(status) != EXIT_SUCCESS) 467 + _metadata->exit_code = KSFT_FAIL; 468 + } 469 + 470 + FIXTURE(outside_socket) 471 + { 472 + struct service_fixture address, transit_address; 473 + }; 474 + 475 + FIXTURE_VARIANT(outside_socket) 476 + { 477 + const bool child_socket; 478 + const int type; 479 + }; 480 + 481 + /* clang-format off */ 482 + FIXTURE_VARIANT_ADD(outside_socket, allow_dgram_child) { 483 + /* clang-format on */ 484 + .child_socket = true, 485 + .type = SOCK_DGRAM, 486 + }; 487 + 488 + /* clang-format off */ 489 + FIXTURE_VARIANT_ADD(outside_socket, deny_dgram_server) { 490 + /* clang-format on */ 491 + .child_socket = false, 492 + .type = SOCK_DGRAM, 493 + }; 494 + 495 + /* clang-format off */ 496 + FIXTURE_VARIANT_ADD(outside_socket, allow_stream_child) { 497 + /* clang-format on */ 498 + .child_socket = true, 499 + .type = SOCK_STREAM, 500 + }; 501 + 502 + /* clang-format off */ 503 + FIXTURE_VARIANT_ADD(outside_socket, deny_stream_server) { 504 + /* clang-format on */ 505 + .child_socket = false, 506 + .type = SOCK_STREAM, 507 + }; 508 + 509 + FIXTURE_SETUP(outside_socket) 510 + { 511 + drop_caps(_metadata); 512 + 513 + memset(&self->transit_address, 0, sizeof(self->transit_address)); 514 + set_unix_address(&self->transit_address, 0); 515 + memset(&self->address, 0, sizeof(self->address)); 516 + set_unix_address(&self->address, 1); 517 + } 518 + 519 + FIXTURE_TEARDOWN(outside_socket) 520 + { 521 + } 522 + 523 + /* 524 + * Test unix_stream_connect and unix_may_send for parent and child processes 525 + * when connecting socket has different domain than the process using it. 526 + */ 527 + TEST_F(outside_socket, socket_with_different_domain) 528 + { 529 + pid_t child; 530 + int err, status; 531 + int pipe_child[2], pipe_parent[2]; 532 + char buf_parent; 533 + int server_socket; 534 + 535 + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); 536 + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); 537 + 538 + child = fork(); 539 + ASSERT_LE(0, child); 540 + if (child == 0) { 541 + int client_socket; 542 + char buf_child; 543 + 544 + EXPECT_EQ(0, close(pipe_parent[1])); 545 + EXPECT_EQ(0, close(pipe_child[0])); 546 + 547 + /* Client always has a domain. */ 548 + create_scoped_domain(_metadata, 549 + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 550 + 551 + if (variant->child_socket) { 552 + int data_socket, passed_socket, stream_server; 553 + 554 + passed_socket = socket(AF_UNIX, variant->type, 0); 555 + ASSERT_LE(0, passed_socket); 556 + stream_server = socket(AF_UNIX, SOCK_STREAM, 0); 557 + ASSERT_LE(0, stream_server); 558 + ASSERT_EQ(0, bind(stream_server, 559 + &self->transit_address.unix_addr, 560 + self->transit_address.unix_addr_len)); 561 + ASSERT_EQ(0, listen(stream_server, backlog)); 562 + ASSERT_EQ(1, write(pipe_child[1], ".", 1)); 563 + data_socket = accept(stream_server, NULL, NULL); 564 + ASSERT_LE(0, data_socket); 565 + ASSERT_EQ(0, send_fd(data_socket, passed_socket)); 566 + EXPECT_EQ(0, close(passed_socket)); 567 + EXPECT_EQ(0, close(stream_server)); 568 + } 569 + 570 + client_socket = socket(AF_UNIX, variant->type, 0); 571 + ASSERT_LE(0, client_socket); 572 + 573 + /* Waits for parent signal for connection. */ 574 + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); 575 + err = connect(client_socket, &self->address.unix_addr, 576 + self->address.unix_addr_len); 577 + if (variant->child_socket) { 578 + EXPECT_EQ(0, err); 579 + } else { 580 + EXPECT_EQ(-1, err); 581 + EXPECT_EQ(EPERM, errno); 582 + } 583 + EXPECT_EQ(0, close(client_socket)); 584 + _exit(_metadata->exit_code); 585 + return; 586 + } 587 + EXPECT_EQ(0, close(pipe_child[1])); 588 + EXPECT_EQ(0, close(pipe_parent[0])); 589 + 590 + if (variant->child_socket) { 591 + int client_child = socket(AF_UNIX, SOCK_STREAM, 0); 592 + 593 + ASSERT_LE(0, client_child); 594 + ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1)); 595 + ASSERT_EQ(0, connect(client_child, 596 + &self->transit_address.unix_addr, 597 + self->transit_address.unix_addr_len)); 598 + server_socket = recv_fd(client_child); 599 + EXPECT_EQ(0, close(client_child)); 600 + } else { 601 + server_socket = socket(AF_UNIX, variant->type, 0); 602 + } 603 + ASSERT_LE(0, server_socket); 604 + 605 + /* Server always has a domain. */ 606 + create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); 607 + 608 + ASSERT_EQ(0, bind(server_socket, &self->address.unix_addr, 609 + self->address.unix_addr_len)); 610 + if (variant->type == SOCK_STREAM) 611 + ASSERT_EQ(0, listen(server_socket, backlog)); 612 + 613 + /* Signals to child that the parent is listening. */ 614 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 615 + 616 + ASSERT_EQ(child, waitpid(child, &status, 0)); 617 + EXPECT_EQ(0, close(server_socket)); 618 + 619 + if (WIFSIGNALED(status) || !WIFEXITED(status) || 620 + WEXITSTATUS(status) != EXIT_SUCCESS) 621 + _metadata->exit_code = KSFT_FAIL; 622 + } 623 + 624 + TEST_HARNESS_MAIN
+156
tools/testing/selftests/landlock/scoped_base_variants.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * Landlock scoped_domains variants 4 + * 5 + * See the hierarchy variants from ptrace_test.c 6 + * 7 + * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> 8 + * Copyright © 2019-2020 ANSSI 9 + * Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com> 10 + */ 11 + 12 + /* clang-format on */ 13 + FIXTURE_VARIANT(scoped_domains) 14 + { 15 + bool domain_both; 16 + bool domain_parent; 17 + bool domain_child; 18 + }; 19 + 20 + /* 21 + * No domain 22 + * 23 + * P1-. P1 -> P2 : allow 24 + * \ P2 -> P1 : allow 25 + * 'P2 26 + */ 27 + /* clang-format off */ 28 + FIXTURE_VARIANT_ADD(scoped_domains, without_domain) { 29 + /* clang-format on */ 30 + .domain_both = false, 31 + .domain_parent = false, 32 + .domain_child = false, 33 + }; 34 + 35 + /* 36 + * Child domain 37 + * 38 + * P1--. P1 -> P2 : allow 39 + * \ P2 -> P1 : deny 40 + * .'-----. 41 + * | P2 | 42 + * '------' 43 + */ 44 + /* clang-format off */ 45 + FIXTURE_VARIANT_ADD(scoped_domains, child_domain) { 46 + /* clang-format on */ 47 + .domain_both = false, 48 + .domain_parent = false, 49 + .domain_child = true, 50 + }; 51 + 52 + /* 53 + * Parent domain 54 + * .------. 55 + * | P1 --. P1 -> P2 : deny 56 + * '------' \ P2 -> P1 : allow 57 + * ' 58 + * P2 59 + */ 60 + /* clang-format off */ 61 + FIXTURE_VARIANT_ADD(scoped_domains, parent_domain) { 62 + /* clang-format on */ 63 + .domain_both = false, 64 + .domain_parent = true, 65 + .domain_child = false, 66 + }; 67 + 68 + /* 69 + * Parent + child domain (siblings) 70 + * .------. 71 + * | P1 ---. P1 -> P2 : deny 72 + * '------' \ P2 -> P1 : deny 73 + * .---'--. 74 + * | P2 | 75 + * '------' 76 + */ 77 + /* clang-format off */ 78 + FIXTURE_VARIANT_ADD(scoped_domains, sibling_domain) { 79 + /* clang-format on */ 80 + .domain_both = false, 81 + .domain_parent = true, 82 + .domain_child = true, 83 + }; 84 + 85 + /* 86 + * Same domain (inherited) 87 + * .-------------. 88 + * | P1----. | P1 -> P2 : allow 89 + * | \ | P2 -> P1 : allow 90 + * | ' | 91 + * | P2 | 92 + * '-------------' 93 + */ 94 + /* clang-format off */ 95 + FIXTURE_VARIANT_ADD(scoped_domains, inherited_domain) { 96 + /* clang-format on */ 97 + .domain_both = true, 98 + .domain_parent = false, 99 + .domain_child = false, 100 + }; 101 + 102 + /* 103 + * Inherited + child domain 104 + * .-----------------. 105 + * | P1----. | P1 -> P2 : allow 106 + * | \ | P2 -> P1 : deny 107 + * | .-'----. | 108 + * | | P2 | | 109 + * | '------' | 110 + * '-----------------' 111 + */ 112 + /* clang-format off */ 113 + FIXTURE_VARIANT_ADD(scoped_domains, nested_domain) { 114 + /* clang-format on */ 115 + .domain_both = true, 116 + .domain_parent = false, 117 + .domain_child = true, 118 + }; 119 + 120 + /* 121 + * Inherited + parent domain 122 + * .-----------------. 123 + * |.------. | P1 -> P2 : deny 124 + * || P1 ----. | P2 -> P1 : allow 125 + * |'------' \ | 126 + * | ' | 127 + * | P2 | 128 + * '-----------------' 129 + */ 130 + /* clang-format off */ 131 + FIXTURE_VARIANT_ADD(scoped_domains, nested_and_parent_domain) { 132 + /* clang-format on */ 133 + .domain_both = true, 134 + .domain_parent = true, 135 + .domain_child = false, 136 + }; 137 + 138 + /* 139 + * Inherited + parent and child domain (siblings) 140 + * .-----------------. 141 + * | .------. | P1 -> P2 : deny 142 + * | | P1 . | P2 -> P1 : deny 143 + * | '------'\ | 144 + * | \ | 145 + * | .--'---. | 146 + * | | P2 | | 147 + * | '------' | 148 + * '-----------------' 149 + */ 150 + /* clang-format off */ 151 + FIXTURE_VARIANT_ADD(scoped_domains, forked_domains) { 152 + /* clang-format on */ 153 + .domain_both = true, 154 + .domain_parent = true, 155 + .domain_child = true, 156 + };
+28
tools/testing/selftests/landlock/scoped_common.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * Landlock scope test helpers 4 + * 5 + * Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com> 6 + */ 7 + 8 + #define _GNU_SOURCE 9 + 10 + #include <sys/types.h> 11 + 12 + static void create_scoped_domain(struct __test_metadata *const _metadata, 13 + const __u16 scope) 14 + { 15 + int ruleset_fd; 16 + const struct landlock_ruleset_attr ruleset_attr = { 17 + .scoped = scope, 18 + }; 19 + 20 + ruleset_fd = 21 + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); 22 + ASSERT_LE(0, ruleset_fd) 23 + { 24 + TH_LOG("Failed to create a ruleset: %s", strerror(errno)); 25 + } 26 + enforce_ruleset(_metadata, ruleset_fd); 27 + EXPECT_EQ(0, close(ruleset_fd)); 28 + }
+152
tools/testing/selftests/landlock/scoped_multiple_domain_variants.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * Landlock variants for three processes with various domains. 4 + * 5 + * Copyright © 2024 Tahera Fahimi <fahimitahera@gmail.com> 6 + */ 7 + 8 + enum sandbox_type { 9 + NO_SANDBOX, 10 + SCOPE_SANDBOX, 11 + /* Any other type of sandboxing domain */ 12 + OTHER_SANDBOX, 13 + }; 14 + 15 + /* clang-format on */ 16 + FIXTURE_VARIANT(scoped_vs_unscoped) 17 + { 18 + const int domain_all; 19 + const int domain_parent; 20 + const int domain_children; 21 + const int domain_child; 22 + const int domain_grand_child; 23 + }; 24 + 25 + /* 26 + * .-----------------. 27 + * | ####### | P3 -> P2 : allow 28 + * | P1----# P2 # | P3 -> P1 : deny 29 + * | # | # | 30 + * | # P3 # | 31 + * | ####### | 32 + * '-----------------' 33 + */ 34 + /* clang-format off */ 35 + FIXTURE_VARIANT_ADD(scoped_vs_unscoped, deny_scoped) { 36 + .domain_all = OTHER_SANDBOX, 37 + .domain_parent = NO_SANDBOX, 38 + .domain_children = SCOPE_SANDBOX, 39 + .domain_child = NO_SANDBOX, 40 + .domain_grand_child = NO_SANDBOX, 41 + /* clang-format on */ 42 + }; 43 + 44 + /* 45 + * ################### 46 + * # ####### # P3 -> P2 : allow 47 + * # P1----# P2 # # P3 -> P1 : deny 48 + * # # | # # 49 + * # # P3 # # 50 + * # ####### # 51 + * ################### 52 + */ 53 + /* clang-format off */ 54 + FIXTURE_VARIANT_ADD(scoped_vs_unscoped, all_scoped) { 55 + .domain_all = SCOPE_SANDBOX, 56 + .domain_parent = NO_SANDBOX, 57 + .domain_children = SCOPE_SANDBOX, 58 + .domain_child = NO_SANDBOX, 59 + .domain_grand_child = NO_SANDBOX, 60 + /* clang-format on */ 61 + }; 62 + 63 + /* 64 + * .-----------------. 65 + * | .-----. | P3 -> P2 : allow 66 + * | P1----| P2 | | P3 -> P1 : allow 67 + * | | | | 68 + * | | P3 | | 69 + * | '-----' | 70 + * '-----------------' 71 + */ 72 + /* clang-format off */ 73 + FIXTURE_VARIANT_ADD(scoped_vs_unscoped, allow_with_other_domain) { 74 + .domain_all = OTHER_SANDBOX, 75 + .domain_parent = NO_SANDBOX, 76 + .domain_children = OTHER_SANDBOX, 77 + .domain_child = NO_SANDBOX, 78 + .domain_grand_child = NO_SANDBOX, 79 + /* clang-format on */ 80 + }; 81 + 82 + /* 83 + * .----. ###### P3 -> P2 : allow 84 + * | P1 |----# P2 # P3 -> P1 : allow 85 + * '----' ###### 86 + * | 87 + * P3 88 + */ 89 + /* clang-format off */ 90 + FIXTURE_VARIANT_ADD(scoped_vs_unscoped, allow_with_one_domain) { 91 + .domain_all = NO_SANDBOX, 92 + .domain_parent = OTHER_SANDBOX, 93 + .domain_children = NO_SANDBOX, 94 + .domain_child = SCOPE_SANDBOX, 95 + .domain_grand_child = NO_SANDBOX, 96 + /* clang-format on */ 97 + }; 98 + 99 + /* 100 + * ###### .-----. P3 -> P2 : allow 101 + * # P1 #----| P2 | P3 -> P1 : allow 102 + * ###### '-----' 103 + * | 104 + * P3 105 + */ 106 + /* clang-format off */ 107 + FIXTURE_VARIANT_ADD(scoped_vs_unscoped, allow_with_grand_parent_scoped) { 108 + .domain_all = NO_SANDBOX, 109 + .domain_parent = SCOPE_SANDBOX, 110 + .domain_children = NO_SANDBOX, 111 + .domain_child = OTHER_SANDBOX, 112 + .domain_grand_child = NO_SANDBOX, 113 + /* clang-format on */ 114 + }; 115 + 116 + /* 117 + * ###### ###### P3 -> P2 : allow 118 + * # P1 #----# P2 # P3 -> P1 : allow 119 + * ###### ###### 120 + * | 121 + * .----. 122 + * | P3 | 123 + * '----' 124 + */ 125 + /* clang-format off */ 126 + FIXTURE_VARIANT_ADD(scoped_vs_unscoped, allow_with_parents_domain) { 127 + .domain_all = NO_SANDBOX, 128 + .domain_parent = SCOPE_SANDBOX, 129 + .domain_children = NO_SANDBOX, 130 + .domain_child = SCOPE_SANDBOX, 131 + .domain_grand_child = NO_SANDBOX, 132 + /* clang-format on */ 133 + }; 134 + 135 + /* 136 + * ###### P3 -> P2 : deny 137 + * # P1 #----P2 P3 -> P1 : deny 138 + * ###### | 139 + * | 140 + * ###### 141 + * # P3 # 142 + * ###### 143 + */ 144 + /* clang-format off */ 145 + FIXTURE_VARIANT_ADD(scoped_vs_unscoped, deny_with_self_and_grandparent_domain) { 146 + .domain_all = NO_SANDBOX, 147 + .domain_parent = SCOPE_SANDBOX, 148 + .domain_children = NO_SANDBOX, 149 + .domain_child = NO_SANDBOX, 150 + .domain_grand_child = SCOPE_SANDBOX, 151 + /* clang-format on */ 152 + };