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 <raito@lix.systems>
This commit is contained in:
Raito Bezarius
2026-01-26 18:54:51 +00:00
parent cfabc37828
commit e083a68a9f
2 changed files with 19 additions and 8 deletions
+13 -3
View File
@@ -231,6 +231,7 @@ static std::vector<struct sock_filter> 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<struct sock_filter> 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<struct sock_filter> 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<struct sock_filter> 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<struct sock_filter> 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([&]() {
+6 -5
View File
@@ -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)