#1462·brotli

[Bug] High: CLI -f follows symlinked output path and overwrites target file

Author: parasol-aserCreated Apr 19, 2026Updated Apr 19, 2026

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
  1. Observe the success output:
T-02 OK: 21-byte seed overwrote symlink target with crafted plaintext
  1. 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.