#[derive(Default, Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[derive(Deserialize, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GlobalSecurityConfiguration
{
pub harden: bool,
pub disable_namespaces: bool,
pub maximum_file_system_mounts: Option<u32>,
pub maximum_memory_maps_per_proces: Option<u32>,
pub harden_jit_ebpf: Option<JustInTimeCompilationHardening>,
pub disable_kexec_loading_of_new_kernel_images_until_reboot: bool,
pub disable_bpf_loading_of_programs_by_unprivileged_users_until_reboot: bool,
pub lock_down_state: LockDownState,
}
impl GlobalSecurityConfiguration
{
#[inline(always)]
pub fn configure(&self, sys_path: &SysPath, proc_path: &ProcPath) -> Result<(), GlobalSecurityConfigurationError>
{
use self::GlobalSecurityConfigurationError::*;
#[inline(always)]
fn harden_value_u8<'a>(proc_path: &ProcPath, file_function: impl FnOnce(&ProcPath, &str) -> PathBuf, file_name: &'static str, value: u8) -> Result<(), GlobalSecurityConfigurationError>
{
harden_value(proc_path, file_function, file_name, UnpaddedDecimalInteger(value))
}
#[inline(always)]
fn harden_value_i8<'a>(proc_path: &ProcPath, file_function: impl FnOnce(&ProcPath, &str) -> PathBuf, file_name: &'static str, value: i8) -> Result<(), GlobalSecurityConfigurationError>
{
harden_value(proc_path, file_function, file_name, UnpaddedDecimalInteger(value))
}
#[inline(always)]
fn harden_value_u32<'a>(proc_path: &ProcPath, file_function: impl FnOnce(&ProcPath, &str) -> PathBuf, file_name: &'static str, value: u32) -> Result<(), GlobalSecurityConfigurationError>
{
harden_value(proc_path, file_function, file_name, UnpaddedDecimalInteger(value))
}
#[inline(always)]
fn hardden_seccomp_logging(proc_path: &ProcPath) -> Result<(), GlobalSecurityConfigurationError>
{
let actions_available = proc_path.sys_kernel_seccomp_file_path("actions_avail").read_raw_without_line_feed().map_err(|cause| CouldNotHarden { cause, proc_sys_kernel_file: "actions_avail" })?;
let mut actions_to_log = Vec::with_capacity(actions_available.len());
for action in actions_available.split_bytes(b' ')
{
if action == b"allow"
{
continue
}
if likely!(!actions_to_log.is_empty())
{
actions_to_log.push(b' ');
}
actions_to_log.extend_from_slice(action)
}
actions_to_log.push(b'\n');
proc_path.sys_kernel_seccomp_file_path("actions_logged").write_value(actions_to_log).map_err(|cause| CouldNotHarden { cause, proc_sys_kernel_file: "actions_logged" })
}
#[inline(always)]
fn harden_value<'a>(proc_path: &ProcPath, file_function: impl FnOnce(&ProcPath, &str) -> PathBuf, file_name: &'static str, value: impl IntoLineFeedTerminatedByteString<'a>) -> Result<(), GlobalSecurityConfigurationError>
{
let file_path = file_function(proc_path, file_name);
if file_path.exists()
{
return file_path.write_value(value).map_err(|cause| CouldNotHarden { cause, proc_sys_kernel_file: file_name })
}
Ok(())
}
#[inline(always)]
fn set_sys_kernel_boolean_value_once(proc_path: &ProcPath, file_name: &str, value: bool, error: impl FnOnce(io::Error) -> GlobalSecurityConfigurationError) -> Result<(), GlobalSecurityConfigurationError>
{
set_value_once
(
proc_path,
|proc_path|
{
let file_path = proc_path.sys_kernel_file_path(file_name);
if file_path.exists()
{
let enabled: bool = file_path.read_zero_or_one_bool().unwrap();
if !enabled
{
return file_path.write_value(true)
}
}
Ok(())
},
value,
error
)
}
if self.harden
{
const RateLimit: u8 = 5;
const RateLimitBurst: u8 = 10;
harden_value(proc_path, ProcPath::sys_kernel_file_path, "core", b"core\n" as &[u8])?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "core_pipe_limit", 1)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "core_uses_pid", 0)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "dmesg_restrict", 1)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "ftrace_dump_on_oops", 0)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "kptr_restrict", 2)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "latencytop", 0)?;
harden_value_i8(proc_path, ProcPath::sys_kernel_file_path, "msg_next_id", -1)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "perf_event_max_stack", 127)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "perf_event_paranoid", 2)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "print-fatal-signals", 0)?;
harden_value(proc_path, ProcPath::sys_kernel_file_path, "printk", b"0 4 0 0\n" as &[u8])?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "printk_delay", 0)?;
harden_value(proc_path, ProcPath::sys_kernel_file_path, "printk_devkmsg", b"off\n" as &[u8])?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "printk_ratelimit", RateLimit)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "printk_ratelimit_burst", RateLimitBurst)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "randomize_va_space", 2)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "sched_child_runs_first", 0)?;
harden_value_i8(proc_path, ProcPath::sys_kernel_file_path, "sem_next_id", -1)?;
harden_value_i8(proc_path, ProcPath::sys_kernel_file_path, "shm_next_id", -1)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "shm_rmid_forced", 1)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "stack_erasing", 1)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "sysrq", 0)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "timer_migration", 1)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "traceoff_on_warning", 0)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_file_path, "tracepoint_printk", 0)?;
const ReadWakeUpThresholdInBitsOfEntry: u32 = 64;
const WriteWakeUpThresholdInBitsOfEntry: u32 = 896;
harden_value_u32(proc_path, ProcPath::sys_kernel_random_file_path, "read_wakeup_threshold", ReadWakeUpThresholdInBitsOfEntry)?;
harden_value_u8(proc_path, ProcPath::sys_kernel_random_file_path, "urandom_min_reseed_secs", 15)?;
harden_value_u32(proc_path, ProcPath::sys_kernel_random_file_path, "write_wakeup_threshold", WriteWakeUpThresholdInBitsOfEntry)?;
hardden_seccomp_logging(proc_path)?;
harden_value_u8(proc_path, ProcPath::sys_fs_file_path, "protected_fifos", 2)?;
harden_value_u8(proc_path, ProcPath::sys_fs_file_path, "protected_hardlinks", 1)?;
harden_value_u8(proc_path, ProcPath::sys_fs_file_path, "protected_regular", 2)?;
harden_value_u8(proc_path, ProcPath::sys_fs_file_path, "protected_symlinks", 1)?;
harden_value_u8(proc_path, ProcPath::sys_fs_file_path, "suid_dumpable", 0)?;
harden_value_u8(proc_path, ProcPath::sys_net_core_file_path, "bpf_jit_kallsyms", 0)?;
harden_value_u8(proc_path, ProcPath::sys_net_core_file_path, "message_burst", RateLimitBurst)?;
harden_value_u8(proc_path, ProcPath::sys_net_core_file_path, "message_cost", RateLimit)?;
harden_value_u8(proc_path, ProcPath::sys_net_core_file_path, "tstamp_allow_data", 0)?;
#[cfg(target_arch = "aarch64")] const CONFIG_ARCH_MMAP_RND_BITS_MAX: u8 = 19;
#[cfg(target_arch = "riscv64")] const CONFIG_ARCH_MMAP_RND_BITS_MAX: u8 = 24;
#[cfg(target_arch = "x86_64")] const CONFIG_ARCH_MMAP_RND_BITS_MAX: u8 = 32;
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] const CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX: u8 = 16;
harden_value_u8(proc_path, ProcPath::sys_vm_file_path, "block_dump", 0)?;
harden_value_u8(proc_path, ProcPath::sys_vm_file_path, "hugetlb_shm_group", 0)?;
harden_value_u8(proc_path, ProcPath::sys_vm_file_path, "laptop_mode", 0)?;
harden_value_u8(proc_path, ProcPath::sys_vm_file_path, "legacy_va_layout", 0)?;
harden_value_u32(proc_path, ProcPath::sys_vm_file_path, "mmap_min_addr", 65536)?;
harden_value_u8(proc_path, ProcPath::sys_vm_file_path, "mmap_rnd_bits", CONFIG_ARCH_MMAP_RND_BITS_MAX)?;
harden_value_u8(proc_path, ProcPath::sys_vm_file_path, "mmap_rnd_compat_bits", CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX)?;
harden_value_u8(proc_path, ProcPath::sys_vm_file_path, "oom_dump_tasks", 0)?;
harden_value_u8(proc_path, ProcPath::sys_vm_file_path, "unprivileged_userfaultfd", 0)?;
harden_value_u8(proc_path, ProcPath::sys_vm_file_path, "vfs_cache_pressure", 100)?;
harden_value_u8(proc_path, ProcPath::sys_abi_file_path, "vsyscall32", 0)?;
harden_value_u8(proc_path, ProcPath::sys_debug_file_path, "exception-trace", 1)?;
harden_value_u8(proc_path, ProcPath::sys_debug_file_path, "kprobes-optimization", 1)?;
harden_value_u8(proc_path, ProcPath::sys_dev_scsi_file_path, "logging_level", 0)?;
ProcessIdentifier::set_maximum_value_to_maximum(proc_path).map_err(CouldNotSetMaximumProcessIdentifiersToMaximum)?;
}
if self.disable_namespaces
{
harden_value_u8(proc_path, ProcPath::sys_user_file_path, "max_cgroup_namespaces", 0)?;
harden_value_u8(proc_path, ProcPath::sys_user_file_path, "max_ipc_namespaces", 0)?;
harden_value_u8(proc_path, ProcPath::sys_user_file_path, "max_mnt_namespaces", 0)?;
harden_value_u8(proc_path, ProcPath::sys_user_file_path, "max_net_namespaces", 0)?;
harden_value_u8(proc_path, ProcPath::sys_user_file_path, "max_pid_namespaces", 0)?;
harden_value_u8(proc_path, ProcPath::sys_user_file_path, "max_user_namespaces", 0)?;
harden_value_u8(proc_path, ProcPath::sys_user_file_path, "max_uts_namespaces", 0)?;
}
set_proc_sys_fs_value(proc_path, "mount-max", self.maximum_file_system_mounts.map(UnpaddedDecimalInteger), CouldNotSetMaximumNumberOfFileSystemMounts)?;
set_proc_sys_vm_value(proc_path, "max_map_count", self.maximum_memory_maps_per_proces.map(UnpaddedDecimalInteger), CouldNotSetMaximumNumberOfMemoryMapsPerProcess)?;
set_value(proc_path, |proc_path, value| value.set_value(proc_path), self.harden_jit_ebpf, CouldNotHardenJitOfBpfPrograms)?;
set_sys_kernel_boolean_value_once(proc_path, "kexec_load_disabled", self.disable_kexec_loading_of_new_kernel_images_until_reboot, CouldNotDisableKexecLoadingUntilNextReboot)?;
set_sys_kernel_boolean_value_once(proc_path, "unprivileged_bpf_disabled", self.disable_bpf_loading_of_programs_by_unprivileged_users_until_reboot, CouldNotDisableKexecLoadingUntilNextReboot)?;
self.lock_down_state.set(sys_path).map_err(CouldNotChangeLockDownState)?;
Ok(())
}
}