1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! The `bzip2` / `bunzip2` / `bzcat` family: stuffr behaving as bzip2 1.0.8.
//!
//! Ported from bzip2 1.0.8's `bzip2.c`, copyright (C) 1996-2019 Julian R
//! Seward, under the bzip2 licence reproduced in `LICENSE-bzip2`. Its flag
//! handling, checks, messages and exit codes are followed line by line; the
//! compression itself is stuffr's bzip2 codec, whose output is byte-identical
//! to bzip2's at every level.
//!
//! Known deviations from bzip2 1.0.8:
//!
//! - `--version`/`-V`/`-L`/`--license` name stuffr and its version on the
//! licence's first line (the rest is verbatim).
//! - Verbosity 2 and above (`-vv`) behaves as `-v`: libbzip2's internal
//! trace (block CRCs, sorting statistics) is not reachable through stuffr's
//! codec API, and bzip2.c's own extra blank lines at that level only frame
//! that trace, so they are left out with it.
//! - Output is written under a temporary name and renamed into place, so an
//! interrupted run leaves a `.stuffr-bzip2-*.tmp` file where bzip2's signal
//! handler would have deleted its partial output.
//! - When the parent ignores `SIGPIPE` (`SIG_IGN` inherited across exec),
//! bzip2 reports a Broken pipe I/O error and exits 1; stuffr dies of
//! `SIGPIPE`, because the Rust runtime sets `SIG_IGN` before `main` and the
//! inherited disposition cannot be read on stable Rust.
//! - The `perror` text after a cut-short stream reproduces the `errno` that
//! bzip2 happens to leave behind as measured on macOS; elsewhere it prints
//! `strerror(0)`.
use OsString;
use ;
use Path;
use ExitCode;
use AtomicU64;
use ;
use ;
/// Entry point for `bzip2`, `bunzip2` and `bzcat`; `name` is the canonical
/// invoked name and `args` the arguments after `argv[0]`.
/// bzip2.c's `main` after the program name: the flags, then the run.
/// The most a [`run_in`] run may write, to standard output and files
/// together. Past it a write fails with `ENOSPC`, which bzip2 reports as an
/// I/O error and exit 1, the code it gives a full disk. A bzip2 stream of a
/// few dozen bytes can expand to tens of MiB, so without a cap a fuzzer's
/// short input could fill memory or disk; 8 MiB is past any block's
/// ordinary output (a 900k block of text) and bounds what a bomb can cost.
/// `-t` writes nothing but decodes just as much (one 50-byte stream is 45 MB
/// of zeros), so its decoded bytes are charged too: every path a sandboxed
/// run takes is bounded by this one figure.
const SANDBOX_OUTPUT_CAP: u64 = 8 * 1024 * 1024;
/// [`run`] without the process: operands resolve against `dir`, standard
/// input is `stdin` (read as a pipe), standard output and error are
/// captured and returned with the exit code, neither is a terminal, `$BZIP2`
/// and `$BZIP` are unset, and `SIGPIPE` is left alone. Output is capped at
/// [`SANDBOX_OUTPUT_CAP`], and the bytes `-t` decodes (which go nowhere)
/// are charged to the same cap. For the fuzz target; not a stable interface.