secmem_proc/config.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
//! Module containing hardening configuration.
use crate::components;
use crate::error::Result;
/// Configuration for the hardening procedure. The configuration allows to
/// enable or disable certain features, such as filesystem access (e.g. for
/// procfs), anti-tracing methods and to use a custom DACL on windows.
pub struct Config {
anti_tracing: bool,
fs: Fs,
unstable: Unstable,
win_dacl: WinDacl,
}
impl Default for Config {
fn default() -> Self {
Self::DEFAULT
}
}
impl Config {
/// Default configuration.
pub const DEFAULT: Self = Self {
anti_tracing: true,
fs: Fs::DEFAULT,
unstable: Unstable::DEFAULT,
win_dacl: WinDacl::DEFAULT,
};
/// Create new default configuration, with anti-tracing set to
/// `anti_tracing`.
#[must_use]
pub const fn new_with_anti_tracing(anti_tracing: bool) -> Self {
Self {
anti_tracing,
fs: Fs::DEFAULT,
unstable: Unstable::DEFAULT,
win_dacl: WinDacl::DEFAULT,
}
}
/// Set anti-tracing to `b` (true means enabled).
pub fn set_anti_tracing(&mut self, b: bool) {
self.anti_tracing = b;
}
/// Set filesystem access to `b` (true means enabled).
pub fn set_fs(&mut self, b: bool) {
self.fs = match b {
true => Fs::TRUE,
false => Fs::FALSE,
};
}
/// Get mutable reference to filesystem access configuration, allowing to
/// modify it.
pub fn fs_mut(&mut self) -> &mut Fs {
&mut self.fs
}
/// Set procfs access to `b` (true means enabled).
pub fn set_fs_procfs(&mut self, b: bool) {
self.fs.set_procfs(b);
}
/// Set unstable hardening methods to `b` (true means enabled).
///
/// Default is disabled (false). Note that the `unstable` crate feature is
/// required for this configuration to have any effect. Without that crate
/// feature, the value of this configuration is silently ignored, and
/// unstable hardening is not performed.
pub fn set_unstable(&mut self, b: bool) {
self.unstable = match b {
true => Unstable::TRUE,
false => Unstable::FALSE,
};
}
/// Get mutable reference to unstable hardening configuration, allowing to
/// modify it.
pub fn unstable_mut(&mut self) -> &mut Unstable {
&mut self.unstable
}
/// Set use of unstable windows native API to `b` (true means enabled).
///
/// Default is disabled (false). Note that the `unstable` crate feature is
/// required for this configuration to have any effect. Without that crate
/// feature, the value of this configuration is silently ignored, and
/// unstable hardening is not performed.
pub fn set_unstable_win_ntapi(&mut self, b: bool) {
self.unstable.set_win_ntapi(b);
}
/// Set use of unstable windows hardening relying on shared kernel memory to
/// `b` (true means enabled).
///
/// Default is disabled (false). Note that the `unstable` crate feature is
/// required for this configuration to have any effect. Without that crate
/// feature, the value of this configuration is silently ignored, and
/// unstable hardening is not performed.
pub fn set_unstable_win_kernelmem(&mut self, b: bool) {
self.unstable.set_win_kernelmem(b);
}
/// Configure a custom windows DACL `dacl` (for the process).
pub fn set_win_dacl(&mut self, dacl: WinDacl) {
self.win_dacl = dacl;
}
/// Configure the windows DAC (for the process)L as the default.
pub fn set_win_dacl_default(&mut self) {
self.set_win_dacl(WinDacl::Default);
}
/// Configure the windows DACL (for the process) as an empty DACL. This
/// means giving no access to any user at all. This is extremely strict. Use
/// with caution.
pub fn set_win_dacl_empty(&mut self) {
self.set_win_dacl(WinDacl::Empty);
}
/// Configure the windows DACL (for the process) as a DACL which gives
/// precisely the accesses specified by `access` to the current user, and no
/// access to any other user.
pub fn set_win_dacl_custom_user_perm(&mut self, access: WinDaclProcessAccess) {
self.set_win_dacl(WinDacl::CustomUserPerm(access));
}
/// Configure to, instead of setting a DACL (for the process) on windows,
/// call the function `fnptr`. This callback function `fnptr` can then be
/// used to set a custom DACL yourself, using the API in
/// [`crate::win_acl`].
pub fn set_win_dacl_custom_fn(&mut self, fnptr: fn() -> Result) {
self.set_win_dacl(WinDacl::CustomFn(fnptr));
}
/// Use the configuration `self` to harden the current process.
pub fn harden_process(self) -> Result {
// hide from debugger
{
if self.unstable.has_win_ntapi() {
#[cfg(all(windows, feature = "unstable"))]
components::hide_thread_from_debugger_ntapi()?;
}
}
// disable debugger attaching; set up memory security; don't dump
{
#[cfg(windows)]
self.win_dacl.call()?;
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
components::disable_tracing_prctl()?;
#[cfg(unix)]
components::disable_core_dumps_rlimit()?;
}
// anti-tracing
if self.anti_tracing {
#[cfg(windows)]
components::check_tracer_winapi()?;
if self.unstable.has_win_kernelmem() {
#[cfg(all(windows, feature = "unstable"))]
components::check_tracer_unstable()?;
}
if self.fs.has_procfs() {
#[cfg(all(target_os = "linux", feature = "std"))]
components::check_tracer_procfs()?;
}
#[cfg(target_os = "freebsd")]
components::check_tracer_prctl()?;
}
Ok(())
}
}
/// Filesystem access configuration.
#[derive(Clone, Debug, PartialEq)]
pub struct Fs {
procfs: bool,
}
impl Default for Fs {
fn default() -> Self {
Self::DEFAULT
}
}
impl Fs {
/// Default filesystem configuration.
pub const DEFAULT: Self = Self::TRUE;
/// Disable all filesystem access.
pub const FALSE: Self = Self { procfs: false };
/// Enable all filesystem access.
pub const TRUE: Self = Self { procfs: true };
/// Set procfs access to `b` (true means enabled).
pub fn set_procfs(&mut self, b: bool) {
self.procfs = b;
}
/// Return whether procfs access is enabled.
const fn has_procfs(&self) -> bool {
self.procfs
}
}
/// Structure for configuring hardening methods which rely on undocumented or
/// unstable target OS/platform details.
///
/// The default is to disable all. Note that the `unstable` crate feature is
/// required for this configuration to have any effect. Without that crate
/// feature, the value of this configuration is silently ignored.
#[derive(Clone, Debug, PartialEq)]
pub struct Unstable {
/// Windows native API.
win_ntapi: bool,
/// Windows shared kernel memory.
win_kernelmem: bool,
}
impl Default for Unstable {
fn default() -> Self {
Self::DEFAULT
}
}
impl Unstable {
/// Default unstable configuration. This is the same as [`Self::FALSE`].
pub const DEFAULT: Self = Self::FALSE;
/// Disable (all) unstable hardening methods.
pub const FALSE: Self = Self {
win_ntapi: false,
win_kernelmem: false,
};
/// Enable all unstable hardening methods.
pub const TRUE: Self = Self {
win_ntapi: true,
win_kernelmem: true,
};
/// Set unstable windows native API hardening methods to `b` (true means
/// enabled).
pub fn set_win_ntapi(&mut self, b: bool) {
self.win_ntapi = b;
}
/// Set unstable windows hardening methods relying on shared kernel memory
/// to `b` (true means enabled).
pub fn set_win_kernelmem(&mut self, b: bool) {
self.win_kernelmem = b;
}
/// Return whether windows native API methods are enabled.
const fn has_win_ntapi(&self) -> bool {
self.win_ntapi
}
/// Return whether shared kernel memory methods are enabled.
const fn has_win_kernelmem(&self) -> bool {
self.win_kernelmem
}
}
/// Custom windows DACL configuration.
pub enum WinDacl {
/// The empty DACL. This means giving no access to any user at all. This is
/// extremely strict. Use with caution.
Empty,
/// The default DACL.
Default,
/// A DACL which gives precisely the accesses specified in the first tuple
/// position to the current user, and no access to any other user.
CustomUserPerm(WinDaclProcessAccess),
/// Don't set a DACL at all.
False,
/// Instead of setting a DACL, call the function in the first tuple
/// position. This callback function can then be used to set a custom DACL
/// yourself, using the API in [`crate::win_acl`].
CustomFn(fn() -> Result),
}
/// Cross-platform type for windows process access masks, used for setting a
/// process DACL on windows.
///
/// Accesses can be added by bit-or-ing them together.
///
/// The type and all associated constants are available on all platforms, but
/// only meaningful on windows.
#[derive(Debug, Clone, Copy)]
pub struct WinDaclProcessAccess(u32);
impl core::ops::BitOr for WinDaclProcessAccess {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl core::ops::BitAnd for WinDaclProcessAccess {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
Self(self.0 & rhs.0)
}
}
impl core::ops::BitOrAssign for WinDaclProcessAccess {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
impl core::ops::BitAndAssign for WinDaclProcessAccess {
fn bitand_assign(&mut self, rhs: Self) {
self.0 &= rhs.0;
}
}
#[cfg(windows)]
impl From<WinDaclProcessAccess> for windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS {
fn from(access: WinDaclProcessAccess) -> Self {
Self(access.0)
}
}
#[cfg(windows)]
impl From<&WinDaclProcessAccess> for windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS {
fn from(access: &WinDaclProcessAccess) -> Self {
Self(access.0)
}
}
#[cfg(windows)]
impl WinDaclProcessAccess {
pub const ALL_ACCESS: Self = Self::new(windows::Win32::System::Threading::PROCESS_ALL_ACCESS);
pub const CREATE_PROCESS: Self =
Self::new(windows::Win32::System::Threading::PROCESS_CREATE_PROCESS);
pub const CREATE_THREAD: Self =
Self::new(windows::Win32::System::Threading::PROCESS_CREATE_THREAD);
pub const DELETE: Self = Self::new(windows::Win32::System::Threading::PROCESS_DELETE);
pub const DUP_HANDLE: Self = Self::new(windows::Win32::System::Threading::PROCESS_DUP_HANDLE);
pub const QUERY_INFORMATION: Self =
Self::new(windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION);
pub const QUERY_LIMITED_INFORMATION: Self =
Self::new(windows::Win32::System::Threading::PROCESS_QUERY_LIMITED_INFORMATION);
pub const READ_CONTROL: Self =
Self::new(windows::Win32::System::Threading::PROCESS_READ_CONTROL);
pub const SET_INFORMATION: Self =
Self::new(windows::Win32::System::Threading::PROCESS_SET_INFORMATION);
pub const SET_QUOTA: Self = Self::new(windows::Win32::System::Threading::PROCESS_SET_QUOTA);
pub const SUSPEND_RESUME: Self =
Self::new(windows::Win32::System::Threading::PROCESS_SUSPEND_RESUME);
pub const SYNCHRONIZE: Self = Self::new(windows::Win32::System::Threading::PROCESS_SYNCHRONIZE);
pub const TERMINATE: Self = Self::new(windows::Win32::System::Threading::PROCESS_TERMINATE);
pub const VM_OPERATION: Self =
Self::new(windows::Win32::System::Threading::PROCESS_VM_OPERATION);
pub const VM_READ: Self = Self::new(windows::Win32::System::Threading::PROCESS_VM_READ);
pub const VM_WRITE: Self = Self::new(windows::Win32::System::Threading::PROCESS_VM_WRITE);
pub const WRITE_DAC: Self = Self::new(windows::Win32::System::Threading::PROCESS_WRITE_DAC);
pub const WRITE_OWNER: Self = Self::new(windows::Win32::System::Threading::PROCESS_WRITE_OWNER);
const fn new(access: windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS) -> Self {
Self(access.0)
}
}
// on non-windows targets just a bunch of dummy constants
#[cfg(not(windows))]
impl WinDaclProcessAccess {
pub const ALL_ACCESS: Self = Self(0);
pub const CREATE_PROCESS: Self = Self(0);
pub const CREATE_THREAD: Self = Self(0);
pub const DELETE: Self = Self(0);
pub const DUP_HANDLE: Self = Self(0);
pub const QUERY_INFORMATION: Self = Self(0);
pub const QUERY_LIMITED_INFORMATION: Self = Self(0);
pub const READ_CONTROL: Self = Self(0);
pub const SET_INFORMATION: Self = Self(0);
pub const SET_QUOTA: Self = Self(0);
pub const SUSPEND_RESUME: Self = Self(0);
pub const SYNCHRONIZE: Self = Self(0);
pub const TERMINATE: Self = Self(0);
pub const VM_OPERATION: Self = Self(0);
pub const VM_READ: Self = Self(0);
pub const VM_WRITE: Self = Self(0);
pub const WRITE_DAC: Self = Self(0);
pub const WRITE_OWNER: Self = Self(0);
}
impl Default for WinDacl {
fn default() -> Self {
Self::DEFAULT
}
}
impl WinDacl {
/// Default DACL.
pub const DEFAULT: Self = Self::Default;
/// Set the DACL configured in `self`. Most users probably want to set this
/// DACL configuration in a [`Config`] using [`Config::set_win_dacl`], and
/// then harden the process using that configuration
/// ([`Config::harden_process`]) instead.
pub fn call(&self) -> Result {
#[cfg(windows)]
return match self {
Self::Empty => components::set_empty_dacl_winapi(),
Self::Default => components::set_default_dacl_winapi(),
Self::CustomUserPerm(access) => components::set_custom_dacl_winapi(access.into()),
Self::False => Ok(()),
Self::CustomFn(f) => f(),
};
#[cfg(not(windows))]
Ok(())
}
}