From e083a68a9f45cd4ed51d8e288ce4c18ff578b50a Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Mon, 26 Jan 2026 19:13:39 +0100 Subject: [PATCH] libstore/linux: forbid xattrs syscalls xattrs are revealing some unfortunate brittleness in real world derivations that are getting -EINVAL errors while executing their test code. The reason for this is that Linux checks [1] UID delegations for xattrs writes. In the Lix sandbox, except if you enabled a uid-range feature, you have exactly 3 UIDs: root, nixbld and nobody. If your test code makes use of UIDs which have not been delegated, you will receive an EINVAL on your operation. Test code is not resilient with respect to the number of available UIDs in their namespace. To avoid further issues for end users who are running into spurious derivation build failures, we forbid xattrs again for now. For more information about the plans, please consult or chime in [2]. Fixes #1105. Reopens #838. Fixes #1103. [1]: https://elixir.bootlin.com/linux/v6.18.6/source/fs/posix_acl.c#L257 [2]: https://zulip.lix.systems/#narrow/channel/9-Store/topic/disablement.20of.20xattrs/with/5295 Change-Id: I864066b34cd8319d2271fac1b179cb4f950d836e Signed-off-by: Raito Bezarius --- lix/libstore/platform/linux.cc | 16 +++++++++++++--- tests/functional2/build/test_xattrs.py | 11 ++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lix/libstore/platform/linux.cc b/lix/libstore/platform/linux.cc index 3b11822fc..aeb1af127 100644 --- a/lix/libstore/platform/linux.cc +++ b/lix/libstore/platform/linux.cc @@ -231,6 +231,7 @@ static std::vector compileSyscallFilter() // Run check-syscalls to determine which new syscalls should be added. // New syscalls must be audited and handled in a way that blocks the following dangerous operations: // * Creation of non-empty setuid/setgid files + // * Creation of extended attributes (including ACLs) // // BEGIN extract-syscalls allowSyscall(ctx, SCMP_SYS(accept)); @@ -320,7 +321,7 @@ static std::vector compileSyscallFilter() allowSyscall(ctx, SCMP_SYS(fork)); allowSyscall(ctx, SCMP_SYS(fremovexattr)); allowSyscall(ctx, SCMP_SYS(fsconfig)); - allowSyscall(ctx, SCMP_SYS(fsetxattr)); + // skip fsetxattr (dangerous) allowSyscall(ctx, SCMP_SYS(fsmount)); allowSyscall(ctx, SCMP_SYS(fsopen)); allowSyscall(ctx, SCMP_SYS(fspick)); @@ -424,7 +425,7 @@ static std::vector compileSyscallFilter() allowSyscall(ctx, SCMP_SYS(lookup_dcookie)); allowSyscall(ctx, SCMP_SYS(lremovexattr)); allowSyscall(ctx, SCMP_SYS(lseek)); - allowSyscall(ctx, SCMP_SYS(lsetxattr)); + // skip lsetxattr (dangerous) allowSyscall(ctx, SCMP_SYS(lstat)); allowSyscall(ctx, SCMP_SYS(lstat64)); allowSyscall(ctx, SCMP_SYS(madvise)); @@ -622,7 +623,7 @@ static std::vector compileSyscallFilter() allowSyscall(ctx, SCMP_SYS(set_tls)); allowSyscall(ctx, SCMP_SYS(setuid)); allowSyscall(ctx, SCMP_SYS(setuid32)); - allowSyscall(ctx, SCMP_SYS(setxattr)); + // skip setxattr (dangerous) allowSyscall(ctx, SCMP_SYS(sgetmask)); allowSyscall(ctx, SCMP_SYS(shmat)); allowSyscall(ctx, SCMP_SYS(shmctl)); @@ -729,6 +730,15 @@ static std::vector compileSyscallFilter() ALLOW_CHMOD_IF_SAFE(ctx, SCMP_SYS(fchmodat), 2); ALLOW_CHMOD_IF_SAFE(ctx, SCMP_SYS(fchmodat2), 2); + // setxattr family: prevent creation of extended attributes or ACLs. + // Not all filesystems support them, and they're incompatible with the NAR format. + if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(setxattr), 0) != 0 + || seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(lsetxattr), 0) != 0 + || seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOTSUP), SCMP_SYS(fsetxattr), 0) != 0) + { + throw SysError("unable to add seccomp rule"); + } + Pipe filterPipe; filterPipe.create(); auto filterBytes_ = std::async([&]() { diff --git a/tests/functional2/build/test_xattrs.py b/tests/functional2/build/test_xattrs.py index 899c73c23..13b4f66e1 100644 --- a/tests/functional2/build/test_xattrs.py +++ b/tests/functional2/build/test_xattrs.py @@ -1,5 +1,3 @@ -import sys - import pytest from pathlib import Path from testlib.fixtures.file_helper import with_files @@ -7,10 +5,15 @@ from testlib.fixtures.nix import Nix from testlib.utils import get_global_asset, CopyFile from testlib.xattrs import verify_no_xattrs_in_tree, skip_if_xattrs_are_unsupported +# NOTE(Raito): xattrs are forbidden in builds for the time being. +# See: https://zulip.lix.systems/#narrow/channel/9-Store/topic/disablement.20of.20xattrs/with/5295 for the rationale. +# Once these hurddles are cleared, remove the skip markers. + @with_files( {"config.nix": get_global_asset("config.nix"), "xattrs.nix": CopyFile("assets/xattrs.nix")} ) +@pytest.mark.skip(reason="xattrs are forbidden in builds") def test_xattrs_during_build(nix: Nix): skip_if_xattrs_are_unsupported(nix.env) @@ -26,9 +29,7 @@ def build_and_get_store_path(nix: Nix, asset: str, attribute: str) -> Path: @with_files( {"config.nix": get_global_asset("config.nix"), "xattrs.nix": CopyFile("assets/xattrs.nix")} ) -@pytest.mark.skipif( - sys.platform != "linux", reason="xattrs scrubbing is not expected to function outside of Linux" -) +@pytest.mark.skip(reason="xattrs are forbidden in builds") def test_xattrs_in_output(nix: Nix): skip_if_xattrs_are_unsupported(nix.env)