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
108
//! Keep the shell's own file descriptors out of the user's fd space.
//!
//! zsh routes every internal open through `movefd()` (Src/utils.c:1990), which
//! relocates the descriptor to >= 10 with `fcntl(fd, F_DUPFD, 10)` and records
//! it as `FDT_INTERNAL`. The point is that fds 0-9 belong to the SCRIPT: `exec
//! 3>out`, `read -u 4`, `print -u 3` and friends address them by number, and a
//! shell that parks its own state there is squatting on the user's namespace.
//!
//! zshrs was squatting. A `-c` run held:
//!
//! fd 3 -> ~/.zshrs/zshrs.log
//! fd 4 -> ~/.zshrs/zshrs_history.db (sqlite)
//! fd 5,6,7 -> ~/.zshrs/compsys.db + -wal + -shm
//!
//! so `print -u 3 -r -- x` appended `x` to the shell's own LOG and reported
//! success (zsh: `bad file number: 3`, status 1), and `exec 3>myfile` would
//! dup2 over the log handle — `exec 4>myfile` over the live sqlite handle.
//!
//! `movefd` only works for a descriptor we own and can close. sqlite caches the
//! fd number inside the connection, so it cannot be relocated after the fact.
//! The fix therefore has to make the open LAND high: hold every low fd for the
//! duration of the open, so the kernel's "lowest free descriptor" rule hands out
//! >= 10, then release. That covers lazily-opened descriptors (sqlite's WAL and
//! SHM appear on first use) as long as the open happens inside the guard.
use RawFd;
/// The first descriptor the shell may use for itself — everything below belongs
/// to the script. Matches C's `movefd` threshold (Src/utils.c:1994, `F_DUPFD, 10`).
const FIRST_INTERNAL_FD: RawFd = 10;
/// Occupies every currently-free descriptor below [`FIRST_INTERNAL_FD`] so that
/// opens performed while it is alive are pushed above the user's fd range.
/// Releases them on drop.
///
/// Descriptors that are ALREADY open are left alone — they may be the user's
/// (`zshrs 3>&1 -c '…'` is legal), and stealing them would be the very bug this
/// exists to prevent.
/// Run `f` with the low descriptors reserved, so anything it opens lands at or
/// above [`FIRST_INTERNAL_FD`].