Implement support for the Sticky Bit.

This commit is contained in:
Nick Mathewson 2022-04-17 11:43:59 -04:00
parent c4a5a49b55
commit c4a6c6d82e
2 changed files with 54 additions and 3 deletions

View File

@ -10,6 +10,18 @@ use crate::{
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> {
/// Return an iterator of all the security problems with `path`.
///
@ -108,12 +120,24 @@ impl<'a> super::Verifier<'a> {
0o077
} else {
// 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
// would allow non-trusted users to change the object, or change
// things around in a directory.)
0o022
// TODO: Handle sticky bit.
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
}
};
let bad_bits = meta.mode() & forbidden_bits;
if bad_bits != 0 {

View File

@ -498,6 +498,33 @@ mod test {
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.
// * 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