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