Nix 1.11.10 blocks persistent access to build-user identities
Builders could leave setuid programs that recovered a nixbld identity during later builds. Nix 1.11.10 blocks that route and rejects filesystem metadata that its archive format cannot represent.
A temporary build identity is useful only if an earlier build cannot recover it later. Nix 1.11.10 fixes a flaw that allowed builders to create setuid executables owned by a nixbld user. An attacker could then use one of those executables to regain that identity and interfere with subsequent builds assigned the same UID. The release notes, dated June 12, 2017, credit Linus Heckemann with discovering and reporting the problem.
The issue concerns the lifetime of authority across builds. Running a builder under a separate account separates it from other users at that moment. Reusing the account later is safe only if the earlier builder cannot retain a way to act as that account. A setuid program can defeat that assumption even after the original build process has ended.
I am interested in Nix because it makes build inputs and desired configuration explicit. That model still needs the operating system to enforce the boundaries around a build. A precise dependency description cannot establish trustworthy results if another process can assume the builder's identity and interfere with its execution.
A build user needs a bounded lifetime of access
The implementation shipped in 1.11.10 acquires an account when build-users-group is nonempty. It asserts that the selected UID and GID are not zero. Before starting work, it kills other processes executing under that UID and prepares ownership of the temporary build directory.
Those steps address existing processes and ownership at the start of the build. The vulnerability describes a different persistence mechanism. A file created earlier can provide a new route back to the account after the cleanup has happened. The release therefore prevents creating the setuid and setgid programs that make that route possible.
The distinction explains why process cleanup alone is insufficient. Killing the old process ends its execution, but does not necessarily remove a reusable executable that can assume the same identity. A security boundary based on reusing accounts must account for durable capabilities left by earlier uses of the account.
The documented consequence is interference with subsequent builds under the same UID. It would be inaccurate to summarize the report simply as a demonstrated route to root. The unwanted authority described by the source is a nixbld identity, which is already enough to undermine the intended separation between builds.
Linux checks permission-changing system calls
On Linux, the fix uses a seccomp BPF filter. The implementation initializes an allow-by-default filter and adds specific denial rules. That is a targeted restriction within the builder's execution environment, not an allowlist claiming to enumerate every permissible system call.
For both the setuid and setgid bits, it checks the mode arguments to chmod, fchmod and fchmodat. Calls requesting the prohibited bit receive EPERM. The argument position differs for fchmodat, and the source spells that out in the filter rule rather than applying the same position to every call.
This detail makes the enforcement mechanism concrete. The builder does not merely receive instructions to avoid special permission bits. Its calls to the relevant operations encounter a kernel-enforced filter. The restriction applies while the build runs, so the problematic executable cannot be prepared through those permission changes and retained for later use.
The filter also adds the 32-bit x86 architecture when the configured system is x86_64-linux. That handles a second system-call ABI in the named configuration. Describing the protection only as a source-level check in a particular chmod library wrapper would miss why the implementation uses seccomp.
Filter setup participates in the security decision
The child-build setup calls setupSeccomp() before running the builder. Its exception handling makes filter failure fatal when a build user is enabled. This matters because the protection would be ineffective if an installation could silently retain the build-user mechanism after failing to install its required restriction.
The condition is visible in the source. An exception during setup is rethrown when buildUser.enabled() is true. The implementation therefore ties use of that identity mechanism to successful enforcement, rather than treating the new filter as optional best-effort hardening for the affected case.
The release notes report a performance cost, giving a roughly one-percent penalty when building GNU Hello as an example. That is a measurement for the named workload, not a universal estimate for all builds. The mechanism adds work to the execution path, but different build workloads need not spend the same fraction of time in the operations affected by it.
I consider that a well-defined tradeoff because the restriction protects the identity boundary on which later builds depend. The release supplies enough implementation detail to explain the cost and purpose. It does not require a general promise that every build will show an identical timing difference.
Extended attributes exceed the archive model
The seccomp filter also blocks setxattr, lsetxattr and fsetxattr, returning ENOTSUP. The release notes explain that extended attributes and POSIX ACLs cannot be represented in Nix's NAR archive format. ACLs can also bypass the ordinary Nix store permission model.
This is a related consistency problem between the filesystem and the representation of stored outputs. If a build can create metadata that the archive does not preserve, the installed object can have properties absent from its serialized form. Restricting that metadata keeps the allowed build result closer to what Nix can describe and transfer.
ACLs add an authority consequence to the representational mismatch. The release explicitly identifies them as a way to bypass regular store permissions. Blocking the setters therefore addresses more than incomplete copying of decorative metadata. It prevents a build from relying on permission information outside the intended store model.
The source comments also note that not every filesystem supports these attributes. The chosen error value communicates lack of support for the prohibited operation within the build environment. A program attempting to set an attribute sees a failure instead of creating state that the archive cannot retain.
macOS retains a minimal sandbox restriction
On macOS, Nix implements the restriction through the existing sandbox mechanism. The notes say a minimal profile allows everything except creation of setuid and setgid programs when ordinary sandboxing is disabled. Disabling the broader build sandbox therefore does not disable this particular security condition.
The implementation appends a denial for file-write-setugid. In the less restricted case, it first supplies an allow-default profile and then adds that denial. It launches the builder through sandbox-exec with the generated profile. This makes the minimal rule an actual execution policy rather than a post-build inspection of output files.
The difference between the platform implementations is important. Linux uses seccomp rules over named system calls and arguments. macOS uses its sandbox policy. Both address the documented persistence route, but they do not share an identical low-level mechanism or imply identical general sandbox behavior.
The minimal profile also makes the term sandbox more precise in this context. An administrator can disable the broad restriction on a builder's access while the special-permission prohibition remains. A configuration setting described as disabling sandboxing should therefore not be read as permission for every filesystem operation. The release documents a narrower mandatory policy for this security boundary.
The protection concerns actions during a build, including attempts to prepare a persistent executable. Inspecting only the final output would describe a different enforcement point. The shipped implementations restrict the operation as the builder requests it, so their role extends beyond deciding whether a completed result may enter the store.
Other platforms lose the build-user mechanism
For other platforms, the release disables build users. The implementation throws an error when a nonempty build-users group requests that mechanism outside Linux or macOS, with an explanation that build users are unsupported there for security reasons. It does not continue under the former assumption without a way to block the relevant executables.
That is a compatibility consequence of the fix. An installation depending on build-user isolation on another platform cannot assume that updating preserves the same configuration. The release chooses to reject an unsupported security arrangement instead of claiming that a separate UID alone provides sufficient isolation.
The result is a more specific contract for the supported mechanism. A build user is acquired for the build, existing processes under that identity are cleared, and the platform restriction prevents the documented durable route back to the account. Each part addresses a different stage in the identity's use, including the period after one build ends and before the next begins.