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