Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 47 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions ntpd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ serde.workspace = true
serde_json.workspace = true

rustls23.workspace = true
libseccomp = "0.4.0"
capctl = "0.2.4"

[dev-dependencies]
ntp-proto = { workspace = true, features = ["__internal-test",] }
Expand Down
30 changes: 30 additions & 0 deletions ntpd/src/ctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{path::PathBuf, process::ExitCode};
use crate::{
daemon::{Config, ObservableState, config::CliArg, tracing::LogLevel},
force_sync,
security::{seccomp_init, drop_caps},
};
use tokio::runtime::Builder;
use tracing_subscriber::util::SubscriberInitExt;
Expand Down Expand Up @@ -169,6 +170,35 @@ fn validate(config: Option<PathBuf>) -> std::io::Result<ExitCode> {
const VERSION: &str = env!("CARGO_PKG_VERSION");

pub fn main() -> std::io::Result<ExitCode> {

// Drop capabilities
drop_caps(None);

// Allowed syscalls
let syscalls = vec![
"clock_adjtime",
"clock_nanosleep",
"clone3",
"dup",
"exit_group",
"fchownat",
"futex",
"getdents64",
"getsockname",
"getsockopt",
"madvise",
"newfstatat",
"open_by_handle_at",
"prctl",
"rseq",
"recvmsg",
"sendmmsg",
"time",
"uname",
"writev",
];
seccomp_init(syscalls);

let options = match NtpCtlOptions::try_parse_from(std::env::args()) {
Ok(options) => options,
Err(msg) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, msg)),
Expand Down
34 changes: 34 additions & 0 deletions ntpd/src/daemon/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub use observer::ObservableState;
pub use system::spawn;
use tokio::runtime::Builder;
use tracing_subscriber::util::SubscriberInitExt;
use crate::security::{seccomp_init, drop_caps};
use capctl::Cap;

use config::NtpDaemonOptions;

Expand All @@ -32,6 +34,38 @@ use self::tracing::LogLevel;
const VERSION: &str = env!("CARGO_PKG_VERSION");

pub fn main() -> Result<(), Box<dyn Error>> {

// Drop capablities
drop_caps(Some(&[
Cap::NET_BIND_SERVICE,
Cap::SYS_TIME,
]));

// Allowed syscalls
let syscalls = vec![
"chmod",
"clock_adjtime",
"clone3",
"clone",
"futex",
"getdents64",
"getsockname",
"getsockopt",
"madvise",
"newfstatat",
"recvmsg",
"rseq",
"unlink",
"writev",
"prctl",
"clock_nanosleep",
"exit_group",
"uname",
"sendmmsg",
"exit"
];
seccomp_init(syscalls);

let options = NtpDaemonOptions::try_parse_from(std::env::args())?;

match options.action {
Expand Down
1 change: 1 addition & 0 deletions ntpd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

mod ctl;
mod daemon;
mod security;
mod force_sync;
mod metrics;

Expand Down
12 changes: 11 additions & 1 deletion ntpd/src/metrics/exporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use std::{
sync::Arc,
};

use crate::daemon::{ObservableState, config::CliArg, initialize_logging_parse_config};
use crate::{
daemon::{ObservableState, config::CliArg, initialize_logging_parse_config},
security::{seccomp_init, drop_caps},
};

const VERSION: &str = env!("CARGO_PKG_VERSION");

Expand Down Expand Up @@ -111,6 +114,13 @@ impl NtpMetricsExporterOptions {
}

pub fn main() -> Result<(), Box<dyn std::error::Error>> {

// Drop capabilities
drop_caps(None);

// Initialize seccomp
seccomp_init(vec!["accept4"]);

let options = NtpMetricsExporterOptions::try_parse_from(std::env::args())?;
match options.action {
MetricsAction::Help => {
Expand Down
100 changes: 100 additions & 0 deletions ntpd/src/security.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use std::process;
use libseccomp::{ScmpAction, ScmpFilterContext, ScmpSyscall};
use capctl::{Cap, CapSet, CapState, prctl};

// Shared syscalls between all three user space tools.
const SHARED_SYSCALLS: [&str; 39] = [
"access",
"arch_prctl",
"bind",
"brk",
"clock_gettime",
"close",
"connect",
"epoll_create1",
"epoll_ctl",
"epoll_wait",
"eventfd2",
"execve",
"fcntl",
"fstat",
"getrandom",
"listen",
"lseek",
"mmap",
"mprotect",
"munmap",
"openat",
"poll",
"pread64",
"prlimit64",
"rseq",
"read",
"recvfrom",
"rt_sigaction",
"rt_sigprocmask",
"sched_getaffinity",
"sendto",
"set_robust_list",
"set_tid_address",
"setsockopt",
"sigaltstack",
"socket",
"statx",
"write",
"ioctl",
];

pub(crate) fn seccomp_init(syscalls: Vec<&str>) {
// Initialize the filter setting the default action to KillProcess. If a bad syscall Is made the process Is terminated by SIGSYS.
let mut ctx = match ScmpFilterContext::new(ScmpAction::KillProcess) {
Ok(c) => c,
Err(e) => {
eprintln!("ERROR: Seccomp context creation failed: {}", e);
process::exit(1);
}
};
let c_syscalls = [&SHARED_SYSCALLS[..], &syscalls[..]].concat();

for name in c_syscalls {
if let Err(e) = ctx.add_rule(ScmpAction::Allow, match ScmpSyscall::from_name(name) {
Ok(k) => k,
Err(e) => {
eprintln!("ERROR: Invalid syscall name {}: {}", name, e);
process::exit(1);
}
}) {
eprintln!("ERROR: Failed to add rule for {}: {}", name, e);
process::exit(1);
}
}
if let Err(e) = ctx.load() {
eprintln!("ERROR: Seccomp load failed: {}", e);
process::exit(1);
}
}

pub(crate) fn drop_caps(needed: Option<&[Cap]>) {
let capset = match needed {
Some(needed_caps) => CapSet::from_iter(needed_caps.iter().cloned()),
None => CapSet::empty(),
};
let mut current = match CapState::get_current() {
Ok(k) => k,
Err(e) => {
eprintln!("ERROR: Failed to get current capabilities: {:?}", e);
process::exit(1);
}
};
current.permitted = capset.clone();
current.inheritable = capset.clone();
current.effective = capset.clone();
if let Err(e) = current.set_current() {
eprintln!("ERROR: Failed to set current capabilities: {:?}", e);
process::exit(1);
}
if let Err(e) = prctl::set_no_new_privs() {
eprintln!("ERROR: Failed to enable no-new-privileges flag on current thread: {:?}", e);
process::exit(1);
}
}
Loading