CloseFiles() previously called fclose(context->fout) before invoking
CopyStat() on the output pathname. CopyStat() then used path-based
chmod() and chown(), which gave an attacker with write access to the
output directory a window to replace the path with a symlink between
the close and the metadata syscalls, redirecting the chmod/chown to an
arbitrary target.
Close the race by doing the metadata copy while the output file is
still open and by switching to fd-based syscalls on fileno(fout):
- CopyStat() now takes FILE* fout and calls fchmod()/fchown() on the
underlying fd.
- CopyTimeStat() uses futimens(fd, ...) in the HAVE_UTIMENSAT branch;
the legacy utime() fallback is preserved for platforms without it.
- CloseFiles() invokes CopyStat() before fclose() and skips it when
current_output_path is NULL (stdout), matching the issue's guidance.
- Windows no-op shims updated from chmod/chown to fchmod/fchown.
Adds deterministic regression coverage under tests/regression/t01/:
an LD_PRELOAD fclose() swap reproduces the pre-fix behavior, and a
negative-assertion wrapper confirms the attack no longer succeeds
after the patch. Additional scripts cover mode/timestamp propagation,
the -n (no-copy-stat) flag, stdin input, stdout output, mode mask, and
roundtrip correctness.
Refs google/brotli#1461
The final comment verification check in DecompressFile() detects when
comment_state is not COMMENT_OK but unconditionally returns BROTLI_TRUE.
This causes the -C (comment verification) flag to accept streams with
missing or mismatched comments, keeping the output file and exiting 0.
Add the missing return BROTLI_FALSE inside the mismatch branch so that
comment verification failures are properly propagated to the caller.
How to prove correctness:
1) when `position` is `0`, `max_length` is allowed to be `ringbuffer_size`
2) in other words: `position + max_length <= ringbuffer_size`
3) `ringbuffer_mask == ringbuffer_size - 1`
4) thus `position + max_length <= ringbuffer_size + 1`
PiperOrigin-RevId: 836145553
ISO C prohibits inline declarations of variables. Move the
declaration to the start of the block.
Co-authored-by: Eugene Kliuchnikov <eustas.ru@gmail.com>