Skip to main content

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