use crate::capability::AccessMode;
use crate::diagnostic::NonoRemediation;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DenialReason {
PolicyBlocked,
InsufficientAccess,
UserDenied,
RateLimited,
BackendError,
UnixSocketDenied,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DenialRecord {
pub path: PathBuf,
pub access: AccessMode,
pub reason: DenialReason,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IpcDenialRecord {
pub target: String,
pub operation: String,
pub reason: String,
pub remediation: Option<NonoRemediation>,
#[deprecated(
since = "0.64.0",
note = "Use `remediation` instead. Will be removed in 1.0.0."
)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub suggested_flag: Option<String>,
}
impl IpcDenialRecord {
#[must_use]
pub fn new(
target: String,
operation: String,
reason: String,
remediation: Option<NonoRemediation>,
) -> Self {
let suggested_flag = remediation.as_ref().and_then(|rem| {
#[allow(deprecated)]
{
crate::diagnostic::codes::suggested_flag_for_remediation(rem)
}
});
#[allow(deprecated)]
Self {
target,
operation,
reason,
remediation,
suggested_flag,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SandboxViolation {
pub operation: String,
pub target: Option<String>,
}
#[must_use]
pub fn seatbelt_operation_to_access(operation: &str) -> Option<AccessMode> {
match operation {
"file-read-data" | "file-read-metadata" | "file-read-xattr" => Some(AccessMode::Read),
"file-write-data" | "file-write-create" | "file-write-unlink" | "file-write-flags"
| "file-write-mode" | "file-write-owner" | "file-write-times" | "file-write-xattr" => {
Some(AccessMode::Write)
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::capability::AccessMode;
use std::path::PathBuf;
#[test]
fn seatbelt_read_operations_map_to_read() {
assert_eq!(
seatbelt_operation_to_access("file-read-data"),
Some(AccessMode::Read)
);
assert_eq!(
seatbelt_operation_to_access("file-read-metadata"),
Some(AccessMode::Read)
);
}
#[test]
fn seatbelt_write_operations_map_to_write() {
assert_eq!(
seatbelt_operation_to_access("file-write-data"),
Some(AccessMode::Write)
);
assert_eq!(
seatbelt_operation_to_access("file-write-create"),
Some(AccessMode::Write)
);
}
#[test]
fn seatbelt_non_filesystem_operations_map_to_none() {
assert_eq!(seatbelt_operation_to_access("mach-lookup"), None);
assert_eq!(seatbelt_operation_to_access("signal"), None);
assert_eq!(seatbelt_operation_to_access("network-outbound"), None);
}
#[test]
fn ipc_denial_record_keeps_legacy_suggested_flag_in_sync() {
let remediation = NonoRemediation::GrantUnixSocket {
path: PathBuf::from("/run/user/0/bus"),
bind: false,
};
let record = IpcDenialRecord::new(
"/run/user/0/bus".to_string(),
"connect".to_string(),
"no matching unix_socket capability".to_string(),
Some(remediation),
);
#[allow(deprecated)]
{
assert_eq!(
record.suggested_flag.as_deref(),
Some("--allow-unix-socket /run/user/0/bus")
);
}
}
}