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