---
title: "CVE-2025-32463: How a User-Controlled Chroot Loaded Code as Root"
description: "A reader-first root-cause analysis of CVE-2025-32463: how sudo entered an attacker-controlled filesystem before authorization completed, why NSS could then load a library as root, and what 1.9.17p1 changed."
date: 2026-08-23
analysisType: "Root Cause Analysis"
cves: ["CVE-2025-32463"]
author: Sevban Dönmez (@jankesec)
canonical: https://jankesec.com/research/sudo-chroot-nss-cve-2025-32463/
---

## CVE-2025-32463 in 60 seconds

`sudo` normally checks policy before it runs a command as root. CVE-2025-32463 let a local user make
`sudo` enter a user-selected root directory while privileged path, identity, and policy work was
still happening. On systems using `/etc/nsswitch.conf`, the new root could contain attacker-controlled
name-service configuration that caused `sudo` to load an attacker-controlled shared library as root.

The attack path was:

```text
local user with access to sudo
  -> select an attacker-controlled directory with sudo -R / --chroot
  -> sudo enters that filesystem before sudoers evaluation finishes
  -> privileged NSS lookup reads the attacker's /etc/nsswitch.conf
  -> libc loads a selected name-service module
  -> attacker-controlled code runs as root before command authorization can protect the boundary
```

This explains the surprising impact: the requested command did not need to be permitted in
`sudoers`. The dangerous code-loading step happened while `sudo` was still deciding whether the
request should be authorized.

## What are chroot, NSS, and `sudo -R`?

`chroot()` changes the filesystem root a process sees. It is useful for selecting an execution
environment, but it is not a security boundary when an untrusted user controls the directory and a
privileged process continues interpreting files inside it.

NSS, the Name Service Switch, tells libc where to resolve users, groups, hosts, and other names.
`/etc/nsswitch.conf` selects providers, and some providers are implemented as shared libraries.
`sudo -R` exposed a user-selected root-directory feature when policy allowed it. The vulnerability
was that the selected root became active early enough to influence privileged NSS behavior before
that policy decision was safely complete.

## What happened?

Sudo 1.9.14 changed path handling so the user-specified root could be entered with `chroot()` while
the `sudoers` file was still being evaluated. A local user could prepare an `/etc/nsswitch.conf`
below that root and steer a later name-service lookup toward an arbitrary shared library. Because
the lookup occurred in the privileged `sudo` process, the library loaded with root privileges.

Sudo 1.9.17p1 reverted the early-root design, returned to prefixing the selected root during path
resolution, and deprecated the chroot feature. The fix restores the order of operations: an
untrusted filesystem must not become part of privileged interpretation before authorization is
complete.

## Who was actually affected?

The upstream disclosure identifies sudo 1.9.14 through 1.9.17 inclusive. Exploitation required a
local user able to invoke the affected `sudo` path, a system supporting `/etc/nsswitch.conf`, and a
vulnerable build. It did not require the eventual command to appear in `sudoers`, because the library
load could occur during evaluation.

Sudo 1.9.17p1 fixes the bug. Distribution package versions may include backports, so defenders should
verify the vendor advisory and changelog for the installed package rather than relying only on the
upstream version string.

## What I verified

- The fix reverts the early `pivot_root` design and returns to prefixing the selected root while
  resolving command paths.
- Before that revert, `runchroot` flowed through checks such as `sudo_goodpath()` and `find_path()`
  while privileged identity and policy work was still in progress.
- The upstream patch states that passwd and group lookups cannot safely happen after changing root
  and names crafted `nsswitch.conf` library loading as CVE-2025-32463's consequence.
- I compared those code changes with the project advisory and public disclosure timeline.
- This is a static reproduction of the dangerous ordering and its fix. I did not load a custom
  shared object or claim a live privilege-escalation reproduction.

## Deep dive: the bug is a sequence, not a dangerous file

It is tempting to describe the vulnerability as “a malicious `nsswitch.conf`.” That is only the
last visible input. A normal program reading configuration from its own root filesystem is not
automatically vulnerable. The dangerous sequence is:

1. a privileged program accepts a root directory chosen by an unprivileged caller;
2. that directory becomes active while policy and identity work is still happening;
3. libc name-service resolution consults configuration below the new root;
4. the configuration influences module loading inside the privileged process;
5. attacker-controlled code executes before authorization provides a meaningful boundary.

<figure class="diagram">
<svg viewBox="0 0 760 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="An unprivileged caller selects a root directory. The privileged sudo process enters it before policy evaluation is complete, NSS reads attacker-controlled configuration, and a library load reaches root execution.">
<defs><marker id="sudo-a" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L8,4 L0,8 z" class="dg-arrow" /></marker></defs>
<text x="0" y="24" class="dg-crit">VULNERABLE DECISION ORDER</text>
<rect x="0" y="52" width="142" height="78" rx="8" class="dg-box" />
<text x="16" y="78" class="dg-label">Local user</text><text x="16" y="100" class="dg-muted">selects new root</text>
<line x1="144" y1="91" x2="190" y2="91" class="dg-line dg-crit-line" marker-end="url(#sudo-a)" />
<rect x="194" y="52" width="166" height="78" rx="8" class="dg-box-crit" />
<text x="210" y="78" class="dg-label">Privileged sudo</text><text x="210" y="100" class="dg-crit">chroot happens early</text>
<line x1="362" y1="91" x2="408" y2="91" class="dg-line dg-crit-line" marker-end="url(#sudo-a)" />
<rect x="412" y="52" width="158" height="78" rx="8" class="dg-box-crit" />
<text x="428" y="78" class="dg-label">NSS lookup</text><text x="428" y="100" class="dg-crit">reads new /etc</text>
<line x1="572" y1="91" x2="618" y2="91" class="dg-line dg-crit-line" marker-end="url(#sudo-a)" />
<rect x="622" y="52" width="138" height="78" rx="8" class="dg-box-crit" />
<text x="638" y="78" class="dg-label">Root process</text><text x="638" y="100" class="dg-muted">loads a module</text>
<line x1="277" y1="132" x2="277" y2="212" class="dg-line dg-dash" />
<rect x="194" y="216" width="166" height="60" rx="8" class="dg-box-accent" />
<text x="210" y="242" class="dg-label">sudoers decision</text><text x="210" y="262" class="dg-accent">arrives too late</text>
<text x="412" y="228" class="dg-muted">The trust decision is already influenced</text>
<text x="412" y="250" class="dg-muted">by files owned by the requester.</text>
</svg>
<figcaption>The security failure is temporal: the process enters an untrusted filesystem context before privileged interpretation is finished.</figcaption>
</figure>

The root cause is therefore not “NSS is unsafe.” NSS is doing what it was designed to do:
read the configuration associated with the current root and load the selected service modules.
The mistake is giving an untrusted caller control of that root while a privileged process can
still trigger those behaviors.

## The 1.9.14 regression changed when paths were resolved

The coordinated oss-security disclosure says the affected range begins with sudo 1.9.14.
That release changed path handling so paths could be resolved via `chroot()` using the
user-specified root directory while the `sudoers` file was still being evaluated.

That ordering is the crucial regression. A user-selected chroot may look like an execution
property: “run the eventual command inside this filesystem.” In the vulnerable implementation,
it also became an interpretation property: “use this filesystem while deciding identities,
paths, and policy.” Those are not equivalent capabilities.

<figure class="diagram">
<svg viewBox="0 0 760 312" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison of a safe decision order and the vulnerable order. The safe path evaluates authorization before entering the selected root; the vulnerable path enters the root before policy and name-service resolution complete.">
<text x="0" y="24" class="dg-accent">INTENDED ORDER</text>
<rect x="0" y="44" width="174" height="58" rx="8" class="dg-box" /><text x="16" y="78" class="dg-label">Parse request</text>
<rect x="196" y="44" width="174" height="58" rx="8" class="dg-box-accent" /><text x="212" y="78" class="dg-label">Authorize</text>
<rect x="392" y="44" width="174" height="58" rx="8" class="dg-box" /><text x="408" y="78" class="dg-label">Enter root</text>
<rect x="588" y="44" width="172" height="58" rx="8" class="dg-box" /><text x="604" y="78" class="dg-label">Execute</text>
<line x1="174" y1="73" x2="196" y2="73" class="dg-line" /><line x1="370" y1="73" x2="392" y2="73" class="dg-line" /><line x1="566" y1="73" x2="588" y2="73" class="dg-line" />
<line x1="0" y1="142" x2="760" y2="142" class="dg-line dg-dash" />
<text x="0" y="178" class="dg-crit">VULNERABLE ORDER</text>
<rect x="0" y="198" width="174" height="58" rx="8" class="dg-box" /><text x="16" y="232" class="dg-label">Parse request</text>
<rect x="196" y="198" width="174" height="58" rx="8" class="dg-box-crit" /><text x="212" y="222" class="dg-label">Enter root</text><text x="212" y="242" class="dg-crit">attacker context</text>
<rect x="392" y="198" width="174" height="58" rx="8" class="dg-box-crit" /><text x="408" y="222" class="dg-label">Resolve + authorize</text><text x="408" y="242" class="dg-crit">trusted process</text>
<rect x="588" y="198" width="172" height="58" rx="8" class="dg-box" /><text x="604" y="232" class="dg-label">Execute</text>
<line x1="174" y1="227" x2="196" y2="227" class="dg-line dg-crit-line" /><line x1="370" y1="227" x2="392" y2="227" class="dg-line dg-crit-line" /><line x1="566" y1="227" x2="588" y2="227" class="dg-line" />
<text x="196" y="292" class="dg-muted">Filesystem control leaks backward into the authorization phase.</text>
</svg>
<figcaption>A chroot intended for command execution became active during privileged decision-making. The dangerous capability is the reordered boundary.</figcaption>
</figure>

This explains why a rule review alone cannot disprove exposure. The public advisory states that
arbitrary commands could run even when they were not listed in `sudoers`. The bypass is not a
clever match against an allowed command. It operates beneath the abstraction administrators
normally audit.

## NSS is the bridge from configuration to code

Name Service Switch allows libc to resolve users, groups, hosts, and other records through
configured sources. `/etc/nsswitch.conf` selects those sources. Some selections correspond to
shared objects loaded by the process.

The vulnerable sudo process did not merely read attacker-controlled text. It performed a
privileged lookup whose implementation could be selected by that text. This is a useful general
research pattern:

> When privileged code enters an attacker-controlled namespace, inventory every implicit
> interpreter, loader, resolver, and configuration search—not only the file named in the patch.

The same reasoning applies to locale catalogs, dynamic loader configuration, PAM stacks,
plugin discovery, certificate stores, and language runtimes. Chroot changes path visibility;
it does not automatically make the visible files trustworthy.

## What the fix changes

The public disclosure says sudo 1.9.17p1 reverts the 1.9.14 change and marks the chroot feature
as deprecated. This is stronger than filtering one suspicious NSS token. It removes the
precondition that allowed user-controlled filesystem state to participate in policy evaluation.

<figure class="diagram">
<svg viewBox="0 0 760 276" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The fixed build keeps policy and identity resolution in the trusted host root, then applies the selected root only after authorization. NSS configuration below the selected root cannot influence the privileged decision.">
<text x="0" y="24" class="dg-accent">1.9.17p1 SECURITY PROPERTY</text>
<rect x="0" y="48" width="208" height="78" rx="8" class="dg-box-accent" />
<text x="16" y="76" class="dg-label">Trusted host root</text><text x="16" y="99" class="dg-accent">policy + identity resolution</text>
<line x1="210" y1="87" x2="274" y2="87" class="dg-line" />
<rect x="278" y="48" width="204" height="78" rx="8" class="dg-box-accent" />
<text x="294" y="76" class="dg-label">Authorization result</text><text x="294" y="99" class="dg-muted">allow or deny is settled</text>
<line x1="484" y1="87" x2="548" y2="87" class="dg-line" />
<rect x="552" y="48" width="208" height="78" rx="8" class="dg-box" />
<text x="568" y="76" class="dg-label">Execution context</text><text x="568" y="99" class="dg-muted">selected root, if allowed</text>
<rect x="0" y="178" width="208" height="72" rx="8" class="dg-box-crit" />
<text x="16" y="206" class="dg-label">Untrusted /etc</text><text x="16" y="228" class="dg-crit">cannot flow backward</text>
<line x1="210" y1="214" x2="548" y2="214" class="dg-line dg-dash" />
<line x1="548" y1="214" x2="548" y2="128" class="dg-line dg-dash" />
<text x="278" y="202" class="dg-muted">No influence on the completed decision</text>
<text x="278" y="224" class="dg-muted">from the user-selected filesystem.</text>
</svg>
<figcaption>The durable fix restores the direction of trust: authorization controls entry into the filesystem context, not the reverse.</figcaption>
</figure>

The deprecation matters. The maintainers explicitly describe user-selected chroot support as
error-prone and not widely used. That is an architectural conclusion: if a feature repeatedly
forces privileged interpretation into an untrusted namespace, restricting or removing the
feature can be safer than accumulating local checks.

## Evidence matrix

| Question | What I checked | Confidence | What remains unknown |
|---|---|---:|---|
| Which versions are affected? | I matched the disclosure's 1.9.14–1.9.17 range to the 1.9.17p1 tag diff. | High | Distribution backports require vendor-specific package checks. |
| What attacker position is required? | I confirmed the affected route is local and begins with a user-selected chroot path. | High | Exact prerequisites vary with build options, policy, and platform NSS behavior. |
| Why does authorization fail? | I traced the selected root into path checks performed before privileged resolution had finished. | High | Private discovery traces and original exploit-development steps are not public here. |
| Does the fix block one filename? | I verified that the fix reverts the broader path-resolution design rather than filtering one name. | High | Future feature removal timing is a release-management decision. |
| Was it exploited? | I checked the public catalog status; I did not infer actors or victims from it. | High | Public records do not identify every victim, actor, or exploitation method. |
| Is the base score settled? | NVD shows 7.8 High while the CNA contribution is 9.3 Critical. | High | The disagreement depends on privilege and scope assumptions. |

The score disagreement is worth preserving instead of averaging away. NVD models a local user
with low privileges and unchanged scope. The CNA vector models no privileges and changed scope.
Both agree on low attack complexity, no user interaction, and complete confidentiality,
integrity, and availability impact. For remediation, the affected version and reachable local
attack surface matter more than choosing the more dramatic label.

## Safe validation without building an exploit

An exposure check should answer three separate questions.

First, record the installed package version and the distribution's security status. A package
version string older than `1.9.17p1` is not conclusive when vendors backport fixes; the vendor
advisory and package changelog are the correct comparison points.

Second, inspect whether the installed sudo build exposes the chroot option and whether local
policy permits relevant invocation paths. This establishes reachability without attempting
library loading.

Third, compare vulnerable and fixed builds under observation. A safe regression harness can use
an inert directory tree and trace filesystem access. The assertion is structural: the fixed
build must not consult name-service configuration below the user-selected root while making the
privileged policy decision. No custom shared object or root shell is needed to verify that the
dangerous lookup disappeared.

Useful negative controls include:

- the same request against 1.9.17p1;
- the same inert tree with no `etc/nsswitch.conf`;
- a request without a user-selected root;
- a distribution package documented as backported even if its upstream version looks older.

These controls prevent a common reporting mistake: treating the presence of a command-line
option or an old-looking version as proof of exploitation.

## My conclusion

Upgrade to sudo 1.9.17p1 or the distribution package that explicitly backports the fix. Until
the update is complete, reduce local account exposure and remove unnecessary policy paths that
permit user-selected root directories. Detection should focus on unexpected use of sudo's
chroot option, unusual filesystem trees prepared by local users, and privileged processes
loading name-service modules from non-system locations.

My conclusion is that the crafted file was only the last step; the real defect was entering a
user-controlled filesystem view before privileged resolution had finished. Once that ordering was
reversed, NSS no longer received attacker-selected configuration during the authorization path.