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
//! IO security helpers for safe file opens.
//!
//! These functions guard against TOCTOU races: `open_readonly_nofollow` uses
//! `O_NOFOLLOW` (where available) to block symlink substitution on the final
//! path component, and `verify_fd_matches_stat` checks that the opened fd
//! refers to the same inode that was stat'd before the open, catching
//! directory-component swaps that `O_NOFOLLOW` cannot block.
use Path;
/// Opens a file for reading without following symlinks on the final path component.
///
/// On Unix systems, uses `O_NOFOLLOW` to block symlink substitution on the final
/// path component. However, `O_NOFOLLOW` cannot protect against directory-component
/// swaps that occur between the `stat()` call and the `open()` call.
/// Call `verify_fd_matches_stat` after opening to ensure the fd refers to the
/// same inode that was stat'd before the open.
///
/// # Security: why `libc::O_NOFOLLOW` and not a hardcoded constant
///
/// The numeric value of `O_NOFOLLOW` is NOT uniform across Linux architectures:
/// - x86-64 and AArch64 Linux: 0x20000 (0o400000)
/// - MIPS Linux: 0x400
/// - SPARC Linux: 0x20
/// - macOS / *BSD: 0x100
///
/// A hardcoded constant (e.g. `0o400000`) compiles silently with the wrong
/// value on MIPS cross-compile targets, disabling the symlink guard with no
/// warning or error. `libc::O_NOFOLLOW` is always the correct value for the
/// compilation target ABI.
// Non-Unix / WASM fallback: O_NOFOLLOW and inode verification are unavailable.
// The WASM build bypasses the filesystem entirely (callers provide content
// directly via WasmIndex::new), so TOCTOU mitigations are not applicable.
// Windows: FILE_FLAG_OPEN_REPARSE_POINT could block symlink traversal on the
// final component (analogous to O_NOFOLLOW), but requires CreateFileW via
// windows-sys. For now, fall back to plain File::open on non-Unix.
/// Verify that the fd we just opened refers to the same inode we stat'd
/// before the open. This catches directory-component symlink swaps that
/// happen in the window between canonicalize() and open(): O_NOFOLLOW only
/// blocks the final path component, not intermediate ones.
/// Windows stub for `verify_fd_matches_stat`.
///
/// The ideal implementation would compare volume serial number + file index
/// via `GetFileInformationByHandle`, but `MetadataExt::file_index()` and
/// `volume_serial_number()` are behind the unstable `windows_by_handle`
/// feature (rust-lang/rust#63010). Until that stabilizes, degrade to `true`.
///
/// Windows has mandatory file locking, so the TOCTOU risk is lower than on
/// Unix where advisory locks are the norm.
/// WASM stub: filesystem is not used (WasmIndex receives content directly),
/// so TOCTOU verification is not applicable.