Skip to content

Commit

Permalink
feat: support RestrictRealtime systemd option
Browse files Browse the repository at this point in the history
  • Loading branch information
desbma-s1n committed Sep 28, 2023
1 parent 92cef27 commit 93e9efb
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 7 deletions.
22 changes: 19 additions & 3 deletions src/summarize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ pub enum ProgramAction {
/// Network (socket) activity
NetworkActivity { af: String },
/// Memory mapping with write and execute bits
WxMemoryMapping,
WriteExecuteMemoryMapping,
/// Set scheduler to a real time one
SetRealtimeScheduler,
/// Names of the syscalls made by the program
Syscalls(HashSet<String>),
}
Expand All @@ -48,6 +50,7 @@ enum SyscallInfo {
path_dst_idx: usize,
flags_idx: Option<usize>,
},
SetScheduler,
Socket,
StatFd {
fd_idx: usize,
Expand Down Expand Up @@ -130,6 +133,9 @@ lazy_static! {
},
),

// set scheduler
("sched_setscheduler", SyscallInfo::SetScheduler),

// socket
("socket", SyscallInfo::Socket),

Expand Down Expand Up @@ -369,6 +375,16 @@ where
_ => (),
}
}
Some(SyscallInfo::SetScheduler) => {
let policy = if let Some(SyscallArg::Integer { value, .. }) = syscall.args.get(1) {
value
} else {
anyhow::bail!("Unexpected args for {}: {:?}", name, syscall.args);
};
if policy.is_flag_set("SCHED_FIFO") | policy.is_flag_set("SCHED_RR") {
actions.push(ProgramAction::SetRealtimeScheduler);
}
}
Some(SyscallInfo::Socket) => {
let af = if let Some(SyscallArg::Integer {
value: IntegerExpression::NamedConst(af),
Expand All @@ -390,7 +406,7 @@ where
anyhow::bail!("Unexpected args for {}: {:?}", name, syscall.args);
};
if prot.is_flag_set("PROT_WRITE") && prot.is_flag_set("PROT_EXEC") {
actions.push(ProgramAction::WxMemoryMapping);
actions.push(ProgramAction::WriteExecuteMemoryMapping);
}
}
None => (),
Expand All @@ -408,7 +424,7 @@ where
syscall_names.sort();
for syscall_name in syscall_names {
let count = stats.get(syscall_name).unwrap();
log::debug!("{:20} {: >12}", format!("{syscall_name}:"), count);
log::debug!("{:24} {: >12}", format!("{syscall_name}:"), count);
}

Ok(actions)
Expand Down
15 changes: 13 additions & 2 deletions src/systemd/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ pub enum OptionValueEffect {
/// Deny a socket family
DenySocketFamily(String),
/// Deny a write execute memory mapping
DenyWxMemoryMapping,
DenyWriteExecuteMemoryMapping,
/// Deny real time scheduling
DenyRealtimeScheduler,
/// Union of multiple effects
Multiple(Vec<OptionValueEffect>),
}
Expand Down Expand Up @@ -1029,7 +1031,7 @@ pub fn build_options(
name: "MemoryDenyWriteExecute".to_string(),
possible_values: vec![OptionValueDescription {
value: OptionValue::Boolean(true),
desc: OptionEffect::Simple(OptionValueEffect::DenyWxMemoryMapping),
desc: OptionEffect::Simple(OptionValueEffect::DenyWriteExecuteMemoryMapping),
}],
});

Expand Down Expand Up @@ -1123,6 +1125,15 @@ pub fn build_options(
}],
});

// https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictRealtime=
options.push(OptionDescription {
name: "RestrictRealtime".to_string(),
possible_values: vec![OptionValueDescription {
value: OptionValue::Boolean(true),
desc: OptionEffect::Simple(OptionValueEffect::DenyRealtimeScheduler),
}],
});

// https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=
//
// Also change the default behavior when calling a denied syscall to return EPERM instead og killing
Expand Down
7 changes: 5 additions & 2 deletions src/systemd/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ impl OptionValueEffect {
true
}
}
OptionValueEffect::DenyWxMemoryMapping => {
!matches!(action, ProgramAction::WxMemoryMapping)
OptionValueEffect::DenyWriteExecuteMemoryMapping => {
!matches!(action, ProgramAction::WriteExecuteMemoryMapping)
}
OptionValueEffect::DenyRealtimeScheduler => {
!matches!(action, ProgramAction::SetRealtimeScheduler)
}
OptionValueEffect::Multiple(effects) => {
effects.iter().all(|e| e.compatible(action, prev_actions))
Expand Down
93 changes: 93 additions & 0 deletions tests/cl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ fn run_true() {
.stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1))
.stdout(predicate::str::contains("RestrictAddressFamilies=none\n").count(1))
.stdout(predicate::str::contains("LockPersonality=true\n").count(1))
.stdout(predicate::str::contains("RestrictRealtime=true\n").count(1))
.stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1));
}

Expand Down Expand Up @@ -77,6 +78,7 @@ fn run_write_dev_null() {
.stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1))
.stdout(predicate::str::contains("RestrictAddressFamilies=none\n").count(1))
.stdout(predicate::str::contains("LockPersonality=true\n").count(1))
.stdout(predicate::str::contains("RestrictRealtime=true\n").count(1))
.stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1));
}

Expand Down Expand Up @@ -110,6 +112,7 @@ fn run_ls_dev() {
.stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1))
.stdout(predicate::str::contains("RestrictAddressFamilies=none\n").count(1))
.stdout(predicate::str::contains("LockPersonality=true\n").count(1))
.stdout(predicate::str::contains("RestrictRealtime=true\n").count(1))
.stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1));
}

Expand Down Expand Up @@ -143,6 +146,7 @@ fn run_ls_proc() {
.stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1))
.stdout(predicate::str::contains("RestrictAddressFamilies=none\n").count(1))
.stdout(predicate::str::contains("LockPersonality=true\n").count(1))
.stdout(predicate::str::contains("RestrictRealtime=true\n").count(1))
.stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1));
}

Expand Down Expand Up @@ -176,6 +180,7 @@ fn run_read_kallsyms() {
.stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1))
.stdout(predicate::str::contains("RestrictAddressFamilies=none\n").count(1))
.stdout(predicate::str::contains("LockPersonality=true\n").count(1))
.stdout(predicate::str::contains("RestrictRealtime=true\n").count(1))
.stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1));
}

Expand Down Expand Up @@ -209,13 +214,15 @@ fn run_ls_modules() {
.stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1))
.stdout(predicate::str::contains("RestrictAddressFamilies=none\n").count(1))
.stdout(predicate::str::contains("LockPersonality=true\n").count(1))
.stdout(predicate::str::contains("RestrictRealtime=true\n").count(1))
.stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1));
}

#[test]
#[cfg_attr(not(feature = "as-root"), ignore)]
fn run_dmesg() {
assert!(Uid::effective().is_root());

Command::cargo_bin(env!("CARGO_PKG_NAME"))
.unwrap()
.args(["run", "--", "dmesg"])
Expand All @@ -234,13 +241,15 @@ fn run_dmesg() {
.stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1))
.stdout(predicate::str::contains("RestrictAddressFamilies=none\n").count(1))
.stdout(predicate::str::contains("LockPersonality=true\n").count(1))
.stdout(predicate::str::contains("RestrictRealtime=true\n").count(1))
.stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1));
}

#[test]
#[cfg_attr(feature = "as-root", ignore)]
fn run_systemctl() {
assert!(!Uid::effective().is_root());

Command::cargo_bin(env!("CARGO_PKG_NAME"))
.unwrap()
.args(["run", "--", "systemctl", "--user"])
Expand All @@ -263,6 +272,7 @@ fn run_systemctl() {
.stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1))
.stdout(predicate::str::contains("RestrictAddressFamilies=AF_UNIX\n").count(1))
.stdout(predicate::str::contains("LockPersonality=true\n").count(1))
.stdout(predicate::str::contains("RestrictRealtime=true\n").count(1))
.stdout(predicates::boolean::OrPredicate::new(
predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1),
predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1),
Expand Down Expand Up @@ -299,6 +309,7 @@ fn run_ss() {
.stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1))
.stdout(predicate::str::contains("RestrictAddressFamilies=AF_NETLINK AF_UNIX\n").count(1))
.stdout(predicate::str::contains("LockPersonality=true\n").count(1))
.stdout(predicate::str::contains("RestrictRealtime=true\n").count(1))
.stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @signal:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1));
}

Expand Down Expand Up @@ -326,5 +337,87 @@ fn run_mmap_wx() {
.stdout(predicate::str::contains("MemoryDenyWriteExecute=\n").not())
.stdout(predicate::str::contains("RestrictAddressFamilies=none\n").count(1))
.stdout(predicate::str::contains("LockPersonality=true\n").count(1))
.stdout(predicate::str::contains("RestrictRealtime=true\n").count(1))
.stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1));

Command::cargo_bin(env!("CARGO_PKG_NAME"))
.unwrap()
.args(["run", "--", "python3", "-c", "import mmap, os, tempfile; f = tempfile.NamedTemporaryFile(\"wb\"); f.write(os.urandom(16)); f.flush(); mmap.mmap(f.file.fileno(), 0, prot=mmap.PROT_WRITE)"])
.unwrap()
.assert()
.success()
.stdout(predicate::str::contains("ProtectSystem=full\n").count(1))
.stdout(predicate::str::contains("ProtectHome=read-only\n").count(1))
.stdout(if env::current_exe().unwrap().starts_with("/tmp") {
predicate::str::contains("PrivateTmp=true\n").count(0)
} else {
predicate::str::contains("PrivateTmp=true\n").count(1)
})
.stdout(predicate::str::contains("PrivateDevices=true\n").count(1))
.stdout(predicate::str::contains("ProtectKernelTunables=true\n").count(1))
.stdout(predicate::str::contains("ProtectKernelModules=true\n").count(1))
.stdout(predicate::str::contains("ProtectKernelLogs=true\n").count(1))
.stdout(predicate::str::contains("ProtectControlGroups=true\n").count(1))
.stdout(predicate::str::contains("ProtectProc=ptraceable\n").count(1))
.stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1))
.stdout(predicate::str::contains("RestrictAddressFamilies=none\n").count(1))
.stdout(predicate::str::contains("LockPersonality=true\n").count(1))
.stdout(predicate::str::contains("RestrictRealtime=true\n").count(1))
.stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @resources:EPERM @sandbox:EPERM @setuid:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1));
}

#[test]
#[cfg_attr(not(feature = "as-root"), ignore)]
fn run_sched_realtime() {
assert!(Uid::effective().is_root());

Command::cargo_bin(env!("CARGO_PKG_NAME"))
.unwrap()
.args(["run", "--", "python3", "-c", "import os; os.sched_setscheduler(0, os.SCHED_RR, os.sched_param(os.sched_get_priority_min(os.SCHED_RR)))"])
.unwrap()
.assert()
.success()
.stdout(predicate::str::contains("ProtectSystem=strict\n").count(1))
.stdout(predicate::str::contains("ProtectHome=read-only\n").count(1))
.stdout(if !Uid::effective().is_root() && env::current_exe().unwrap().starts_with("/tmp") {
predicate::str::contains("PrivateTmp=true\n").count(0)
} else {
predicate::str::contains("PrivateTmp=true\n").count(1)
})
.stdout(predicate::str::contains("PrivateDevices=true\n").count(1))
.stdout(predicate::str::contains("ProtectKernelTunables=true\n").count(1))
.stdout(predicate::str::contains("ProtectKernelModules=true\n").count(1))
.stdout(predicate::str::contains("ProtectKernelLogs=true\n").count(1))
.stdout(predicate::str::contains("ProtectControlGroups=true\n").count(1))
.stdout(predicate::str::contains("ProtectProc=ptraceable\n").count(1))
.stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1))
.stdout(predicate::str::contains("RestrictAddressFamilies=none\n").count(1))
.stdout(predicate::str::contains("LockPersonality=true\n").count(1))
.stdout(predicate::str::contains("RestrictRealtime=").not())
.stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @sandbox:EPERM @setuid:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1));

Command::cargo_bin(env!("CARGO_PKG_NAME"))
.unwrap()
.args(["run", "--", "python3", "-c", "import os; os.sched_setscheduler(0, os.SCHED_IDLE, os.sched_param(0))"])
.unwrap()
.assert()
.success()
.stdout(predicate::str::contains("ProtectSystem=strict\n").count(1))
.stdout(predicate::str::contains("ProtectHome=read-only\n").count(1))
.stdout(if !Uid::effective().is_root() && env::current_exe().unwrap().starts_with("/tmp") {
predicate::str::contains("PrivateTmp=true\n").count(0)
} else {
predicate::str::contains("PrivateTmp=true\n").count(1)
})
.stdout(predicate::str::contains("PrivateDevices=true\n").count(1))
.stdout(predicate::str::contains("ProtectKernelTunables=true\n").count(1))
.stdout(predicate::str::contains("ProtectKernelModules=true\n").count(1))
.stdout(predicate::str::contains("ProtectKernelLogs=true\n").count(1))
.stdout(predicate::str::contains("ProtectControlGroups=true\n").count(1))
.stdout(predicate::str::contains("ProtectProc=ptraceable\n").count(1))
.stdout(predicate::str::contains("MemoryDenyWriteExecute=true\n").count(1))
.stdout(predicate::str::contains("RestrictAddressFamilies=none\n").count(1))
.stdout(predicate::str::contains("LockPersonality=true\n").count(1))
.stdout(predicate::str::contains("RestrictRealtime=true\n").count(1))
.stdout(predicate::str::contains("SystemCallFilter=~@aio:EPERM @chown:EPERM @clock:EPERM @cpu-emulation:EPERM @debug:EPERM @io-event:EPERM @ipc:EPERM @keyring:EPERM @memlock:EPERM @module:EPERM @mount:EPERM @network-io:EPERM @obsolete:EPERM @pkey:EPERM @privileged:EPERM @process:EPERM @raw-io:EPERM @reboot:EPERM @sandbox:EPERM @setuid:EPERM @swap:EPERM @sync:EPERM @timer:EPERM\n").count(1));
}

0 comments on commit 93e9efb

Please sign in to comment.