diff --git a/src/summarize.rs b/src/summarize.rs index 4fccf9e..4d40624 100644 --- a/src/summarize.rs +++ b/src/summarize.rs @@ -90,7 +90,7 @@ impl ValueCounted for u16 { } } -impl CountableSetSpecifier { +impl CountableSetSpecifier { fn contains_one(&self, needle: &T) -> bool { match self { Self::None => false, @@ -116,6 +116,28 @@ impl CountableSetSpecifier { Self::All => !matches!(other, Self::None), } } + + pub(crate) fn remove(&mut self, es: &Self) { + debug_assert!(self.intersects(es)); + let Self::One(e) = es else { unreachable!() }; + match self { + Self::None => unreachable!(), + Self::One(_) => { + *self = Self::None; + } + Self::Some(es) => { + let idx = es.iter().position(|e2| e == e2).unwrap(); + es.remove(idx); + } + Self::AllExcept(excs) => { + let idx = excs.binary_search(&e).unwrap_err(); + excs.insert(idx, e.to_owned()); + } + Self::All => { + *self = Self::AllExcept(vec![e.to_owned()]); + } + } + } } /// Quantify something that is done or denied diff --git a/src/systemd/resolver.rs b/src/systemd/resolver.rs index 5843fdb..925659e 100644 --- a/src/systemd/resolver.rs +++ b/src/systemd/resolver.rs @@ -7,8 +7,15 @@ use crate::{ }, }; +type OptionValueUpdater = fn(&OptionValueEffect) -> OptionValue; + impl OptionValueEffect { - fn compatible(&self, action: &ProgramAction, prev_actions: &[ProgramAction]) -> bool { + fn compatible( + &self, + action: &ProgramAction, + prev_actions: &[ProgramAction], + value_updater: Option, + ) -> ActionOptionEffectCompatibility { match self { OptionValueEffect::DenyAction(denied) => match denied { ProgramAction::NetworkActivity(denied) => { @@ -19,19 +26,43 @@ impl OptionValueEffect { local_port, }) = action { - !denied.af.intersects(af) - || !denied.proto.intersects(proto) - || !denied.kind.intersects(kind) - || !denied.local_port.intersects(local_port) + let af_match = denied.af.intersects(af); + let proto_match = denied.proto.intersects(proto); + let kind_match = denied.kind.intersects(kind); + let local_port_match = denied.local_port.intersects(local_port); + if !af_match || !proto_match || !kind_match || !local_port_match { + ActionOptionEffectCompatibility::Compatible + } else if let Some(value_updater) = value_updater { + // This option supports altering the effects and generating the option value, to make it + // compatible + let mut new_eff_local_port = denied.local_port.to_owned(); + new_eff_local_port.remove(local_port); + let new_eff = OptionValueEffect::DenyAction( + ProgramAction::NetworkActivity(NetworkActivity { + af: denied.af.to_owned(), + proto: denied.proto.to_owned(), + kind: denied.kind.to_owned(), + local_port: new_eff_local_port, + }), + ); + ActionOptionEffectCompatibility::CompatibleIfChanged( + ChangedOptionValueDescription { + value: value_updater(&new_eff), + effect: new_eff, + }, + ) + } else { + ActionOptionEffectCompatibility::Incompatible + } } else { - true + ActionOptionEffectCompatibility::Compatible } } ProgramAction::WriteExecuteMemoryMapping | ProgramAction::SetRealtimeScheduler | ProgramAction::Wakeup | ProgramAction::MknodSpecial - | ProgramAction::SetAlarm => action != denied, + | ProgramAction::SetAlarm => (action != denied).into(), ProgramAction::Syscalls(_) | ProgramAction::Read(_) | ProgramAction::Write(_) @@ -39,46 +70,101 @@ impl OptionValueEffect { }, OptionValueEffect::DenyWrite(ro_paths) => match action { ProgramAction::Write(path_action) | ProgramAction::Create(path_action) => { - !ro_paths.matches(path_action) + !ro_paths.matches(path_action).into() } - _ => true, + _ => ActionOptionEffectCompatibility::Compatible, }, OptionValueEffect::Hide(hidden_paths) => { if let ProgramAction::Read(path_action) = action { - !hidden_paths.matches(path_action) - || prev_actions.contains(&ProgramAction::Create(path_action.clone())) + (!hidden_paths.matches(path_action) + || prev_actions.contains(&ProgramAction::Create(path_action.clone()))) + .into() } else { - true + ActionOptionEffectCompatibility::Compatible } } OptionValueEffect::DenySyscalls(denied) => { if let ProgramAction::Syscalls(syscalls) = action { let denied_syscalls = denied.syscalls(); let syscalls = syscalls.iter().map(String::as_str).collect(); - denied_syscalls.intersection(&syscalls).next().is_none() + denied_syscalls + .intersection(&syscalls) + .next() + .is_none() + .into() } else { - true + ActionOptionEffectCompatibility::Compatible } } - OptionValueEffect::Multiple(effects) => { - effects.iter().all(|e| e.compatible(action, prev_actions)) - } + OptionValueEffect::Multiple(effects) => effects + .iter() + .all(|e| match e.compatible(action, prev_actions) { + ActionOptionEffectCompatibility::Compatible => true, + ActionOptionEffectCompatibility::CompatibleIfChanged(_) => todo!(), + ActionOptionEffectCompatibility::Incompatible => false, + }) + .into(), + } + } +} + +/// A systemd option value and its effect, altered from original +struct ChangedOptionValueDescription { + pub value: OptionValue, + pub effect: OptionValueEffect, +} + +/// How compatible is an action with an option effect? +enum ActionOptionEffectCompatibility { + Compatible, + CompatibleIfChanged(ChangedOptionValueDescription), + Incompatible, +} + +impl From for ActionOptionEffectCompatibility { + fn from(value: bool) -> Self { + if value { + Self::Compatible + } else { + Self::Incompatible } } } -pub(crate) fn actions_compatible(eff: &OptionValueEffect, actions: &[ProgramAction]) -> bool { +pub(crate) fn actions_compatible( + eff: &OptionValueEffect, + actions: &[ProgramAction], +) -> ActionOptionEffectCompatibility { + let mut changed_desc: Option = None; for i in 0..actions.len() { - if !eff.compatible(&actions[i], &actions[..i]) { - log::debug!( - "Option effect {:?} is incompatible with {:?}", - eff, - actions[i] - ); - return false; + let cur_eff = changed_desc.as_ref().map(|d| &d.effect).unwrap_or(eff); + match cur_eff.compatible(&actions[i], &actions[..i]) { + ActionOptionEffectCompatibility::Compatible => {} + ActionOptionEffectCompatibility::CompatibleIfChanged(new_desc) => { + log::debug!( + "Option effect {:?} is incompatible with {:?}, changing effect to {:?}", + cur_eff, + actions[i], + new_desc.effect + ); + changed_desc = Some(new_desc); + } + ActionOptionEffectCompatibility::Incompatible => { + log::debug!( + "Option effect {:?} is incompatible with {:?}", + cur_eff, + actions[i] + ); + return ActionOptionEffectCompatibility::Incompatible; + } } } - true + + if let Some(new_desc) = changed_desc { + ActionOptionEffectCompatibility::CompatibleIfChanged(new_desc) + } else { + ActionOptionEffectCompatibility::Compatible + } } pub(crate) fn resolve( @@ -98,15 +184,23 @@ pub(crate) fn resolve( }); break; } - OptionEffect::Simple(effect) => { - if actions_compatible(effect, actions) { + OptionEffect::Simple(effect) => match actions_compatible(effect, actions) { + ActionOptionEffectCompatibility::Compatible => { candidates.push(OptionWithValue { name: opt.name.to_owned(), value: opt_value_desc.value.clone(), }); break; } - } + ActionOptionEffectCompatibility::CompatibleIfChanged(opt_new_desc) => { + candidates.push(OptionWithValue { + name: opt.name.to_owned(), + value: opt_new_desc.value.clone(), + }); + break; + } + ActionOptionEffectCompatibility::Incompatible => {} + }, OptionEffect::Cumulative(effects) => { match &opt_value_desc.value { OptionValue::List { @@ -121,8 +215,17 @@ pub(crate) fn resolve( for (optv, opte) in values.iter().zip(effects) { let compatible = actions_compatible(opte, actions); let enable_opt = match mode { - ListMode::WhiteList => !compatible, - ListMode::BlackList => compatible, + ListMode::WhiteList => matches!( + compatible, + ActionOptionEffectCompatibility::Incompatible + ), + ListMode::BlackList => match compatible { + ActionOptionEffectCompatibility::Compatible => true, + ActionOptionEffectCompatibility::CompatibleIfChanged(_) => { + unimplemented!() + } + ActionOptionEffectCompatibility::Incompatible => false, + }, }; if enable_opt { compatible_opts.push(optv.to_string());