Skip to content

Build1 publisher3 min readPublished

xz refuses to unpack a PHP extension when Docker's seccomp answers Landlock with EACCES

A dev.to write-up traces a build that fails only on enterprise-kernel servers to three correct components meeting badly. The seccomp profile that fixes it stays inert until the build runs on Docker's classic engine.

The Engineer · Build desk

Illustration accompanying xz refuses to unpack a PHP extension when Docker's seccomp answers Landlock with EACCES

What happened

  • A Docker build stops at RUN docker-php-ext-install with "xz: (stdin): Failed to enable the sandbox", followed by tar reporting a child status of 1 and an unrecoverable error.
  • The failure lands at the same step every time on the server, while the identical image builds without trouble on the author's laptop.
  • Recent xz sandboxes itself through Landlock before touching untrusted data, telling the kernel it no longer wants to open files or use the network.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • exposure The edit lands in daemon.json, so every container the host starts gets the Landlock calls turned into an errno. The blast radius of the fix is the whole daemon.
  • decision Pinning DOCKER_BUILDKIT=0 in the build user's shell profile buys reliability for this image by giving up the modern engine on everything else that user builds.
  • constraint Kernel ABI vintage is now part of the build contract: a Dockerfile validated on a laptop kernel is validated against that kernel, and whether it unpacks on a vendor-patched enterprise one is a separate question.
  • precedent The author ties the workaround to a kernel upgrade. If it is not written into the documentation, the override outlives the kernel that needed it.

Given ENOSYS, xz drops its sandbox and keeps decompressing, which is what it does on any kernel without Landlock [7]. Docker's seccomp filter does not answer that way: an unrecognised syscall gets a permission error [6]. So xz reads EACCES as the feature existing and being denied to it, will not touch untrusted input unprotected, and exits [7]. The dev.to author puts it plainly: the program is doing its job, and nobody is attacking it, because it is unpacking the source of a PHP extension [8].

Each of the three parts is defensible alone. Kernels from RHEL, CentOS Stream, Rocky and AlmaLinux carry old-looking version numbers over heavy vendor patching, and they expose the Landlock syscalls with an ABI older than recent xz expects [4]. The official PHP images are built on current Debian and ship an xz that cannot handle that ABI [5]. The seccomp default filters what a container may call [6]. According to the write-up, searching the error string turns up little because the two populations barely overlap: hosts with that kernel usually do not run that image [9].

The seccomp edit is the part everyone gets to. Copy your Docker version's default profile [10], add the block naming landlock_create_ruleset, landlock_add_rule and landlock_restrict_self with SCMP_ACT_ERRNO and errnoRet 38, point daemon.json at it, restart [11]. The fix rests on 38 being the errno xz reads as "no such syscall"; the post argues the ENOSYS path without naming the number [7]. Then the build fails again, because the modern builder does not run build steps under the daemon's profile and handles its own isolation [13]. Hence DOCKER_BUILDKIT=0, the step the author says almost everyone skips [12].

Consider where each of the two changes applies. The profile becomes the daemon's default, so it covers every container the host starts [11][13]. The post suggests making the variable permanent in the building user's shell profile [14]. That selects the classic engine for every image that user builds afterwards, well beyond the one that was failing [18].

The author is explicit about the trade. This turns off a hardening [15]. He argues the real risk is modest because Landlock on that kernel was not usable in the first place, so nothing that worked stops working, and he wants it written into the infrastructure documentation as a renunciation and not a fix [15]. He also gives it an expiry: when the server moves to a newer kernel, remove the profile and retry the build without it [16].

What would have to be true for this to be your bug: a vendor-patched enterprise kernel exposing an old Landlock ABI, an image carrying a modern xz, and a Docker seccomp default that does not know the three landlock names [4][5][6]. Miss one of the three and the same Dockerfile builds. The account is one engineer's three hours, and he reports emptying the build cache and checking disk space before he got to the errno [17].

What to watch

  • Whether Docker's shipped default seccomp profile starts naming the three landlock syscalls. That would remove the need for a hand-edited profile.
  • Whether the RHEL-family vendor kernels update their Landlock ABI, the condition the author sets for deleting the profile and retrying the build.
  • Whether a way to hand the modern builder the same profile gets documented, since the current remedy is to route around it.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories