fs_mistrust/
lib.rs

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