1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
//! Declare an Error type for `fs-mistrust`.
use std::path::Path;
use std::{path::PathBuf, sync::Arc};
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
#[cfg(feature = "anon_home")]
use crate::anon_home::PathExt as _;
// Define a local-only version of anonymize_home so that we can define our errors
// unconditionally.
#[cfg(not(feature = "anon_home"))]
trait PathExt {
/// A do-nothing extension function.
fn anonymize_home(&self) -> &Path;
}
#[cfg(not(feature = "anon_home"))]
impl PathExt for Path {
fn anonymize_home(&self) -> &Path {
self
}
}
/// An error returned while checking a path for privacy.
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
/// A target (or one of its ancestors) was not found.
#[error("File or directory {} not found", _0.anonymize_home())]
NotFound(PathBuf),
/// A target (or one of its ancestors) had incorrect permissions.
///
/// Only generated on unix-like systems.
///
/// The first integer contains the current permission bits, and the second
/// contains the permission bits which were incorrectly set.
#[error("Incorrect permissions: {} is {}; must be {}",
_0.anonymize_home(),
format_access_bits(* .1, '='), format_access_bits(* .2, '-'))]
BadPermission(PathBuf, u32, u32),
/// A target (or one of its ancestors) had an untrusted owner.
///
/// Only generated on unix-like systems.
///
/// The provided integer contains the user_id o
#[error("Bad owner (UID {1}) on file or directory {}", _0.anonymize_home())]
BadOwner(PathBuf, u32),
/// A target (or one of its ancestors) had the wrong type.
///
/// Ordinarily, the target may be anything at all, though you can override
/// this with [`require_file`](crate::Verifier::require_file) and
/// [`require_directory`](crate::Verifier::require_directory).
#[error("Wrong type of file at {}", _0.anonymize_home())]
BadType(PathBuf),
/// We were unable to inspect the target or one of its ancestors.
///
/// (Ironically, we might lack permissions to see if something's permissions
/// are correct.)
///
/// (The `std::io::Error` that caused this problem is wrapped in an `Arc` so
/// that our own [`Error`] type can implement `Clone`.)
#[error("Unable to access {}", _0.anonymize_home())]
CouldNotInspect(PathBuf, #[source] Arc<IoError>),
/// Multiple errors occurred while inspecting the target.
///
/// This variant will only be returned if the caller specifically asked for
/// it by calling [`all_errors`](crate::Verifier::all_errors).
///
/// We will never construct an instance of this variant with an empty `Vec`.
#[error("Multiple errors found")]
Multiple(Vec<Box<Error>>),
/// We've realized that we can't finish resolving our path without taking
/// more than the maximum number of steps. The likeliest explanation is a
/// symlink loop.
#[error("Too many steps taken or planned: Possible symlink loop?")]
StepsExceeded,
/// We can't find our current working directory, or we found it but it looks
/// impossible.
#[error("Problem finding current directory")]
CurrentDirectory(#[source] Arc<IoError>),
/// We tried to create a directory, and encountered a failure in doing so.
#[error("Problem creating directory")]
CreatingDir(#[source] Arc<IoError>),
/// We found a problem while checking the contents of the directory.
#[error("Problem in directory contents")]
Content(#[source] Box<Error>),
/// We were unable to inspect the contents of the directory
///
/// This error is only present when the `walkdir` feature is enabled.
#[cfg(feature = "walkdir")]
#[error("Unable to list directory contents")]
Listing(#[source] Arc<walkdir::Error>),
/// Tried to use an invalid path with a [`CheckedDir`](crate::CheckedDir),
#[error("Provided path was not valid for use with CheckedDir")]
InvalidSubdirectory,
/// We encountered an error while attempting an IO operation on a file.
#[error("IO error on {} while attempting to {action}", filename.anonymize_home())]
Io {
/// The file that we were trying to modify or inspect
filename: PathBuf,
/// The action that failed.
action: &'static str,
/// The error that we got when trying to perform the operation.
#[source]
err: Arc<IoError>,
},
/// A field was missing when we tried to construct a
/// [`Mistrust`](crate::Mistrust).
#[error("Missing field when constructing Mistrust")]
MissingField(#[from] derive_builder::UninitializedFieldError),
/// A group that we were configured to trust could not be found.
#[error("Configured with nonexistent group: {0}")]
NoSuchGroup(String),
/// A user that we were configured to trust could not be found.
#[error("Configured with nonexistent user: {0}")]
NoSuchUser(String),
/// Error accessing passwd/group databases or obtaining our uids/gids
#[error("Error accessing passwd/group databases or obtaining our uids/gids")]
PasswdGroupIoError(#[source] Arc<IoError>),
}
impl Error {
/// Create an error from an IoError encountered while inspecting permissions
/// on an object.
pub(crate) fn inspecting(err: IoError, fname: impl Into<PathBuf>) -> Self {
match err.kind() {
IoErrorKind::NotFound => Error::NotFound(fname.into()),
_ => Error::CouldNotInspect(fname.into(), Arc::new(err)),
}
}
/// Create an error from an IoError encountered while performing IO (open,
/// read, write) on an object.
pub(crate) fn io(err: IoError, fname: impl Into<PathBuf>, action: &'static str) -> Self {
match err.kind() {
IoErrorKind::NotFound => Error::NotFound(fname.into()),
_ => Error::Io {
filename: fname.into(),
action,
err: Arc::new(err),
},
}
}
/// Return the path, if any, associated with this error.
pub fn path(&self) -> Option<&Path> {
Some(
match self {
Error::NotFound(pb) => pb,
Error::BadPermission(pb, ..) => pb,
Error::BadOwner(pb, _) => pb,
Error::BadType(pb) => pb,
Error::CouldNotInspect(pb, _) => pb,
Error::Io { filename: pb, .. } => pb,
Error::Multiple(_) => return None,
Error::StepsExceeded => return None,
Error::CurrentDirectory(_) => return None,
Error::CreatingDir(_) => return None,
Error::InvalidSubdirectory => return None,
Error::Content(e) => return e.path(),
Error::Listing(e) => return e.path(),
Error::MissingField(_) => return None,
Error::NoSuchGroup(_) => return None,
Error::NoSuchUser(_) => return None,
Error::PasswdGroupIoError(_) => return None,
}
.as_path(),
)
}
/// Return true iff this error indicates a problem with filesystem
/// permissions.
///
/// (Other errors typically indicate an IO problem, possibly one preventing
/// us from looking at permissions in the first place)
pub fn is_bad_permission(&self) -> bool {
match self {
Error::BadPermission(..) | Error::BadOwner(_, _) | Error::BadType(_) => true,
Error::NotFound(_)
| Error::CouldNotInspect(_, _)
| Error::StepsExceeded
| Error::CurrentDirectory(_)
| Error::CreatingDir(_)
| Error::Listing(_)
| Error::InvalidSubdirectory
| Error::Io { .. }
| Error::MissingField(_)
| Error::NoSuchGroup(_)
| Error::NoSuchUser(_)
| Error::PasswdGroupIoError(_) => false,
Error::Multiple(errs) => errs.iter().any(|e| e.is_bad_permission()),
Error::Content(err) => err.is_bad_permission(),
}
}
/// Return an iterator over all of the errors contained in this Error.
///
/// If this is a singleton, the iterator returns only a single element.
/// Otherwise, it returns all the elements inside the `Error::Multiple`
/// variant.
///
/// Does not recurse, since we do not create nested instances of
/// `Error::Multiple`.
pub fn errors<'a>(&'a self) -> impl Iterator<Item = &Error> + 'a {
let result: Box<dyn Iterator<Item = &Error> + 'a> = match self {
Error::Multiple(v) => Box::new(v.iter().map(|e| e.as_ref())),
_ => Box::new(vec![self].into_iter()),
};
result
}
}
impl std::iter::FromIterator<Error> for Option<Error> {
fn from_iter<T: IntoIterator<Item = Error>>(iter: T) -> Self {
let mut iter = iter.into_iter();
let first_err = iter.next()?;
if let Some(second_err) = iter.next() {
let mut errors = Vec::with_capacity(iter.size_hint().0 + 2);
errors.push(Box::new(first_err));
errors.push(Box::new(second_err));
errors.extend(iter.map(Box::new));
Some(Error::Multiple(errors))
} else {
Some(first_err)
}
}
}
/// Convert the low 9 bits of `bits` into a unix-style string describing its
/// access permission. Insert `c` between the ugo and perm.
///
/// For example, 0o022, '+' becomes 'g+w,o+w'.
///
/// Used for generating error messages.
pub fn format_access_bits(bits: u32, c: char) -> String {
let mut s = String::new();
for (shift, prefix) in [(6, 'u'), (3, 'g'), (0, 'o')] {
let b = (bits >> shift) & 7;
if b != 0 {
if !s.is_empty() {
s.push(',');
}
s.push(prefix);
s.push(c);
for (bit, ch) in [(4, 'r'), (2, 'w'), (1, 'x')] {
if b & bit != 0 {
s.push(ch);
}
}
}
}
s
}
#[cfg(test)]
mod test {
// @@ begin test lint list maintained by maint/add_warning @@
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
use super::*;
#[test]
fn bits() {
assert_eq!(format_access_bits(0o777, '='), "u=rwx,g=rwx,o=rwx");
assert_eq!(format_access_bits(0o022, '='), "g=w,o=w");
assert_eq!(format_access_bits(0o022, '-'), "g-w,o-w");
assert_eq!(format_access_bits(0o020, '-'), "g-w");
assert_eq!(format_access_bits(0, ' '), "");
}
#[test]
fn bad_perms() {
assert_eq!(
Error::BadPermission(PathBuf::from("/path"), 0o777, 0o022).to_string(),
"Incorrect permissions: /path is u=rwx,g=rwx,o=rwx; must be g-w,o-w"
);
}
}