fs_mistrust/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3// TODO: Stuff to add before this crate is ready....
4// - Test the absolute heck out of it.
5
6// POSSIBLY TODO:
7// - Cache information across runs.
8
9// @@ begin lint list maintained by maint/add_warning @@
10#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
11#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
12#![warn(missing_docs)]
13#![warn(noop_method_call)]
14#![warn(unreachable_pub)]
15#![warn(clippy::all)]
16#![deny(clippy::await_holding_lock)]
17#![deny(clippy::cargo_common_metadata)]
18#![deny(clippy::cast_lossless)]
19#![deny(clippy::checked_conversions)]
20#![warn(clippy::cognitive_complexity)]
21#![deny(clippy::debug_assert_with_mut_call)]
22#![deny(clippy::exhaustive_enums)]
23#![deny(clippy::exhaustive_structs)]
24#![deny(clippy::expl_impl_clone_on_copy)]
25#![deny(clippy::fallible_impl_from)]
26#![deny(clippy::implicit_clone)]
27#![deny(clippy::large_stack_arrays)]
28#![warn(clippy::manual_ok_or)]
29#![deny(clippy::missing_docs_in_private_items)]
30#![warn(clippy::needless_borrow)]
31#![warn(clippy::needless_pass_by_value)]
32#![warn(clippy::option_option)]
33#![deny(clippy::print_stderr)]
34#![deny(clippy::print_stdout)]
35#![warn(clippy::rc_buffer)]
36#![deny(clippy::ref_option_ref)]
37#![warn(clippy::semicolon_if_nothing_returned)]
38#![warn(clippy::trait_duplication_in_bounds)]
39#![deny(clippy::unchecked_time_subtraction)]
40#![deny(clippy::unnecessary_wraps)]
41#![warn(clippy::unseparated_literal_suffix)]
42#![deny(clippy::unwrap_used)]
43#![deny(clippy::mod_module_files)]
44#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
45#![allow(clippy::uninlined_format_args)]
46#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
47#![allow(clippy::result_large_err)] // temporary workaround for arti#587
48#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
49#![allow(clippy::needless_lifetimes)] // See arti#1765
50#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
51#![allow(clippy::collapsible_if)] // See arti#2342
52#![deny(clippy::unused_async)]
53//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
54
55// This crate used to have unsafe code to interact with various libc functions.
56// Nowadays we use pwd_grp, which is tested with miri.
57// This #[forbid] assures us that we have removed all direct unsafe libc access.
58//
59// If this crate grows some other reason to want some unsafe, it is OK to remove this,
60// subject to all the usual considerations when writing unsafe.
61#![forbid(unsafe_code)]
62
63mod dir;
64mod disable;
65mod err;
66mod file_access;
67mod imp;
68#[cfg(all(
69 target_family = "unix",
70 not(target_os = "ios"),
71 not(target_os = "android"),
72 not(target_os = "tvos")
73))]
74mod user;
75
76#[cfg(feature = "anon_home")]
77pub mod anon_home;
78#[cfg(test)]
79pub(crate) mod testing;
80pub mod walk;
81
82#[cfg(feature = "serde")]
83use serde::{Deserialize, Serialize};
84use std::{
85 fs::DirBuilder,
86 path::{Path, PathBuf},
87 sync::Arc,
88};
89
90pub use dir::CheckedDir;
91pub use disable::GLOBAL_DISABLE_VAR;
92pub use err::{Error, format_access_bits};
93pub use file_access::FileAccess;
94
95/// A result type as returned by this crate
96pub type Result<T> = std::result::Result<T, Error>;
97
98#[cfg(all(
99 target_family = "unix",
100 not(target_os = "ios"),
101 not(target_os = "android"),
102 not(target_os = "tvos")
103))]
104pub use user::{TrustedGroup, TrustedUser};
105
106/// Configuration for verifying that a file or directory is really "private".
107///
108/// By default, we mistrust everything that we can: we assume that every
109/// directory on the filesystem is potentially misconfigured. This object can
110/// be used to change that.
111///
112/// Once you have a working [`Mistrust`], you can call its "`check_*`" methods
113/// directly, or use [`verifier()`](Mistrust::verifier) to configure a more
114/// complicated check.
115///
116/// See the [crate documentation](crate) for more information.
117///
118/// # Environment variables
119///
120/// The [`Mistrust`] can be configured to consider an environment variable.
121/// See [`MistrustBuilder::controlled_by_default_env_var`] and similar methods.
122///
123/// Names that seem to say "don't disable" are treated as "false". Any
124/// other value is treated as "true". (That is, we err on the side of
125/// assuming that if you set a disable variable, you meant to disable.)
126///
127/// If the `Mistrust` is configured to use an environment variable,
128/// this environment variable typically becomes part of the application's public interface,
129/// so this library commits to a stable behaviour for parsing these variables.
130/// Specifically the following case-insensitive strings are considered "false":
131/// "false", "no", "never", "n", "0", "".
132///
133/// Examples using the default environment variable:
134///
135/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS="false"` — checks enabled
136/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS=" false "` — checks enabled
137/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS="NO"` — checks enabled
138/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS=0` — checks enabled
139/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS=` — checks enabled
140/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS=" "` — checks enabled
141/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS="true"` — checks disabled
142/// - `FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS="asdf"` — checks disabled
143///
144/// # TODO
145///
146/// * support more kinds of trust configuration, including more trusted users,
147/// trusted groups, multiple trusted directories, etc?
148#[derive(Debug, Clone, derive_builder::Builder, Eq, PartialEq)]
149#[cfg_attr(feature = "serde", builder(derive(Debug, Serialize, Deserialize)))]
150#[cfg_attr(not(feature = "serde"), builder(derive(Debug)))]
151#[builder(build_fn(error = "Error"))]
152#[cfg_attr(feature = "serde", builder_struct_attr(serde(default)))]
153pub struct Mistrust {
154 /// If the user called [`MistrustBuilder::ignore_prefix`], what did they give us?
155 ///
156 /// (This is stored in canonical form.)
157 #[builder(
158 setter(into, strip_option),
159 field(build = "canonicalize_opt_prefix(&self.ignore_prefix)?")
160 )]
161 ignore_prefix: Option<PathBuf>,
162
163 /// Are we configured to disable all permission and ownership tests?
164 ///
165 /// (This field is present in the builder only.)
166 #[builder(setter(custom), field(type = "Option<bool>", build = "()"))]
167 dangerously_trust_everyone: (),
168
169 /// Should we check the environment to decide whether to disable permission
170 /// and ownership tests?
171 ///
172 /// (This field is present in the builder only.)
173 #[builder(setter(custom), field(type = "Option<disable::Disable>", build = "()"))]
174 #[cfg_attr(feature = "serde", builder_field_attr(serde(skip)))]
175 disable_by_environment: (),
176
177 /// Internal value combining `dangerously_trust_everyone` and
178 /// `disable_by_environment` to decide whether we're doing permissions
179 /// checks or not.
180 #[builder(setter(custom), field(build = "self.should_be_enabled()"))]
181 #[cfg_attr(feature = "serde", builder_field_attr(serde(skip)))]
182 status: disable::Status,
183
184 /// What user ID do we trust by default (if any?)
185 #[cfg(all(
186 target_family = "unix",
187 not(target_os = "ios"),
188 not(target_os = "android"),
189 not(target_os = "tvos")
190 ))]
191 #[builder(
192 setter(into),
193 field(type = "TrustedUser", build = "self.trust_user.get_uid()?")
194 )]
195 trust_user: Option<u32>,
196
197 /// What group ID do we trust by default (if any?)
198 #[cfg(all(
199 target_family = "unix",
200 not(target_os = "ios"),
201 not(target_os = "android"),
202 not(target_os = "tvos")
203 ))]
204 #[builder(
205 setter(into),
206 field(type = "TrustedGroup", build = "self.trust_group.get_gid()?")
207 )]
208 trust_group: Option<u32>,
209}
210
211/// Compute the canonical prefix for a given path prefix.
212///
213/// The funny types here are used to please derive_builder.
214#[allow(clippy::option_option)]
215fn canonicalize_opt_prefix(prefix: &Option<Option<PathBuf>>) -> Result<Option<PathBuf>> {
216 match prefix {
217 Some(Some(path)) if path.as_os_str().is_empty() => Ok(None),
218 Some(Some(path)) => Ok(Some(
219 path.canonicalize()
220 .map_err(|e| Error::inspecting(e, path))?,
221 )),
222 _ => Ok(None),
223 }
224 // TODO: Permit "not found?" .
225}
226
227impl MistrustBuilder {
228 /// Configure this `Mistrust` to trust only the admin (root) user.
229 ///
230 /// By default, both the currently running user and the root user will be
231 /// trusted.
232 ///
233 /// This option disables the default group-trust behavior as well.
234 #[cfg(all(
235 target_family = "unix",
236 not(target_os = "ios"),
237 not(target_os = "android"),
238 not(target_os = "tvos"),
239 ))]
240 pub fn trust_admin_only(&mut self) -> &mut Self {
241 self.trust_user = TrustedUser::None;
242 self.trust_group = TrustedGroup::None;
243 self
244 }
245
246 /// Configure this `Mistrust` to trust no groups at all.
247 ///
248 /// By default, we trust the group (if any) with the same name as the
249 /// current user if we are currently running as a member of that group.
250 ///
251 /// With this option set, no group is trusted, and any group-readable or
252 /// group-writable objects are treated the same as world-readable and
253 /// world-writable objects respectively.
254 #[cfg(all(
255 target_family = "unix",
256 not(target_os = "ios"),
257 not(target_os = "android"),
258 not(target_os = "tvos"),
259 ))]
260 pub fn trust_no_group_id(&mut self) -> &mut Self {
261 self.trust_group = TrustedGroup::None;
262 self
263 }
264
265 /// Configure this `Mistrust` to trust every user and every group.
266 ///
267 /// With this option set, every file and directory is treated as having
268 /// valid permissions: even world-writeable files are allowed. File-type
269 /// checks are still performed.
270 ///
271 /// This option is mainly useful to handle cases where you want to make
272 /// these checks optional, and still use [`CheckedDir`] without having to
273 /// implement separate code paths for the "checking on" and "checking off"
274 /// cases.
275 ///
276 /// Setting this flag will supersede any value set in the environment.
277 pub fn dangerously_trust_everyone(&mut self) -> &mut Self {
278 self.dangerously_trust_everyone = Some(true);
279 self
280 }
281
282 /// Remove any ignored prefix, restoring this [`MistrustBuilder`] to a state
283 /// as if [`MistrustBuilder::ignore_prefix`] had not been called.
284 pub fn remove_ignored_prefix(&mut self) -> &mut Self {
285 self.ignore_prefix = Some(None);
286 self
287 }
288
289 /// Configure this [`MistrustBuilder`] to become disabled based on the
290 /// environment variable `var`.
291 ///
292 /// See [`Mistrust`](Mistrust#environment-variables) for details about
293 /// the handling of the environment variable.
294 ///
295 /// If `var` is not set, then we'll look at
296 /// `$FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS`.
297 pub fn controlled_by_env_var(&mut self, var: &str) -> &mut Self {
298 self.disable_by_environment = Some(disable::Disable::OnUserEnvVar(var.to_string()));
299 self
300 }
301
302 /// Like `controlled_by_env_var`, but do not override any previously set
303 /// environment settings.
304 ///
305 /// See [`Mistrust`](Mistrust#environment-variables) for details about
306 /// the handling of the environment variable.
307 ///
308 /// (The `arti-client` wants this, so that it can inform a caller-supplied
309 /// `MistrustBuilder` about its Arti-specific env var, but only if the
310 /// caller has not already provided a variable of its own. Other code
311 /// embedding `fs-mistrust` may want it too.)
312 pub fn controlled_by_env_var_if_not_set(&mut self, var: &str) -> &mut Self {
313 if self.disable_by_environment.is_none() {
314 self.controlled_by_env_var(var)
315 } else {
316 self
317 }
318 }
319
320 /// Configure this [`MistrustBuilder`] to become disabled based on the
321 /// environment variable `$FS_MISTRUST_DISABLE_PERMISSIONS_CHECKS` only,
322 ///
323 /// See [`Mistrust`](Mistrust#environment-variables) for details about
324 /// the handling of the environment variable.
325 ///
326 /// This is the default.
327 pub fn controlled_by_default_env_var(&mut self) -> &mut Self {
328 self.disable_by_environment = Some(disable::Disable::OnGlobalEnvVar);
329 self
330 }
331
332 /// Configure this [`MistrustBuilder`] to never consult the environment to
333 /// see whether it should be disabled.
334 pub fn ignore_environment(&mut self) -> &mut Self {
335 self.disable_by_environment = Some(disable::Disable::Never);
336 self
337 }
338
339 /// Considering our settings, determine whether we should trust all users
340 /// (and thereby disable our permission checks.)
341 fn should_be_enabled(&self) -> disable::Status {
342 // If we've disabled checks in our configuration, then that settles it.
343 if self.dangerously_trust_everyone == Some(true) {
344 return disable::Status::DisableChecks;
345 }
346
347 // Otherwise, we use our "disable_by_environment" setting to see whether
348 // we should check the environment.
349 self.disable_by_environment
350 .as_ref()
351 .unwrap_or(&disable::Disable::default())
352 .should_disable_checks()
353 }
354}
355
356impl Default for Mistrust {
357 fn default() -> Self {
358 MistrustBuilder::default()
359 .build()
360 .expect("Could not build default")
361 }
362}
363
364/// An object used to perform a single check.
365///
366/// Obtained from [`Mistrust::verifier()`].
367///
368/// A `Verifier` is used when [`Mistrust::check_directory`] and
369/// [`Mistrust::make_directory`] are not sufficient for your needs.
370#[derive(Clone, Debug)]
371#[must_use]
372pub struct Verifier<'a> {
373 /// The [`Mistrust`] that was used to create this verifier.
374 mistrust: &'a Mistrust,
375
376 /// Has the user called [`Verifier::permit_readable`]?
377 readable_okay: bool,
378
379 /// Has the user called [`Verifier::all_errors`]?
380 collect_multiple_errors: bool,
381
382 /// If the user called [`Verifier::require_file`] or
383 /// [`Verifier::require_directory`], which did they call?
384 enforce_type: Type,
385
386 /// If true, we want to check all the contents of this directory as well as
387 /// the directory itself. Requires the `walkdir` feature.
388 check_contents: bool,
389}
390
391/// A type of object that we have been told to require.
392#[derive(Debug, Clone, Copy)]
393enum Type {
394 /// A directory.
395 Dir,
396 /// A regular file.
397 File,
398 /// A directory or a regular file.
399 DirOrFile,
400 /// Absolutely anything at all.
401 Anything,
402}
403
404impl Mistrust {
405 /// Return a new [`MistrustBuilder`].
406 pub fn builder() -> MistrustBuilder {
407 MistrustBuilder::default()
408 }
409
410 /// Initialize a new default `Mistrust`.
411 ///
412 /// By default:
413 /// * we will inspect all directories that are used to resolve any path that is checked.
414 pub fn new() -> Self {
415 Self::default()
416 }
417
418 /// Construct a new `Mistrust` that trusts all users and all groups.
419 ///
420 /// (In effect, this `Mistrust` will have all of its permissions checks
421 /// disabled, since if all users and groups are trusted, it doesn't matter
422 /// what the permissions on any file and directory are.)
423 pub fn new_dangerously_trust_everyone() -> Self {
424 Self::builder()
425 .dangerously_trust_everyone()
426 .build()
427 .expect("Could not construct a Mistrust")
428 }
429
430 /// Create a new [`Verifier`] with this configuration, to perform a single check.
431 pub fn verifier(&self) -> Verifier<'_> {
432 Verifier {
433 mistrust: self,
434 readable_okay: false,
435 collect_multiple_errors: false,
436 enforce_type: Type::DirOrFile,
437 check_contents: false,
438 }
439 }
440
441 /// Verify that `dir` is a directory that only trusted users can read from,
442 /// list the files in, or write to.
443 ///
444 /// If it is, and we can verify that, return `Ok(())`. Otherwise, return
445 /// the first problem that we encountered when verifying it.
446 ///
447 /// `m.check_directory(dir)` is equivalent to
448 /// `m.verifier().require_directory().check(dir)`. If you need different
449 /// behavior, see [`Verifier`] for more options.
450 pub fn check_directory<P: AsRef<Path>>(&self, dir: P) -> Result<()> {
451 self.verifier().require_directory().check(dir)
452 }
453
454 /// As `check_directory`, but create the directory if needed.
455 ///
456 /// `m.check_directory(dir)` is equivalent to
457 /// `m.verifier().make_directory(dir)`. If you need different behavior, see
458 /// [`Verifier`] for more options.
459 pub fn make_directory<P: AsRef<Path>>(&self, dir: P) -> Result<()> {
460 self.verifier().make_directory(dir)
461 }
462
463 /// Return true if this `Mistrust` object has been configured to trust all
464 /// users.
465 pub(crate) fn is_disabled(&self) -> bool {
466 self.status.disabled()
467 }
468
469 /// Create a new [`FileAccess`] for reading or writing files
470 /// while enforcing the rules of this `Mistrust`.
471 pub fn file_access(&self) -> FileAccess<'_> {
472 self.verifier().file_access()
473 }
474}
475
476impl<'a> Verifier<'a> {
477 /// Create a new [`FileAccess`] for reading or writing files
478 /// while enforcing the rules of this `Verifier`.
479 pub fn file_access(self) -> FileAccess<'a> {
480 FileAccess::from_verifier(self)
481 }
482
483 /// Configure this `Verifier` to require that all paths it checks be
484 /// files (not directories).
485 pub fn require_file(mut self) -> Self {
486 self.enforce_type = Type::File;
487 self
488 }
489
490 /// Configure this `Verifier` to require that all paths it checks be
491 /// directories.
492 pub fn require_directory(mut self) -> Self {
493 self.enforce_type = Type::Dir;
494 self
495 }
496
497 /// Configure this `Verifier` to allow the paths that it checks to be
498 /// filesystem objects of any type.
499 ///
500 /// By default, the final path (after resolving all links) must be a
501 /// directory or a regular file, not (for example) a block device or a named
502 /// pipe.
503 pub fn permit_all_object_types(mut self) -> Self {
504 self.enforce_type = Type::Anything;
505 self
506 }
507
508 /// Configure this `Verifier` to permit the target files/directory to be
509 /// _readable_ by untrusted users.
510 ///
511 /// By default, we assume that the caller wants the target file or directory
512 /// to be only readable or writable by trusted users. With this flag, we
513 /// permit the target file or directory to be readable by untrusted users,
514 /// but not writable.
515 ///
516 /// (Note that we always allow the _parent directories_ of the target to be
517 /// readable by untrusted users, since their readability does not make the
518 /// target readable.)
519 pub fn permit_readable(mut self) -> Self {
520 self.readable_okay = true;
521 self
522 }
523
524 /// Tell this `Verifier` to accumulate as many errors as possible, rather
525 /// than stopping at the first one.
526 ///
527 /// If a single error is found, that error will be returned. Otherwise, the
528 /// resulting error type will be [`Error::Multiple`].
529 ///
530 /// # Example
531 ///
532 /// ```
533 /// # use fs_mistrust::Mistrust;
534 /// if let Err(e) = Mistrust::new().verifier().all_errors().check("/home/gardenGnostic/.gnupg/") {
535 /// for error in e.errors() {
536 /// println!("{}", e)
537 /// }
538 /// }
539 /// ```
540 pub fn all_errors(mut self) -> Self {
541 self.collect_multiple_errors = true;
542 self
543 }
544
545 /// Configure this verifier so that, after checking the directory, check all
546 /// of its contents.
547 ///
548 /// Symlinks are not permitted; both files and directories are allowed. This
549 /// option implies `require_directory()`, since only a directory can have
550 /// contents.
551 ///
552 /// Requires that the `walkdir` feature is enabled.
553 #[cfg(feature = "walkdir")]
554 pub fn check_content(mut self) -> Self {
555 self.check_contents = true;
556 self.require_directory()
557 }
558
559 /// Check whether the file or directory at `path` conforms to the
560 /// requirements of this `Verifier` and the [`Mistrust`] that created it.
561 pub fn check<P: AsRef<Path>>(&self, path: P) -> Result<()> {
562 let path = path.as_ref();
563
564 // This is the powerhouse of our verifier code:
565 //
566 // See the `imp` module for actual implementation logic.
567 let mut error_iterator = self
568 .check_errors(path.as_ref())
569 .chain(self.check_content_errors(path.as_ref()));
570
571 // Collect either the first error, or all errors.
572 let opt_error: Option<Error> = if self.collect_multiple_errors {
573 error_iterator.collect()
574 } else {
575 let next = error_iterator.next();
576 drop(error_iterator); // so that "canonical" is no longer borrowed.
577 next
578 };
579
580 if let Some(err) = opt_error {
581 return Err(err);
582 }
583
584 Ok(())
585 }
586 /// Check whether `path` is a valid directory, and create it if it doesn't
587 /// exist.
588 ///
589 /// Returns `Ok` if the directory already existed or if it was just created,
590 /// and it conforms to the requirements of this `Verifier` and the
591 /// [`Mistrust`] that created it.
592 ///
593 /// Return an error if:
594 /// * there was a permissions or ownership problem in the path or any of
595 /// its ancestors,
596 /// * there was a problem when creating the directory
597 /// * after creating the directory, we found that it had a permissions or
598 /// ownership problem.
599 pub fn make_directory<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
600 self.enforce_type = Type::Dir;
601
602 let path = path.as_ref();
603 match self.clone().check(path) {
604 Err(Error::NotFound(_)) => {}
605 Err(other_error) => return Err(other_error),
606 Ok(()) => return Ok(()), // no error; file exists.
607 }
608
609 // Looks like we got a "not found", so we're creating the path.
610 let mut bld = DirBuilder::new();
611 #[cfg(target_family = "unix")]
612 {
613 use std::os::unix::fs::DirBuilderExt;
614 bld.mode(0o700);
615 }
616 bld.recursive(true)
617 .create(path)
618 .map_err(|e| Error::CreatingDir(Arc::new(e)))?;
619
620 // We built the path! But for paranoia's sake, check it again.
621 self.check(path)
622 }
623
624 /// Check whether `path` is a directory conforming to the requirements of
625 /// this `Verifier` and the [`Mistrust`] that created it.
626 ///
627 /// If it is, then return a new [`CheckedDir`] that can be used to securely access
628 /// the contents of this directory.
629 pub fn secure_dir<P: AsRef<Path>>(self, path: P) -> Result<CheckedDir> {
630 let path = path.as_ref();
631 self.clone().require_directory().check(path)?;
632 CheckedDir::new(&self, path)
633 }
634
635 /// Check whether `path` is a directory conforming to the requirements of
636 /// this `Verifier` and the [`Mistrust`] that created it.
637 ///
638 /// If successful, then return a new [`CheckedDir`] that can be used to
639 /// securely access the contents of this directory.
640 pub fn make_secure_dir<P: AsRef<Path>>(self, path: P) -> Result<CheckedDir> {
641 let path = path.as_ref();
642 self.clone().require_directory().make_directory(path)?;
643 CheckedDir::new(&self, path)
644 }
645}
646
647#[cfg(test)]
648mod test {
649 // @@ begin test lint list maintained by maint/add_warning @@
650 #![allow(clippy::bool_assert_comparison)]
651 #![allow(clippy::clone_on_copy)]
652 #![allow(clippy::dbg_macro)]
653 #![allow(clippy::mixed_attributes_style)]
654 #![allow(clippy::print_stderr)]
655 #![allow(clippy::print_stdout)]
656 #![allow(clippy::single_char_pattern)]
657 #![allow(clippy::unwrap_used)]
658 #![allow(clippy::unchecked_time_subtraction)]
659 #![allow(clippy::useless_vec)]
660 #![allow(clippy::needless_pass_by_value)]
661 //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
662 use super::*;
663 use assert_matches::assert_matches;
664 use testing::{Dir, MistrustOp, mistrust_build};
665
666 #[cfg(target_family = "unix")]
667 use testing::LinkType;
668
669 #[cfg(target_family = "unix")]
670 #[test]
671 fn simple_cases() {
672 let d = Dir::new();
673 d.dir("a/b/c");
674 d.dir("e/f/g");
675 d.chmod("a", 0o755);
676 d.chmod("a/b", 0o755);
677 d.chmod("a/b/c", 0o700);
678 d.chmod("e", 0o755);
679 d.chmod("e/f", 0o777);
680 d.link_rel(LinkType::Dir, "a/b/c", "d");
681
682 let m = mistrust_build(&[
683 MistrustOp::IgnorePrefix(d.canonical_root()),
684 MistrustOp::TrustNoGroupId(),
685 ]);
686
687 // /a/b/c should be fine...
688 m.check_directory(d.path("a/b/c")).unwrap();
689 // /e/f/g should not.
690 let e = m.check_directory(d.path("e/f/g")).unwrap_err();
691 assert!(matches!(e, Error::BadPermission(_, 0o777, 0o022)));
692 assert_eq!(e.path().unwrap(), d.path("e/f").canonicalize().unwrap());
693
694 m.check_directory(d.path("d")).unwrap();
695 }
696
697 #[cfg(target_family = "unix")]
698 #[test]
699 fn admin_only() {
700 use std::os::unix::prelude::MetadataExt;
701
702 let d = Dir::new();
703 d.dir("a/b");
704 d.chmod("a", 0o700);
705 d.chmod("a/b", 0o700);
706
707 if d.path("a/b").metadata().unwrap().uid() == 0 {
708 // Nothing to do here; we _are_ root.
709 return;
710 }
711
712 // With normal settings should be okay...
713 let m = mistrust_build(&[MistrustOp::IgnorePrefix(d.canonical_root())]);
714 m.check_directory(d.path("a/b")).unwrap();
715
716 // With admin_only, it'll fail.
717 let m = mistrust_build(&[
718 MistrustOp::IgnorePrefix(d.canonical_root()),
719 MistrustOp::TrustAdminOnly(),
720 ]);
721
722 let err = m.check_directory(d.path("a/b")).unwrap_err();
723 assert!(matches!(err, Error::BadOwner(_, _)));
724 assert_eq!(err.path().unwrap(), d.path("a").canonicalize().unwrap());
725 }
726
727 #[test]
728 fn want_type() {
729 let d = Dir::new();
730 d.dir("a");
731 d.file("b");
732 d.chmod("a", 0o700);
733 d.chmod("b", 0o600);
734
735 let m = mistrust_build(&[
736 MistrustOp::IgnorePrefix(d.canonical_root()),
737 MistrustOp::TrustNoGroupId(),
738 ]);
739
740 // If we insist stuff is its own type, it works fine.
741 m.verifier().require_directory().check(d.path("a")).unwrap();
742 m.verifier().require_file().check(d.path("b")).unwrap();
743
744 // If we insist on a different type, we hit an error.
745 let e = m
746 .verifier()
747 .require_directory()
748 .check(d.path("b"))
749 .unwrap_err();
750 assert!(matches!(e, Error::BadType(_)));
751 assert_eq!(e.path().unwrap(), d.path("b").canonicalize().unwrap());
752
753 let e = m.verifier().require_file().check(d.path("a")).unwrap_err();
754 assert!(matches!(e, Error::BadType(_)));
755 assert_eq!(e.path().unwrap(), d.path("a").canonicalize().unwrap());
756
757 // TODO: Possibly, make sure that a special file matches neither.
758 }
759
760 #[cfg(target_family = "unix")]
761 #[test]
762 fn readable_ok() {
763 let d = Dir::new();
764 d.dir("a/b");
765 d.file("a/b/c");
766 d.chmod("a", 0o750);
767 d.chmod("a/b", 0o750);
768 d.chmod("a/b/c", 0o640);
769
770 let m = mistrust_build(&[
771 MistrustOp::IgnorePrefix(d.canonical_root()),
772 MistrustOp::TrustNoGroupId(),
773 ]);
774
775 // These will fail, since the file or directory is readable.
776 let e = m.verifier().check(d.path("a/b")).unwrap_err();
777 assert!(matches!(e, Error::BadPermission(..)));
778 assert_eq!(e.path().unwrap(), d.path("a/b").canonicalize().unwrap());
779 let e = m.verifier().check(d.path("a/b/c")).unwrap_err();
780 assert!(matches!(e, Error::BadPermission(..)));
781 assert_eq!(e.path().unwrap(), d.path("a/b/c").canonicalize().unwrap());
782
783 // Now allow readable targets.
784 m.verifier().permit_readable().check(d.path("a/b")).unwrap();
785 m.verifier()
786 .permit_readable()
787 .check(d.path("a/b/c"))
788 .unwrap();
789 }
790
791 #[cfg(target_family = "unix")]
792 #[test]
793 fn multiple_errors() {
794 let d = Dir::new();
795 d.dir("a/b");
796 d.chmod("a", 0o700);
797 d.chmod("a/b", 0o700);
798
799 let m = mistrust_build(&[
800 MistrustOp::IgnorePrefix(d.canonical_root()),
801 MistrustOp::TrustNoGroupId(),
802 ]);
803
804 // Only one error occurs, so we get that error.
805 let e = m
806 .verifier()
807 .all_errors()
808 .check(d.path("a/b/c"))
809 .unwrap_err();
810 assert!(matches!(e, Error::NotFound(_)));
811 assert_eq!(1, e.errors().count());
812
813 // Introduce a second error...
814 d.chmod("a/b", 0o770);
815 let e = m
816 .verifier()
817 .all_errors()
818 .check(d.path("a/b/c"))
819 .unwrap_err();
820 assert!(matches!(e, Error::Multiple(_)));
821 let errs: Vec<_> = e.errors().collect();
822 assert_eq!(2, errs.len());
823 assert!(matches!(&errs[0], Error::BadPermission(..)));
824 assert!(matches!(&errs[1], Error::NotFound(_)));
825 }
826
827 #[cfg(target_family = "unix")]
828 #[test]
829 fn sticky() {
830 let d = Dir::new();
831 d.dir("a/b/c");
832 d.chmod("a", 0o777);
833 d.chmod("a/b", 0o755);
834 d.chmod("a/b/c", 0o700);
835
836 let m = mistrust_build(&[MistrustOp::IgnorePrefix(d.canonical_root())]);
837
838 // `a` is world-writable, so the first check will fail.
839 m.check_directory(d.path("a/b/c")).unwrap_err();
840
841 // Now `a` is world-writable _and_ sticky, so the check should succeed.
842 d.chmod("a", 0o777 | crate::imp::STICKY_BIT);
843
844 m.check_directory(d.path("a/b/c")).unwrap();
845
846 // Make sure we got the right definition!
847 #[allow(clippy::useless_conversion)]
848 {
849 assert_eq!(crate::imp::STICKY_BIT, u32::from(libc::S_ISVTX));
850 }
851 }
852
853 #[cfg(target_family = "unix")]
854 #[test]
855 fn trust_gid() {
856 use std::os::unix::prelude::MetadataExt;
857 let d = Dir::new();
858 d.dir("a/b");
859 d.chmod("a", 0o770);
860 d.chmod("a/b", 0o770);
861
862 let m = mistrust_build(&[
863 MistrustOp::IgnorePrefix(d.canonical_root()),
864 MistrustOp::TrustNoGroupId(),
865 ]);
866
867 // By default, we shouldn't be accept this directory, since it is
868 // group-writable.
869 let e = m.check_directory(d.path("a/b")).unwrap_err();
870 assert!(matches!(e, Error::BadPermission(..)));
871
872 // But we can make the group trusted, which will make it okay for the
873 // directory to be group-writable.
874 let gid = d.path("a/b").metadata().unwrap().gid();
875
876 let m = mistrust_build(&[
877 MistrustOp::IgnorePrefix(d.canonical_root()),
878 MistrustOp::TrustGroup(gid),
879 ]);
880
881 m.check_directory(d.path("a/b")).unwrap();
882
883 // OTOH, if we made a _different_ group trusted, it'll fail.
884 let m = mistrust_build(&[
885 MistrustOp::IgnorePrefix(d.canonical_root()),
886 MistrustOp::TrustGroup(gid ^ 1),
887 ]);
888
889 let e = m.check_directory(d.path("a/b")).unwrap_err();
890 assert!(matches!(e, Error::BadPermission(..)));
891 }
892
893 #[test]
894 fn make_directory() {
895 let d = Dir::new();
896 d.dir("a/b");
897
898 let m = mistrust_build(&[MistrustOp::IgnorePrefix(d.canonical_root())]);
899
900 #[cfg(target_family = "unix")]
901 {
902 // Try once with bad permissions.
903 d.chmod("a", 0o777);
904 let e = m.make_directory(d.path("a/b/c/d")).unwrap_err();
905 assert!(matches!(e, Error::BadPermission(..)));
906
907 // Now make the permissions correct.
908 d.chmod("a", 0o0700);
909 d.chmod("a/b", 0o0700);
910 }
911
912 // Make the directory!
913 m.make_directory(d.path("a/b/c/d")).unwrap();
914
915 // Make sure it exists and has good permissions.
916 m.check_directory(d.path("a/b/c/d")).unwrap();
917
918 // Try make_directory again and make sure _that_ succeeds.
919 m.make_directory(d.path("a/b/c/d")).unwrap();
920 }
921
922 #[cfg(target_family = "unix")]
923 #[cfg(feature = "walkdir")]
924 #[test]
925 fn check_contents() {
926 let d = Dir::new();
927 d.dir("a/b/c");
928 d.file("a/b/c/d");
929 d.chmod("a", 0o700);
930 d.chmod("a/b", 0o700);
931 d.chmod("a/b/c", 0o755);
932 d.chmod("a/b/c/d", 0o666);
933
934 let m = mistrust_build(&[MistrustOp::IgnorePrefix(d.canonical_root())]);
935
936 // A check should work...
937 m.check_directory(d.path("a/b")).unwrap();
938
939 // But we get an error if we check the contents.
940 let e = m
941 .verifier()
942 .all_errors()
943 .check_content()
944 .check(d.path("a/b"))
945 .unwrap_err();
946 assert_eq!(1, e.errors().count());
947
948 // We only expect an error on the _writable_ contents: the _readable_
949 // a/b/c is okay.
950 assert_eq!(e.path().unwrap(), d.path("a/b/c/d"));
951 }
952
953 #[test]
954 fn trust_everyone() {
955 let d = Dir::new();
956 d.dir("a/b/c");
957 d.file("a/b/c/d");
958 d.chmod("a", 0o777);
959 d.chmod("a/b", 0o777);
960 d.chmod("a/b/c", 0o777);
961 d.chmod("a/b/c/d", 0o666);
962
963 let m = mistrust_build(&[MistrustOp::DangerouslyTrustEveryone()]);
964
965 // This is fine.
966 m.check_directory(d.path("a/b/c")).unwrap();
967 // This isn't a directory!
968 let err = m.check_directory(d.path("a/b/c/d")).unwrap_err();
969 assert!(matches!(err, Error::BadType(_)));
970
971 // But it _is_ a file.
972 m.verifier()
973 .require_file()
974 .check(d.path("a/b/c/d"))
975 .unwrap();
976 }
977
978 #[test]
979 fn default_mistrust() {
980 // we can't test a mistrust without ignore_prefix, but we should make sure that we can build one.
981 let _m = Mistrust::default();
982 }
983
984 #[test]
985 fn empty_path() {
986 let m = mistrust_build(&[MistrustOp::DangerouslyTrustEveryone()]);
987 assert_matches!(m.check_directory(""), Err(Error::NotFound(_)));
988
989 let m = Mistrust::default();
990 assert_matches!(m.check_directory(""), Err(Error::NotFound(_)));
991 }
992
993 // TODO: Write far more tests.
994 // * Can there be a test for a failed readlink()? I can't see an easy way
995 // to provoke that without trying to make a time-of-check/time-of-use race
996 // condition, since we stat the link before we call readlink on it.
997 // * Can there be a test for a failing call to std::env::current_dir? Seems
998 // hard to provoke without calling set_current_dir(), which isn't good
999 // manners in a test.
1000}