Skip to content

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

@parasol-aser

Description

@parasol-aser

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:
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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions