Remote cache symlinks with relative targets can escape exec root on download
- Dominant language
- Java
- Stars
- 25.8k
- Forks
- 4.6k
- Avg merge
- 2d 20h
- Merged PRs (30d)
- 72
Description
## Summary
When materializing symlinks from a cached `ActionResult`, `createSymlinks()` in `RemoteExecutionService.java` validates that the symlink **path** stays inside `execRoot`, but performs no validation on relative symlink **targets**. A relative target like `../../../.ssh/authorized_keys` passes the path check but resolves outside the build output tree.
## Reproduction
1. Set up a shared remote cache (e.g., `--remote_cache=grpc://cache-server:8080`)
2. A malicious actor pushes an `ActionResult` containing a symlink entry:
- `path`: `bazel-out/k8-fastbuild/bin/output_link` (valid, inside execRoot)
- `target`: `../../../../.ssh/authorized_keys` (relative, escapes execRoot)
3. A developer or CI machine fetches this cached result
4. `createSymlinks()` materializes the symlink — the path check passes (path is inside execRoot), but the resolved target points to `~/.ssh/authorized_keys`
5. Subsequent build actions or tools that follow the symlink now read/write outside the sandbox
## Context
The **upload side** already validates symlink targets via `UploadManifest.checkAbsoluteSymlinkAllowed()` using `SymlinkAbsolutePathStrategy`. However, the download side has no equivalent check for relative targets that traverse upward out of the exec root.
This creates an asymmetry: a directly-built action would be caught on upload, but a poisoned cache entry (injected via direct API calls to the cache, bypassing Bazel's upload validation) would not be caught on download.
## Impact
Shared remote caches are common in production Bazel deployments (CI farms, developer machines). A compromised cache entry could:
- Create symlinks to sensitive files (SSH keys, credentials, source code outside the workspace)
- Enable data exfiltration if a subsequent action reads the symlink and uploads results
- Enable file overwrites if a subsequent action writes through the symlink
## Proposed Fix
Before calling `createSymbolicLink()`, resolve relative targets against the symlink's parent directory and verify the resolved path remains within `execRoot`. Absolute targets should follow the existing `SymlinkAbsolutePathStrategy` policy.
## Related PR
#30346
Contributor guide
Research direction
Start in RemoteExecutionService.java at createSymlinks(), then compare the download path with UploadManifest.checkAbsoluteSymlinkAllowed() and SymlinkAbsolutePathStrategy. Add regression coverage for relative targets that traverse outside execRoot, while preserving the existing policy for absolute targets; the related PR #30346 indicates work may already be underway.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- build-system, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100