[Bug] High: CLI -f follows symlinked output path and overwrites target file
- Dominant language
- TypeScript
- Stars
- 14.9k
- Forks
- 1.4k
- Avg merge
- 4d 20h
- Merged PRs (30d)
- 9
Description
Priority: High
Affected component: `brotli(1)` CLI output file creation in `c/tools/brotli.c` (`OpenOutputFile()`)
Root cause:
`OpenOutputFile()` opens the output path with `open(output_path, O_CREAT | (force ? 0 : O_EXCL) | O_WRONLY | O_TRUNC, ...)`. When `-f` / `--force` is used, `O_EXCL` is removed and the code still does not set `O_NOFOLLOW`. A pre-existing symlink at the output path is therefore followed, and the symlink target is truncated and rewritten with attacker-controlled decompressed content.
Exact reproduction steps:
1. Use the current `master` HEAD that was verified locally (`ab685df`) and build the CLI (`build-asan/brotli` was used for verification).
2. Run the deterministic repro helper from the verification artifacts:
```bash
timeout 180s tests/regression/t02/repro_symlink_overwrite.sh build-asan/brotli
```
3. Observe the success output:
```text
T-02 OK: 21-byte seed overwrote symlink target with crafted plaintext
```
4. The helper creates `source.txt -> target.txt`, then runs `brotli -d -f source.txt.br`. After the run, `target.txt` contains `ATTACKER_CONTENT`, showing that the CLI followed the symlink and overwrote the target file instead of refusing the path.
Relevant file paths:
- `c/tools/brotli.c:795-816` (`OpenOutputFile()`)
- In particular `c/tools/brotli.c:803-804` (output opened without `O_NOFOLLOW`; `-f` removes `O_EXCL`)
- Verification repro assets: `tests/regression/t02/repro_symlink_overwrite.sh`, `tests/regression/t02/source.txt.br`, `tests/regression/t02/source.txt`, `tests/regression/t02/target.txt`
Suggested fix direction:
- Add `O_NOFOLLOW` to the output `open()` flags unconditionally.
- Keep `-f` scoped to overwriting an existing regular file; it should not implicitly allow following symlinks.
- If `open()` fails with `ELOOP`, emit a clear diagnostic so the refusal is obvious to users.
- On platforms without `O_NOFOLLOW`, use a final-component symlink check before opening as defense in depth.
Contributor guide
Research direction
Start with c/tools/brotli.c:795-816, especially OpenOutputFile() and the flags at lines 803-804. Run tests/regression/t02/repro_symlink_overwrite.sh with the built CLI to reproduce the overwrite, then verify that the symlink path is refused and target.txt is unchanged when -f is used.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash, c
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100