Skip to content

Commit

Permalink
feat: InaccessiblePaths dynamic whitelisting (WIP, BROKEN)
Browse files Browse the repository at this point in the history
  • Loading branch information
desbma committed Jan 26, 2025
1 parent 4f5a867 commit 18dc602
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 97 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ infinite_loop = "warn"
lossy_float_literal = "warn"
# missing_docs_in_private_items = "warn"
mixed_read_write_in_expression = "warn"
multiple_inherent_impl = "warn"
# multiple_inherent_impl = "warn"
needless_raw_strings = "warn"
non_zero_suggestions = "warn"
panic = "warn"
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ fn main() -> anyhow::Result<()> {
println!(" - `{}`", if v { "true" } else { "false" });
}
systemd::OptionValue::String(v) => println!(" - `{v}`"),
systemd::OptionValue::List { values, .. } => {
systemd::OptionValue::List(systemd::ListOptionValue { values, .. }) => {
for val in values {
println!(" - `{val}`");
}
Expand Down
2 changes: 1 addition & 1 deletion src/systemd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mod service;
mod version;

pub(crate) use options::{
build_options, OptionDescription, OptionValue, SocketFamily, SocketProtocol,
build_options, ListOptionValue, OptionDescription, OptionValue, SocketFamily, SocketProtocol,
};
pub(crate) use resolver::resolve;
pub(crate) use service::Service;
Expand Down
146 changes: 117 additions & 29 deletions src/systemd/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl fmt::Display for OptionDescription {
}
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub(crate) enum ListMode {
WhiteList,
BlackList,
Expand All @@ -59,13 +59,16 @@ pub(crate) enum ListMode {
pub(crate) enum OptionValue {
Boolean(bool), // In most case we only model the 'true' value, because false is no-op and the default
String(String), // enum-like, or free string
List {
values: Vec<String>,
value_if_empty: Option<&'static str>,
prefix: &'static str,
repeat_option: bool,
mode: ListMode,
},
List(ListOptionValue),
}

#[derive(Debug, Clone)]
pub(crate) struct ListOptionValue {
pub values: Vec<String>,
pub value_if_empty: Option<&'static str>,
pub prefix: &'static str,
pub repeat_option: bool,
pub mode: ListMode,
}

impl FromStr for OptionValue {
Expand Down Expand Up @@ -133,6 +136,31 @@ pub(crate) enum OptionValueEffect {
Multiple(Vec<OptionValueEffect>),
}

impl OptionValueEffect {
pub(crate) fn merge(&mut self, other: &OptionValueEffect) {
match self {
OptionValueEffect::Multiple(effs) => match other {
OptionValueEffect::Multiple(oeffs) => {
effs.extend(oeffs.iter().cloned());
}
oeff => {
effs.push(oeff.clone());
}
},
eff => match other {
OptionValueEffect::Multiple(oeffs) => {
let mut new_effs = vec![eff.to_owned()];
new_effs.extend(oeffs.iter().cloned());
*eff = OptionValueEffect::Multiple(new_effs);
}
oeff => {
*eff = OptionValueEffect::Multiple(vec![eff.to_owned(), oeff.to_owned()]);
}
},
}
}
}

#[derive(Debug, Clone)]
pub(crate) enum DenySyscalls {
/// See <https://github.com/systemd/systemd/blob/v254/src/shared/seccomp-util.c#L306>
Expand Down Expand Up @@ -227,9 +255,9 @@ impl DenySyscalls {
}

/// A systemd option with a value, as would be present in a config file
#[derive(Debug)]
#[derive(Debug, Clone)]
pub(crate) struct OptionWithValue {
pub name: String,
pub name: String, // TODO &'static str?
pub value: OptionValue,
}

Expand All @@ -256,13 +284,13 @@ impl fmt::Display for OptionWithValue {
write!(f, "{}={}", self.name, if *value { "true" } else { "false" })
}
OptionValue::String(value) => write!(f, "{}={}", self.name, value),
OptionValue::List {
OptionValue::List(ListOptionValue {
values,
value_if_empty,
prefix,
repeat_option,
..
} => {
}) => {
if values.is_empty() {
write!(f, "{}=", self.name)?;
if let Some(value_if_empty) = value_if_empty {
Expand Down Expand Up @@ -1167,7 +1195,7 @@ pub(crate) fn build_options(
})),
}],
updater: Some(OptionUpdater {
effect: |effect, action, _hopts| match effect {
effect: |effect, action, _| match effect {
OptionValueEffect::DenyWrite(PathDescription::Base { base, exceptions }) => {
let new_exception = match action {
ProgramAction::Write(action_path) => Some(action_path.to_owned()),
Expand Down Expand Up @@ -1201,7 +1229,7 @@ pub(crate) fn build_options(
},
OptionWithValue {
name: "ReadWritePaths".to_owned(),
value: OptionValue::List {
value: OptionValue::List(ListOptionValue {
values: merge_similar_paths(
exceptions,
hopts.merge_paths_threshold,
Expand All @@ -1210,10 +1238,10 @@ pub(crate) fn build_options(
.filter_map(|p| p.to_str().map(ToOwned::to_owned))
.collect(),
value_if_empty: None,
prefix: "",
prefix: "-",
repeat_option: false,
mode: ListMode::WhiteList,
},
}),
},
]
}
Expand All @@ -1233,7 +1261,7 @@ pub(crate) fn build_options(
options.push(OptionDescription {
name: "InaccessiblePaths",
possible_values: vec![OptionValueDescription {
value: OptionValue::List {
value: OptionValue::List(ListOptionValue {
values: base_paths
.iter()
.filter_map(|d| d.to_str().map(ToOwned::to_owned))
Expand All @@ -1242,7 +1270,7 @@ pub(crate) fn build_options(
prefix: "-",
repeat_option: false,
mode: ListMode::BlackList,
},
}),
desc: OptionEffect::Cumulative(
base_paths
.iter()
Expand All @@ -1255,7 +1283,67 @@ pub(crate) fn build_options(
.collect(),
),
}],
updater: None, // TODO
updater: Some(OptionUpdater {
effect: |effect, action, _| {
let (ProgramAction::Read(action_path)
| ProgramAction::Write(action_path)
| ProgramAction::Create(action_path)) = action
else {
return None;
};
match effect {
OptionValueEffect::Hide(PathDescription::Base { base, exceptions }) => {
let mut new_exceptions = Vec::with_capacity(exceptions.len() + 1);
new_exceptions.extend(exceptions.iter().cloned());
new_exceptions.push(action_path.to_owned());
Some(OptionValueEffect::Hide(PathDescription::Base {
base: base.to_owned(),
exceptions: new_exceptions,
}))
}
OptionValueEffect::Hide(PathDescription::Pattern(_)) => {
unimplemented!()
}
_ => None,
}
},
options: |effect, hopts| match effect {
OptionValueEffect::Hide(PathDescription::Base { base, exceptions }) => {
vec![
OptionWithValue {
name: "InaccessiblePaths".to_owned(),
#[expect(clippy::unwrap_used)] // path is from our option, so unicode safe
value: OptionValue::String(base.to_str().unwrap().to_owned()),
},
OptionWithValue {
name: "TemporaryFileSystem".to_owned(),
#[expect(clippy::unwrap_used)] // path is from our option, so unicode safe
value: OptionValue::String(format!("{}:ro", base.to_str().unwrap())),// TODO discriminate ro/rw or remove it
},
OptionWithValue {
name: "BindPaths".to_owned(),
value: OptionValue::List(ListOptionValue {
values: merge_similar_paths(
exceptions,
hopts.merge_paths_threshold,
)
.iter()
.filter_map(|p| p.to_str().map(ToOwned::to_owned))
.collect(),
value_if_empty: None,
prefix: "-",
repeat_option: false,
mode: ListMode::WhiteList,
}),
},
]
}
OptionValueEffect::DenyWrite(PathDescription::Pattern(_)) => {
unimplemented!()
}
_ => unreachable!(),
},
}),
});

// TODO NoExecPaths + ExecPaths updater
Expand Down Expand Up @@ -1324,13 +1412,13 @@ pub(crate) fn build_options(
options.push(OptionDescription {
name: "RestrictAddressFamilies",
possible_values: vec![OptionValueDescription {
value: OptionValue::List {
value: OptionValue::List(ListOptionValue {
values: afs.iter().map(|s| (*s).to_owned()).collect(),
value_if_empty: Some("none"),
prefix: "",
repeat_option: false,
mode: ListMode::WhiteList,
},
}),
desc: OptionEffect::Cumulative(
afs.into_iter()
.map(|af| {
Expand Down Expand Up @@ -1384,7 +1472,7 @@ pub(crate) fn build_options(
options.push(OptionDescription {
name: "SocketBindDeny",
possible_values: vec![OptionValueDescription {
value: OptionValue::List {
value: OptionValue::List(ListOptionValue {
values: deny_binds
.iter()
.map(|(af, proto)| format!("{af}:{proto}"))
Expand All @@ -1393,7 +1481,7 @@ pub(crate) fn build_options(
prefix: "",
repeat_option: true,
mode: ListMode::BlackList,
},
}),
desc: OptionEffect::Cumulative(
deny_binds
.into_iter()
Expand Down Expand Up @@ -1441,7 +1529,7 @@ pub(crate) fn build_options(
};
vec![OptionWithValue {
name: "SocketBindDeny".to_owned(),
value: OptionValue::List {
value: OptionValue::List(ListOptionValue {
values: denied_na
.af
.elements()
Expand All @@ -1462,7 +1550,7 @@ pub(crate) fn build_options(
prefix: "",
repeat_option: true,
mode: ListMode::BlackList,
},
}),
}]
},
}),
Expand Down Expand Up @@ -1656,13 +1744,13 @@ pub(crate) fn build_options(
options.push(OptionDescription {
name: "CapabilityBoundingSet",
possible_values: vec![OptionValueDescription {
value: OptionValue::List {
value: OptionValue::List(ListOptionValue {
values: cap_effects.iter().map(|(c, _e)| (*c).to_owned()).collect(),
value_if_empty: None,
prefix: "~",
repeat_option: false,
mode: ListMode::BlackList,
},
}),
desc: OptionEffect::Cumulative(cap_effects.into_iter().map(|(_c, e)| e).collect()),
}],
updater: None,
Expand All @@ -1683,7 +1771,7 @@ pub(crate) fn build_options(
options.push(OptionDescription {
name: "SystemCallFilter",
possible_values: vec![OptionValueDescription {
value: OptionValue::List {
value: OptionValue::List(ListOptionValue {
values: syscall_classes
.iter()
.map(|c| format!("@{c}:EPERM"))
Expand All @@ -1692,7 +1780,7 @@ pub(crate) fn build_options(
prefix: "~",
repeat_option: false,
mode: ListMode::BlackList,
},
}),
desc: OptionEffect::Cumulative(
syscall_classes
.into_iter()
Expand Down
Loading

0 comments on commit 18dc602

Please sign in to comment.