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:
- Use the current
master HEAD that was verified locally (ab685df) and build the CLI (build-asan/brotli was used for verification).
- Run the deterministic repro helper from the verification artifacts:
timeout 180s tests/regression/t02/repro_symlink_overwrite.sh build-asan/brotli
- Observe the success output:
T-02 OK: 21-byte seed overwrote symlink target with crafted plaintext
- 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.
Priority: High
Affected component:
brotli(1)CLI output file creation inc/tools/brotli.c(OpenOutputFile())Root cause:
OpenOutputFile()opens the output path withopen(output_path, O_CREAT | (force ? 0 : O_EXCL) | O_WRONLY | O_TRUNC, ...). When-f/--forceis used,O_EXCLis removed and the code still does not setO_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:
masterHEAD that was verified locally (ab685df) and build the CLI (build-asan/brotliwas used for verification).source.txt -> target.txt, then runsbrotli -d -f source.txt.br. After the run,target.txtcontainsATTACKER_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())c/tools/brotli.c:803-804(output opened withoutO_NOFOLLOW;-fremovesO_EXCL)tests/regression/t02/repro_symlink_overwrite.sh,tests/regression/t02/source.txt.br,tests/regression/t02/source.txt,tests/regression/t02/target.txtSuggested fix direction:
O_NOFOLLOWto the outputopen()flags unconditionally.-fscoped to overwriting an existing regular file; it should not implicitly allow following symlinks.open()fails withELOOP, emit a clear diagnostic so the refusal is obvious to users.O_NOFOLLOW, use a final-component symlink check before opening as defense in depth.