Commit c4a6c6d8 authored by Nick Mathewson's avatar Nick Mathewson 🦞
Browse files

Implement support for the Sticky Bit.

parent c4a5a49b
Loading
Loading
Loading
Loading
+27 −3
Original line number Original line Diff line number Diff line
@@ -10,6 +10,18 @@ use crate::{
    Error, Result, Type,
    Error, Result, Type,
};
};


/// Definition for the "sticky bit", which on Unix means that the contents of
/// directory may not be renamed, deleted, or otherwise modified by a non-owner
/// of those contents, even if the user has write permissions on the directory.
///
/// This is the usual behavior for /tmp: You can make your own directories in
/// /tmp, but you can't modify other people's.
///
/// (We'd use libc's version of `S_ISVTX`, but they vacillate between u16 and
/// u32 depending what platform you're on.)
#[cfg(target_family = "unix")]
pub(crate) const STICKY_BIT: u32 = 0o1000;

impl<'a> super::Verifier<'a> {
impl<'a> super::Verifier<'a> {
    /// Return an iterator of all the security problems with `path`.
    /// Return an iterator of all the security problems with `path`.
    ///
    ///
@@ -108,12 +120,24 @@ impl<'a> super::Verifier<'a> {
                0o077
                0o077
            } else {
            } else {
                // If this is the target object and it may be readable, or if
                // If this is the target object and it may be readable, or if
                // this is _any parent directory_, then we only forbid the
                // this is _any parent directory_, then we typically forbid the
                // group-write and all-write bits.  (Those are the bits that
                // group-write and all-write bits.  (Those are the bits that
                // would allow non-trusted users to change the object, or change
                // would allow non-trusted users to change the object, or change
                // things around in a directory.)
                // things around in a directory.)
                if meta.is_dir()
                    && meta.mode() & STICKY_BIT != 0
                    && path_type == PathType::Intermediate
                {
                    // This is an intermediate directory and this sticky bit is
                    // set.  Thus, we don't care if it is world-writable or
                    // group-writable, since only the _owner_  of a file in this
                    // directory can move or rename it.
                    0o000
                } else {
                    // It's not a sticky-bit intermediate directory; actually
                    // forbid 022.
                    0o022
                    0o022
                // TODO: Handle sticky bit.
                }
            };
            };
            let bad_bits = meta.mode() & forbidden_bits;
            let bad_bits = meta.mode() & forbidden_bits;
            if bad_bits != 0 {
            if bad_bits != 0 {
+27 −0
Original line number Original line Diff line number Diff line
@@ -498,6 +498,33 @@ mod test {
        assert!(matches!(&errs[1], Error::NotFound(_)));
        assert!(matches!(&errs[1], Error::NotFound(_)));
    }
    }


    #[cfg(target_family = "unix")]
    #[test]
    fn sticky() {
        let d = Dir::new();
        d.dir("a/b/c");
        d.chmod("a", 0o777);
        d.chmod("a/b", 0o755);
        d.chmod("a/b/c", 0o700);

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

        // `a` is world-writable, so the first check will fail.
        m.check_directory(d.path("a/b/c")).unwrap_err();

        // Now `a` is world-writable _and_ sticky, so the check should succeed.
        d.chmod("a", 0o777 | crate::imp::STICKY_BIT);

        m.check_directory(d.path("a/b/c")).unwrap();

        // Make sure we got the right definition!
        #[allow(clippy::useless_conversion)]
        {
            assert_eq!(crate::imp::STICKY_BIT, u32::from(libc::S_ISVTX));
        }
    }

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