Skip to main content

aria_primitives/
private_fs.rs

1//! Filesystem primitives for state that must remain private to the current user.
2//!
3//! Errors here name the fault, not the path. The caller knows which of its
4//! files it was preparing, and says so when it wraps the failure.
5
6use std::fs::{self, DirBuilder, File, Metadata, Permissions};
7use std::io;
8use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
9use std::path::Path;
10
11const PRIVATE_DIRECTORY_MODE: u32 = 0o700;
12pub const PRIVATE_FILE_MODE: u32 = 0o600;
13
14pub fn prepare_directory(path: &Path) -> Result<(), PrivateDirectoryError> {
15    match DirBuilder::new().mode(PRIVATE_DIRECTORY_MODE).create(path) {
16        Ok(()) => {}
17        Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {}
18        Err(source) => return Err(PrivateDirectoryError::Create(source)),
19    }
20
21    let metadata = fs::symlink_metadata(path).map_err(PrivateDirectoryError::Inspect)?;
22    if !metadata.file_type().is_dir() {
23        return Err(PrivateDirectoryError::NotDirectory);
24    }
25
26    let expected_uid = rustix::process::getuid().as_raw();
27    if metadata.uid() != expected_uid {
28        return Err(PrivateDirectoryError::WrongOwner {
29            expected_uid,
30            actual_uid: metadata.uid(),
31        });
32    }
33
34    if metadata.permissions().mode() & 0o777 != PRIVATE_DIRECTORY_MODE {
35        fs::set_permissions(path, Permissions::from_mode(PRIVATE_DIRECTORY_MODE))
36            .map_err(PrivateDirectoryError::SetPermissions)?;
37    }
38    Ok(())
39}
40
41#[derive(Debug, thiserror::Error)]
42pub enum PrivateDirectoryError {
43    #[error("failed to create the directory")]
44    Create(#[source] io::Error),
45
46    #[error("failed to inspect the directory")]
47    Inspect(#[source] io::Error),
48
49    #[error("the path is not a directory")]
50    NotDirectory,
51
52    #[error("the directory belongs to uid {actual_uid}, expected uid {expected_uid}")]
53    WrongOwner { expected_uid: u32, actual_uid: u32 },
54
55    #[error("failed to secure the directory permissions")]
56    SetPermissions(#[source] io::Error),
57}
58
59/// For files the daemon writes, and therefore owns: a drifted mode is repaired.
60pub fn prepare_file(file: &File) -> Result<Metadata, PrivateFileError> {
61    let metadata = own_regular_file(file)?;
62    if metadata.permissions().mode() & 0o777 != PRIVATE_FILE_MODE {
63        file.set_permissions(Permissions::from_mode(PRIVATE_FILE_MODE))
64            .map_err(PrivateFileError::SetPermissions)?;
65    }
66    Ok(metadata)
67}
68
69/// For files the daemon reads back: a drifted mode is refused, never repaired.
70pub fn verify_file(file: &File) -> Result<Metadata, PrivateFileError> {
71    let metadata = own_regular_file(file)?;
72    let actual_mode = metadata.permissions().mode() & 0o777;
73    if actual_mode != PRIVATE_FILE_MODE {
74        return Err(PrivateFileError::NotPrivate {
75            expected_mode: PRIVATE_FILE_MODE,
76            actual_mode,
77        });
78    }
79    Ok(metadata)
80}
81
82fn own_regular_file(file: &File) -> Result<Metadata, PrivateFileError> {
83    let metadata = file.metadata().map_err(PrivateFileError::Inspect)?;
84    if !metadata.is_file() {
85        return Err(PrivateFileError::NotRegularFile);
86    }
87
88    let expected_uid = rustix::process::getuid().as_raw();
89    if metadata.uid() != expected_uid {
90        return Err(PrivateFileError::WrongOwner {
91            expected_uid,
92            actual_uid: metadata.uid(),
93        });
94    }
95    Ok(metadata)
96}
97
98#[derive(Debug, thiserror::Error)]
99pub enum PrivateFileError {
100    #[error("failed to inspect the file")]
101    Inspect(#[source] io::Error),
102
103    #[error("the path is not a regular file")]
104    NotRegularFile,
105
106    #[error("the file belongs to uid {actual_uid}, expected uid {expected_uid}")]
107    WrongOwner { expected_uid: u32, actual_uid: u32 },
108
109    #[error("the file has mode {actual_mode:o}; expected {expected_mode:o}")]
110    NotPrivate {
111        expected_mode: u32,
112        actual_mode: u32,
113    },
114
115    #[error("failed to secure the file permissions")]
116    SetPermissions(#[source] io::Error),
117}