/rust/registry/src/index.crates.io-6f17d22bba15001f/cap-primitives-3.4.4/src/fs/symlink.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! This defines `symlink`, the primary entrypoint to sandboxed symlink |
2 | | //! creation. |
3 | | |
4 | | use crate::fs::errors; |
5 | | #[cfg(all(racy_asserts, not(windows)))] |
6 | | use crate::fs::symlink_unchecked; |
7 | | #[cfg(racy_asserts)] |
8 | | use crate::fs::{canonicalize, manually, map_result, stat_unchecked, FollowSymlinks, Metadata}; |
9 | | #[cfg(all(racy_asserts, windows))] |
10 | | use crate::fs::{symlink_dir_unchecked, symlink_file_unchecked}; |
11 | | use std::path::Path; |
12 | | use std::{fs, io}; |
13 | | |
14 | | /// Perform a `symlinkat`-like operation, ensuring that the resolution of the |
15 | | /// path never escapes the directory tree rooted at `start`. An error is |
16 | | /// returned if the target path is absolute. |
17 | | #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] |
18 | | #[cfg(not(windows))] |
19 | | #[inline] |
20 | 0 | pub fn symlink(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { |
21 | 0 | // Don't allow creating symlinks to absolute paths. This isn't strictly |
22 | 0 | // necessary to preserve the sandbox, since `open` will refuse to follow |
23 | 0 | // absolute symlinks in any case. However, it is useful to enforce this |
24 | 0 | // restriction so that a WASI program can't trick some other non-WASI |
25 | 0 | // program into following an absolute path. |
26 | 0 | if old_path.has_root() { |
27 | 0 | return Err(errors::escape_attempt()); |
28 | 0 | } |
29 | 0 |
|
30 | 0 | write_symlink_impl(old_path, new_start, new_path) |
31 | 0 | } |
32 | | |
33 | | #[cfg(not(windows))] |
34 | 0 | fn write_symlink_impl(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { |
35 | | use crate::fs::symlink_impl; |
36 | | |
37 | | #[cfg(racy_asserts)] |
38 | | let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No); |
39 | | |
40 | | // Call the underlying implementation. |
41 | 0 | let result = symlink_impl(old_path, new_start, new_path); |
42 | 0 |
|
43 | 0 | #[cfg(racy_asserts)] |
44 | 0 | let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No); |
45 | 0 |
|
46 | 0 | #[cfg(racy_asserts)] |
47 | 0 | check_symlink( |
48 | 0 | old_path, |
49 | 0 | new_start, |
50 | 0 | new_path, |
51 | 0 | &stat_before, |
52 | 0 | &result, |
53 | 0 | &stat_after, |
54 | 0 | ); |
55 | 0 |
|
56 | 0 | result |
57 | 0 | } |
58 | | |
59 | | /// Perform a `symlinkat`-like operation, ensuring that the resolution of the |
60 | | /// link path never escapes the directory tree rooted at `start`. |
61 | | #[cfg(not(windows))] |
62 | 0 | pub fn symlink_contents<P: AsRef<Path>, Q: AsRef<Path>>( |
63 | 0 | old_path: P, |
64 | 0 | new_start: &fs::File, |
65 | 0 | new_path: Q, |
66 | 0 | ) -> io::Result<()> { |
67 | 0 | write_symlink_impl(old_path.as_ref(), new_start, new_path.as_ref()) |
68 | 0 | } |
69 | | |
70 | | /// Perform a `symlink_file`-like operation, ensuring that the resolution of |
71 | | /// the path never escapes the directory tree rooted at `start`. |
72 | | #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] |
73 | | #[cfg(windows)] |
74 | | #[inline] |
75 | | pub fn symlink_file(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { |
76 | | use crate::fs::symlink_file_impl; |
77 | | |
78 | | // As above, don't allow creating symlinks to absolute paths. |
79 | | if old_path.has_root() { |
80 | | return Err(errors::escape_attempt()); |
81 | | } |
82 | | |
83 | | #[cfg(racy_asserts)] |
84 | | let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No); |
85 | | |
86 | | // Call the underlying implementation. |
87 | | let result = symlink_file_impl(old_path, new_start, new_path); |
88 | | |
89 | | #[cfg(racy_asserts)] |
90 | | let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No); |
91 | | |
92 | | #[cfg(racy_asserts)] |
93 | | check_symlink_file( |
94 | | old_path, |
95 | | new_start, |
96 | | new_path, |
97 | | &stat_before, |
98 | | &result, |
99 | | &stat_after, |
100 | | ); |
101 | | |
102 | | result |
103 | | } |
104 | | |
105 | | /// Perform a `symlink_dir`-like operation, ensuring that the resolution of the |
106 | | /// path never escapes the directory tree rooted at `start`. |
107 | | #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] |
108 | | #[cfg(windows)] |
109 | | #[inline] |
110 | | pub fn symlink_dir(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { |
111 | | use crate::fs::symlink_dir_impl; |
112 | | |
113 | | // As above, don't allow creating symlinks to absolute paths. |
114 | | if old_path.has_root() { |
115 | | return Err(errors::escape_attempt()); |
116 | | } |
117 | | |
118 | | #[cfg(racy_asserts)] |
119 | | let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No); |
120 | | |
121 | | // Call the underlying implementation. |
122 | | let result = symlink_dir_impl(old_path, new_start, new_path); |
123 | | |
124 | | #[cfg(racy_asserts)] |
125 | | let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No); |
126 | | |
127 | | #[cfg(racy_asserts)] |
128 | | check_symlink_dir( |
129 | | old_path, |
130 | | new_start, |
131 | | new_path, |
132 | | &stat_before, |
133 | | &result, |
134 | | &stat_after, |
135 | | ); |
136 | | |
137 | | result |
138 | | } |
139 | | |
140 | | #[cfg(all(not(windows), racy_asserts))] |
141 | | #[allow(clippy::enum_glob_use)] |
142 | | fn check_symlink( |
143 | | old_path: &Path, |
144 | | new_start: &fs::File, |
145 | | new_path: &Path, |
146 | | stat_before: &io::Result<Metadata>, |
147 | | result: &io::Result<()>, |
148 | | stat_after: &io::Result<Metadata>, |
149 | | ) { |
150 | | use io::ErrorKind::*; |
151 | | |
152 | | match ( |
153 | | map_result(stat_before), |
154 | | map_result(result), |
155 | | map_result(stat_after), |
156 | | ) { |
157 | | (Err((NotFound, _)), Ok(()), Ok(metadata)) => { |
158 | | assert!(metadata.file_type().is_symlink()); |
159 | | let canon = |
160 | | manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap(); |
161 | | assert_same_file_metadata!( |
162 | | &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(), |
163 | | &metadata |
164 | | ); |
165 | | } |
166 | | |
167 | | (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { |
168 | | assert_same_file_metadata!(&metadata_before, &metadata_after); |
169 | | } |
170 | | |
171 | | (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) { |
172 | | Ok(canon) => match map_result(&symlink_unchecked(old_path, new_start, &canon)) { |
173 | | Err((_unchecked_kind, _unchecked_message)) => { |
174 | | /* TODO: Check error messages. |
175 | | assert_eq!( |
176 | | kind, |
177 | | unchecked_kind, |
178 | | "unexpected error kind from symlink new_start='{:?}', \ |
179 | | new_path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", |
180 | | new_start, |
181 | | new_path.display(), |
182 | | stat_before, |
183 | | result, |
184 | | stat_after |
185 | | ); |
186 | | assert_eq!(message, unchecked_message); |
187 | | */ |
188 | | } |
189 | | _ => panic!("unsandboxed symlink success"), |
190 | | }, |
191 | | Err((_canon_kind, _canon_message)) => { |
192 | | /* TODO: Check error messages. |
193 | | assert_eq!(kind, canon_kind); |
194 | | assert_eq!(message, canon_message); |
195 | | */ |
196 | | } |
197 | | }, |
198 | | |
199 | | _other => { |
200 | | /* TODO: Check error messages. |
201 | | panic!( |
202 | | "inconsistent symlink checks: new_start='{:?}' new_path='{}':\n{:#?}", |
203 | | new_start, |
204 | | new_path.display(), |
205 | | other, |
206 | | ) |
207 | | */ |
208 | | } |
209 | | } |
210 | | } |
211 | | |
212 | | #[cfg(all(windows, racy_asserts))] |
213 | | #[allow(clippy::enum_glob_use)] |
214 | | fn check_symlink_file( |
215 | | old_path: &Path, |
216 | | new_start: &fs::File, |
217 | | new_path: &Path, |
218 | | stat_before: &io::Result<Metadata>, |
219 | | result: &io::Result<()>, |
220 | | stat_after: &io::Result<Metadata>, |
221 | | ) { |
222 | | use io::ErrorKind::*; |
223 | | |
224 | | match ( |
225 | | map_result(stat_before), |
226 | | map_result(result), |
227 | | map_result(stat_after), |
228 | | ) { |
229 | | (Err((NotFound, _)), Ok(()), Ok(metadata)) => { |
230 | | assert!(metadata.file_type().is_symlink()); |
231 | | let canon = |
232 | | manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap(); |
233 | | assert_same_file_metadata!( |
234 | | &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(), |
235 | | &metadata |
236 | | ); |
237 | | } |
238 | | |
239 | | (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { |
240 | | assert_same_file_metadata!(&metadata_before, &metadata_after); |
241 | | } |
242 | | |
243 | | (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) { |
244 | | Ok(canon) => match map_result(&symlink_file_unchecked(old_path, new_start, &canon)) { |
245 | | Err((_unchecked_kind, _unchecked_message)) => { |
246 | | /* TODO: Check error messages. |
247 | | assert_eq!( |
248 | | kind, |
249 | | unchecked_kind, |
250 | | "unexpected error kind from symlink new_start='{:?}', \ |
251 | | new_path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", |
252 | | new_start, |
253 | | new_path.display(), |
254 | | stat_before, |
255 | | result, |
256 | | stat_after |
257 | | ); |
258 | | assert_eq!(message, unchecked_message); |
259 | | */ |
260 | | } |
261 | | _ => panic!("unsandboxed symlink success"), |
262 | | }, |
263 | | Err((_canon_kind, _canon_message)) => { |
264 | | /* TODO: Check error messages. |
265 | | assert_eq!(kind, canon_kind); |
266 | | assert_eq!(message, canon_message); |
267 | | */ |
268 | | } |
269 | | }, |
270 | | |
271 | | _other => { |
272 | | /* TODO: Check error messages. |
273 | | panic!( |
274 | | "inconsistent symlink checks: new_start='{:?}' new_path='{}':\n{:#?}", |
275 | | new_start, |
276 | | new_path.display(), |
277 | | other, |
278 | | ) |
279 | | */ |
280 | | } |
281 | | } |
282 | | } |
283 | | |
284 | | #[cfg(all(windows, racy_asserts))] |
285 | | #[allow(clippy::enum_glob_use)] |
286 | | fn check_symlink_dir( |
287 | | old_path: &Path, |
288 | | new_start: &fs::File, |
289 | | new_path: &Path, |
290 | | stat_before: &io::Result<Metadata>, |
291 | | result: &io::Result<()>, |
292 | | stat_after: &io::Result<Metadata>, |
293 | | ) { |
294 | | use io::ErrorKind::*; |
295 | | |
296 | | match ( |
297 | | map_result(stat_before), |
298 | | map_result(result), |
299 | | map_result(stat_after), |
300 | | ) { |
301 | | (Err((NotFound, _)), Ok(()), Ok(metadata)) => { |
302 | | assert!(metadata.file_type().is_symlink()); |
303 | | let canon = |
304 | | manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap(); |
305 | | assert_same_file_metadata!( |
306 | | &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(), |
307 | | &metadata |
308 | | ); |
309 | | } |
310 | | |
311 | | (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { |
312 | | assert_same_file_metadata!(&metadata_before, &metadata_after); |
313 | | } |
314 | | |
315 | | (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) { |
316 | | Ok(canon) => match map_result(&symlink_dir_unchecked(old_path, new_start, &canon)) { |
317 | | Err((_unchecked_kind, _unchecked_message)) => { |
318 | | /* TODO: Check error messages. |
319 | | assert_eq!( |
320 | | kind, |
321 | | unchecked_kind, |
322 | | "unexpected error kind from symlink new_start='{:?}', \ |
323 | | new_path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", |
324 | | new_start, |
325 | | new_path.display(), |
326 | | stat_before, |
327 | | result, |
328 | | stat_after |
329 | | ); |
330 | | assert_eq!(message, unchecked_message); |
331 | | */ |
332 | | } |
333 | | _ => panic!("unsandboxed symlink success"), |
334 | | }, |
335 | | Err((_canon_kind, _canon_message)) => { |
336 | | /* TODO: Check error messages. |
337 | | assert_eq!(kind, canon_kind); |
338 | | assert_eq!(message, canon_message); |
339 | | */ |
340 | | } |
341 | | }, |
342 | | |
343 | | _other => { |
344 | | /* TODO: Check error messages. |
345 | | panic!( |
346 | | "inconsistent symlink checks: new_start='{:?}' new_path='{}':\n{:#?}", |
347 | | new_start, |
348 | | new_path.display(), |
349 | | other, |
350 | | ) |
351 | | */ |
352 | | } |
353 | | } |
354 | | } |