Commit 712b4617 authored by Nick Mathewson's avatar Nick Mathewson 🦞
Browse files

Add support for trusted group IDs.

parent c4a6c6d8
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -114,7 +114,7 @@ impl<'a> super::Verifier<'a> {
            if uid != 0 && Some(uid) != self.mistrust.trust_uid {
                errors.push(Error::BadOwner(path.into(), uid));
            }
            let forbidden_bits = if !self.readable_okay && path_type == PathType::Final {
            let mut forbidden_bits = if !self.readable_okay && path_type == PathType::Final {
                // If this is the target object, and it must not be readable,
                // then we forbid it to be group-rwx and all-rwx.
                0o077
@@ -139,6 +139,10 @@ impl<'a> super::Verifier<'a> {
                    0o022
                }
            };
            // If we trust the GID, then we allow even more bits to be set.
            if self.mistrust.trust_gid == Some(meta.gid()) {
                forbidden_bits &= !0o070;
            }
            let bad_bits = meta.mode() & forbidden_bits;
            if bad_bits != 0 {
                errors.push(Error::BadPermission(path.into(), bad_bits));
+53 −2
Original line number Diff line number Diff line
@@ -87,8 +87,6 @@

// TODO: Stuff to add before this crate is ready....
//  - Ability to create directory if it doesn't exist.
//  - Get more flexible about group permissions. (diziet had an idea.)
//  - Stop-at-homedir support.
//  - Test the absolute heck out of it.

// POSSIBLY TODO:
@@ -168,6 +166,10 @@ pub struct Mistrust {
    /// What user ID do we trust by default (if any?)
    #[cfg(target_family = "unix")]
    trust_uid: Option<u32>,

    /// What group ID do we trust by default (if any?)
    #[cfg(target_family = "unix")]
    trust_gid: Option<u32>,
}

impl Default for Mistrust {
@@ -176,6 +178,8 @@ impl Default for Mistrust {
            ignore_prefix: None,
            #[cfg(target_family = "unix")]
            trust_uid: Some(unsafe { libc::getuid() }),
            #[cfg(target_family = "unix")]
            trust_gid: None,
        }
    }
}
@@ -249,6 +253,23 @@ impl Mistrust {
        self
    }

    #[cfg(target_family = "unix")]
    /// Configure a trusted group ID for this `Mistrust`.
    ///
    /// If a group ID is considered "trusted", then any file or directory we
    /// inspect is allowed to be readable and writable by that group.
    ///
    /// By default, no group ID is trusted, and any group-readable or
    /// group-writable objects are treated the same as world-readable and
    /// world-writable objects respectively.
    ///
    /// Anybody who is a member (or becomes a member) of the provided group will
    /// be allowed to read and modify the verified files.
    pub fn trust_group_id(&mut self, gid: u32) -> &mut Self {
        self.trust_gid = Some(gid);
        self
    }

    /// Create a new [`Verifier`] with this configuration, to perform a single check.
    pub fn verifier(&self) -> Verifier<'_> {
        Verifier {
@@ -525,6 +546,36 @@ mod test {
        }
    }

    #[cfg(target_family = "unix")]
    #[test]
    fn trust_gid() {
        use std::os::unix::prelude::MetadataExt;
        let d = Dir::new();
        d.dir("a/b");
        d.chmod("a", 0o770);
        d.chmod("a/b", 0o770);

        let mut m = Mistrust::new();
        m.ignore_prefix(d.canonical_root()).unwrap();

        // By default, we shouldn't be accept this directory, since it is
        // group-writable.
        let e = m.check_directory(d.path("a/b")).unwrap_err();
        assert!(matches!(e, Error::BadPermission(_, _)));

        // But we can make the group trusted, which will make it okay for the
        // directory to be group-writable.
        let gid = d.path("a/b").metadata().unwrap().gid();
        m.trust_group_id(gid);
        m.check_directory(d.path("a/b")).unwrap();

        // OTOH, if we made a _different_ group trusted, it'll fail.

        m.trust_group_id(gid ^ 1);
        let e = m.check_directory(d.path("a/b")).unwrap_err();
        assert!(matches!(e, Error::BadPermission(_, _)));
    }

    // TODO: Write far more tests.
    // * Can there be a test for a failed readlink()?  I can't see an easy way
    //   to provoke that without trying to make a time-of-check/time-of-use race