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

Configure Feed

Select the types of activity you want to include in your feed.

landlock: Allow TSYNC with LOG_SUBDOMAINS_OFF and fd=-1

LANDLOCK_RESTRICT_SELF_TSYNC does not allow
LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with ruleset_fd=-1, preventing
a multithreaded process from atomically propagating subdomain log muting
to all threads without creating a domain layer. Relax the fd=-1
condition to accept TSYNC alongside LOG_SUBDOMAINS_OFF, and update the
documentation accordingly.

Add flag validation tests for all TSYNC combinations with ruleset_fd=-1,
and audit tests verifying both transition directions: muting via TSYNC
(logged to not logged) and override via TSYNC (not logged to logged).

Cc: Günther Noack <gnoack@google.com>
Cc: stable@vger.kernel.org
Fixes: 42fc7e6543f6 ("landlock: Multithreading support for landlock_restrict_self()")
Reviewed-by: Günther Noack <gnoack3000@gmail.com>
Link: https://lore.kernel.org/r/20260407164107.2012589-2-mic@digikod.net
Signed-off-by: Mickaël Salaün <mic@digikod.net>

+322 -6
+3 -1
include/uapi/linux/landlock.h
··· 116 116 * ``LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF``, this flag only affects 117 117 * future nested domains, not the one being created. It can also be used 118 118 * with a @ruleset_fd value of -1 to mute subdomain logs without creating a 119 - * domain. 119 + * domain. When combined with %LANDLOCK_RESTRICT_SELF_TSYNC and a 120 + * @ruleset_fd value of -1, this configuration is propagated to all threads 121 + * of the current process. 120 122 * 121 123 * The following flag supports policy enforcement in multithreaded processes: 122 124 *
+9 -5
security/landlock/syscalls.c
··· 512 512 513 513 /* 514 514 * It is allowed to set LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with 515 - * -1 as ruleset_fd, but no other flag must be set. 515 + * -1 as ruleset_fd, optionally combined with 516 + * LANDLOCK_RESTRICT_SELF_TSYNC to propagate this configuration to all 517 + * threads. No other flag must be set. 516 518 */ 517 519 if (!(ruleset_fd == -1 && 518 - flags == LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) { 520 + (flags & ~LANDLOCK_RESTRICT_SELF_TSYNC) == 521 + LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) { 519 522 /* Gets and checks the ruleset. */ 520 523 ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ); 521 524 if (IS_ERR(ruleset)) ··· 540 537 541 538 /* 542 539 * The only case when a ruleset may not be set is if 543 - * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF is set and ruleset_fd is -1. 544 - * We could optimize this case by not calling commit_creds() if this flag 545 - * was already set, but it is not worth the complexity. 540 + * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF is set (optionally with 541 + * LANDLOCK_RESTRICT_SELF_TSYNC) and ruleset_fd is -1. We could 542 + * optimize this case by not calling commit_creds() if this flag was 543 + * already set, but it is not worth the complexity. 546 544 */ 547 545 if (ruleset) { 548 546 /*
+233
tools/testing/selftests/landlock/audit_test.c
··· 162 162 struct thread_data { 163 163 pid_t parent_pid; 164 164 int ruleset_fd, pipe_child, pipe_parent; 165 + bool mute_subdomains; 165 166 }; 166 167 167 168 static void *thread_audit_test(void *arg) ··· 366 365 EXPECT_EQ(0, records.access); 367 366 368 367 EXPECT_EQ(0, close(ruleset_fd)); 368 + } 369 + 370 + /* 371 + * Thread function: runs two rounds of (create domain, trigger denial, signal 372 + * back), waiting for the main thread before each round. When mute_subdomains 373 + * is set, phase 1 also mutes subdomain logs via the fd=-1 path before creating 374 + * the domain. The ruleset_fd is kept open across both rounds so each 375 + * restrict_self call stacks a new domain layer. 376 + */ 377 + static void *thread_sandbox_deny_twice(void *arg) 378 + { 379 + const struct thread_data *data = (struct thread_data *)arg; 380 + uintptr_t err = 0; 381 + char buffer; 382 + 383 + /* Phase 1: optionally mutes, creates a domain, and triggers a denial. */ 384 + if (read(data->pipe_parent, &buffer, 1) != 1) { 385 + err = 1; 386 + goto out; 387 + } 388 + 389 + if (data->mute_subdomains && 390 + landlock_restrict_self(-1, 391 + LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) { 392 + err = 2; 393 + goto out; 394 + } 395 + 396 + if (landlock_restrict_self(data->ruleset_fd, 0)) { 397 + err = 3; 398 + goto out; 399 + } 400 + 401 + if (kill(data->parent_pid, 0) != -1 || errno != EPERM) { 402 + err = 4; 403 + goto out; 404 + } 405 + 406 + if (write(data->pipe_child, ".", 1) != 1) { 407 + err = 5; 408 + goto out; 409 + } 410 + 411 + /* Phase 2: stacks another domain and triggers a denial. */ 412 + if (read(data->pipe_parent, &buffer, 1) != 1) { 413 + err = 6; 414 + goto out; 415 + } 416 + 417 + if (landlock_restrict_self(data->ruleset_fd, 0)) { 418 + err = 7; 419 + goto out; 420 + } 421 + 422 + if (kill(data->parent_pid, 0) != -1 || errno != EPERM) { 423 + err = 8; 424 + goto out; 425 + } 426 + 427 + if (write(data->pipe_child, ".", 1) != 1) { 428 + err = 9; 429 + goto out; 430 + } 431 + 432 + out: 433 + close(data->ruleset_fd); 434 + close(data->pipe_child); 435 + close(data->pipe_parent); 436 + return (void *)err; 437 + } 438 + 439 + /* 440 + * Verifies that LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with 441 + * LANDLOCK_RESTRICT_SELF_TSYNC and ruleset_fd=-1 propagates log_subdomains_off 442 + * to a sibling thread, suppressing audit logging on domains it subsequently 443 + * creates. 444 + * 445 + * Phase 1 (before TSYNC) acts as an inline baseline: the sibling creates a 446 + * domain and triggers a denial that IS logged. 447 + * 448 + * Phase 2 (after TSYNC) verifies suppression: the sibling stacks another domain 449 + * and triggers a denial that is NOT logged. 450 + */ 451 + TEST_F(audit, log_subdomains_off_tsync) 452 + { 453 + const struct landlock_ruleset_attr ruleset_attr = { 454 + .scoped = LANDLOCK_SCOPE_SIGNAL, 455 + }; 456 + struct audit_records records; 457 + struct thread_data child_data = {}; 458 + int pipe_child[2], pipe_parent[2]; 459 + char buffer; 460 + pthread_t thread; 461 + void *thread_ret; 462 + 463 + child_data.parent_pid = getppid(); 464 + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); 465 + child_data.pipe_child = pipe_child[1]; 466 + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); 467 + child_data.pipe_parent = pipe_parent[0]; 468 + child_data.ruleset_fd = 469 + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); 470 + ASSERT_LE(0, child_data.ruleset_fd); 471 + 472 + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); 473 + 474 + /* Creates the sibling thread. */ 475 + ASSERT_EQ(0, pthread_create(&thread, NULL, thread_sandbox_deny_twice, 476 + &child_data)); 477 + 478 + /* 479 + * Phase 1: the sibling creates a domain and triggers a denial before 480 + * any log muting. This proves the audit path works. 481 + */ 482 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 483 + ASSERT_EQ(1, read(pipe_child[0], &buffer, 1)); 484 + 485 + /* The denial must be logged. */ 486 + EXPECT_EQ(0, matches_log_signal(_metadata, self->audit_fd, 487 + child_data.parent_pid, NULL)); 488 + 489 + /* Drains any remaining records (e.g. domain allocation). */ 490 + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); 491 + 492 + /* 493 + * Mutes subdomain logs and propagates to the sibling thread via TSYNC, 494 + * without creating a domain. 495 + */ 496 + ASSERT_EQ(0, landlock_restrict_self( 497 + -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF | 498 + LANDLOCK_RESTRICT_SELF_TSYNC)); 499 + 500 + /* 501 + * Phase 2: the sibling stacks another domain and triggers a denial. 502 + * Because log_subdomains_off was propagated via TSYNC, the new domain 503 + * has log_status=LANDLOCK_LOG_DISABLED. 504 + */ 505 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 506 + ASSERT_EQ(1, read(pipe_child[0], &buffer, 1)); 507 + 508 + /* No denial record should appear. */ 509 + EXPECT_EQ(-EAGAIN, matches_log_signal(_metadata, self->audit_fd, 510 + child_data.parent_pid, NULL)); 511 + 512 + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); 513 + EXPECT_EQ(0, records.access); 514 + 515 + EXPECT_EQ(0, close(pipe_child[0])); 516 + EXPECT_EQ(0, close(pipe_parent[1])); 517 + ASSERT_EQ(0, pthread_join(thread, &thread_ret)); 518 + EXPECT_EQ(NULL, thread_ret); 519 + } 520 + 521 + /* 522 + * Verifies that LANDLOCK_RESTRICT_SELF_TSYNC without 523 + * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF overrides a sibling thread's 524 + * log_subdomains_off, re-enabling audit logging on domains the sibling 525 + * subsequently creates. 526 + * 527 + * Phase 1: the sibling sets log_subdomains_off, creates a muted domain, and 528 + * triggers a denial that is NOT logged. 529 + * 530 + * Phase 2 (after TSYNC without LOG_SUBDOMAINS_OFF): the sibling stacks another 531 + * domain and triggers a denial that IS logged, proving the muting was 532 + * overridden. 533 + */ 534 + TEST_F(audit, tsync_override_log_subdomains_off) 535 + { 536 + const struct landlock_ruleset_attr ruleset_attr = { 537 + .scoped = LANDLOCK_SCOPE_SIGNAL, 538 + }; 539 + struct audit_records records; 540 + struct thread_data child_data = {}; 541 + int pipe_child[2], pipe_parent[2]; 542 + char buffer; 543 + pthread_t thread; 544 + void *thread_ret; 545 + 546 + child_data.parent_pid = getppid(); 547 + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); 548 + child_data.pipe_child = pipe_child[1]; 549 + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); 550 + child_data.pipe_parent = pipe_parent[0]; 551 + child_data.ruleset_fd = 552 + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); 553 + ASSERT_LE(0, child_data.ruleset_fd); 554 + 555 + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); 556 + 557 + child_data.mute_subdomains = true; 558 + 559 + /* Creates the sibling thread. */ 560 + ASSERT_EQ(0, pthread_create(&thread, NULL, thread_sandbox_deny_twice, 561 + &child_data)); 562 + 563 + /* 564 + * Phase 1: the sibling mutes subdomain logs, creates a domain, and 565 + * triggers a denial. The denial must not be logged. 566 + */ 567 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 568 + ASSERT_EQ(1, read(pipe_child[0], &buffer, 1)); 569 + 570 + EXPECT_EQ(-EAGAIN, matches_log_signal(_metadata, self->audit_fd, 571 + child_data.parent_pid, NULL)); 572 + 573 + /* Drains any remaining records. */ 574 + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); 575 + EXPECT_EQ(0, records.access); 576 + 577 + /* 578 + * Overrides the sibling's log_subdomains_off by calling TSYNC without 579 + * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF. 580 + */ 581 + ASSERT_EQ(0, landlock_restrict_self(child_data.ruleset_fd, 582 + LANDLOCK_RESTRICT_SELF_TSYNC)); 583 + 584 + /* 585 + * Phase 2: the sibling stacks another domain and triggers a denial. 586 + * Because TSYNC replaced its log_subdomains_off with 0, the new domain 587 + * has log_status=LANDLOCK_LOG_PENDING. 588 + */ 589 + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); 590 + ASSERT_EQ(1, read(pipe_child[0], &buffer, 1)); 591 + 592 + /* The denial must be logged. */ 593 + EXPECT_EQ(0, matches_log_signal(_metadata, self->audit_fd, 594 + child_data.parent_pid, NULL)); 595 + 596 + EXPECT_EQ(0, close(pipe_child[0])); 597 + EXPECT_EQ(0, close(pipe_parent[1])); 598 + ASSERT_EQ(0, pthread_join(thread, &thread_ret)); 599 + EXPECT_EQ(NULL, thread_ret); 369 600 } 370 601 371 602 FIXTURE(audit_flags)
+77
tools/testing/selftests/landlock/tsync_test.c
··· 247 247 EXPECT_EQ(0, close(ruleset_fd)); 248 248 } 249 249 250 + /* clang-format off */ 251 + FIXTURE(tsync_without_ruleset) {}; 252 + /* clang-format on */ 253 + 254 + FIXTURE_VARIANT(tsync_without_ruleset) 255 + { 256 + const __u32 flags; 257 + const int expected_errno; 258 + }; 259 + 260 + /* clang-format off */ 261 + FIXTURE_VARIANT_ADD(tsync_without_ruleset, tsync_only) { 262 + /* clang-format on */ 263 + .flags = LANDLOCK_RESTRICT_SELF_TSYNC, 264 + .expected_errno = EBADF, 265 + }; 266 + 267 + /* clang-format off */ 268 + FIXTURE_VARIANT_ADD(tsync_without_ruleset, subdomains_off_same_exec_off) { 269 + /* clang-format on */ 270 + .flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF | 271 + LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF | 272 + LANDLOCK_RESTRICT_SELF_TSYNC, 273 + .expected_errno = EBADF, 274 + }; 275 + 276 + /* clang-format off */ 277 + FIXTURE_VARIANT_ADD(tsync_without_ruleset, subdomains_off_new_exec_on) { 278 + /* clang-format on */ 279 + .flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF | 280 + LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON | 281 + LANDLOCK_RESTRICT_SELF_TSYNC, 282 + .expected_errno = EBADF, 283 + }; 284 + 285 + /* clang-format off */ 286 + FIXTURE_VARIANT_ADD(tsync_without_ruleset, all_flags) { 287 + /* clang-format on */ 288 + .flags = LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF | 289 + LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON | 290 + LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF | 291 + LANDLOCK_RESTRICT_SELF_TSYNC, 292 + .expected_errno = EBADF, 293 + }; 294 + 295 + /* clang-format off */ 296 + FIXTURE_VARIANT_ADD(tsync_without_ruleset, subdomains_off) { 297 + /* clang-format on */ 298 + .flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF | 299 + LANDLOCK_RESTRICT_SELF_TSYNC, 300 + .expected_errno = 0, 301 + }; 302 + 303 + FIXTURE_SETUP(tsync_without_ruleset) 304 + { 305 + disable_caps(_metadata); 306 + } 307 + 308 + FIXTURE_TEARDOWN(tsync_without_ruleset) 309 + { 310 + } 311 + 312 + TEST_F(tsync_without_ruleset, check) 313 + { 314 + int ret; 315 + 316 + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); 317 + 318 + ret = landlock_restrict_self(-1, variant->flags); 319 + if (variant->expected_errno) { 320 + EXPECT_EQ(-1, ret); 321 + EXPECT_EQ(variant->expected_errno, errno); 322 + } else { 323 + EXPECT_EQ(0, ret); 324 + } 325 + } 326 + 250 327 TEST_HARNESS_MAIN