diff --git a/Cargo.lock b/Cargo.lock index 54f79f37af..c9f3f299cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -463,6 +463,7 @@ dependencies = [ "remain", "resources", "rutabaga_gfx", + "sandbox", "scudo", "serde", "serde_json", @@ -1597,6 +1598,17 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +[[package]] +name = "sandbox" +version = "0.1.0" +dependencies = [ + "anyhow", + "base", + "prebuilts", + "win_util", + "winapi", +] + [[package]] name = "scudo" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 21b8fb53db..cf59be4bc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,6 +104,7 @@ exclude = [ "common/io_uring", "common/p9", "common/sync", + "sandbox", "tube_transporter", "win_util", "tools/examples/baremetal", @@ -249,6 +250,7 @@ ctrlc = "*" futures = "0.3" gpu_display = { path = "gpu_display", optional = true } rand = "0.8" +sandbox = { path = "sandbox" } tracing = { path = "tracing" } tube_transporter = { path = "tube_transporter" } winapi = "*" diff --git a/sandbox/Cargo.toml b/sandbox/Cargo.toml new file mode 100644 index 0000000000..c31ee1b4e3 --- /dev/null +++ b/sandbox/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "sandbox" +version = "0.1.0" +authors = ["The Chromium OS Authors"] +edition = "2021" + +[dependencies] +base = { path = "../base" } +win_util = { path = "../win_util"} +winapi = { version = "*", features = ["everything", "std", "impl-default"] } + +[build-dependencies] +anyhow = "*" +prebuilts = { path = "../prebuilts" } diff --git a/sandbox/bindings.rs b/sandbox/bindings.rs new file mode 100644 index 0000000000..9b06170a6d --- /dev/null +++ b/sandbox/bindings.rs @@ -0,0 +1,448 @@ +// Copyright 2022 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// If this file changes, update this file upstream and update windows pre-built libraries +// that upstream uses. +// +// TODO(b/239836957): Add how to generate and update pre-built library. + +#![allow(deref_nullptr)] + +/* automatically generated by rust-bindgen 0.56.0 */ + +pub const JOB_OBJECT_UILIMIT_NONE: u32 = 0; +pub const JOB_OBJECT_UILIMIT_HANDLES: u32 = 1; +pub const JOB_OBJECT_UILIMIT_READCLIPBOARD: u32 = 2; +pub const JOB_OBJECT_UILIMIT_WRITECLIPBOARD: u32 = 4; +pub const JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS: u32 = 8; +pub const JOB_OBJECT_UILIMIT_DISPLAYSETTINGS: u32 = 16; +pub const JOB_OBJECT_UILIMIT_GLOBALATOMS: u32 = 32; +pub const JOB_OBJECT_UILIMIT_DESKTOP: u32 = 64; +pub const JOB_OBJECT_UILIMIT_EXITWINDOWS: u32 = 128; +pub const JOB_OBJECT_UILIMIT_ALL: u32 = 255; +pub type size_t = ::std::os::raw::c_ulonglong; +pub type wchar_t = ::std::os::raw::c_ushort; +pub type DWORD = ::std::os::raw::c_ulong; +pub type HANDLE = *mut ::std::os::raw::c_void; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _PROCESS_INFORMATION { + pub hProcess: HANDLE, + pub hThread: HANDLE, + pub dwProcessId: DWORD, + pub dwThreadId: DWORD, +} +#[test] +fn bindgen_test_layout__PROCESS_INFORMATION() { + assert_eq!( + ::std::mem::size_of::<_PROCESS_INFORMATION>(), + 24usize, + concat!("Size of: ", stringify!(_PROCESS_INFORMATION)) + ); + assert_eq!( + ::std::mem::align_of::<_PROCESS_INFORMATION>(), + 8usize, + concat!("Alignment of ", stringify!(_PROCESS_INFORMATION)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_PROCESS_INFORMATION>())).hProcess as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_PROCESS_INFORMATION), + "::", + stringify!(hProcess) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_PROCESS_INFORMATION>())).hThread as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(_PROCESS_INFORMATION), + "::", + stringify!(hThread) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<_PROCESS_INFORMATION>())).dwProcessId as *const _ as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(_PROCESS_INFORMATION), + "::", + stringify!(dwProcessId) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_PROCESS_INFORMATION>())).dwThreadId as *const _ as usize }, + 20usize, + concat!( + "Offset of field: ", + stringify!(_PROCESS_INFORMATION), + "::", + stringify!(dwThreadId) + ) + ); +} +pub type PROCESS_INFORMATION = _PROCESS_INFORMATION; +#[repr(i32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum ResultCode { + SBOX_ALL_OK = 0, + SBOX_ERROR_GENERIC = 1, + SBOX_ERROR_BAD_PARAMS = 2, + SBOX_ERROR_UNSUPPORTED = 3, + SBOX_ERROR_NO_SPACE = 4, + SBOX_ERROR_INVALID_IPC = 5, + SBOX_ERROR_FAILED_IPC = 6, + SBOX_ERROR_NO_HANDLE = 7, + SBOX_ERROR_UNEXPECTED_CALL = 8, + SBOX_ERROR_WAIT_ALREADY_CALLED = 9, + SBOX_ERROR_CHANNEL_ERROR = 10, + SBOX_ERROR_CANNOT_CREATE_DESKTOP = 11, + SBOX_ERROR_CANNOT_CREATE_WINSTATION = 12, + SBOX_ERROR_FAILED_TO_SWITCH_BACK_WINSTATION = 13, + SBOX_ERROR_INVALID_APP_CONTAINER = 14, + SBOX_ERROR_INVALID_CAPABILITY = 15, + SBOX_ERROR_CANNOT_INIT_APPCONTAINER = 16, + SBOX_ERROR_PROC_THREAD_ATTRIBUTES = 17, + SBOX_ERROR_CREATE_PROCESS = 18, + SBOX_ERROR_DELEGATE_PRE_SPAWN = 19, + SBOX_ERROR_ASSIGN_PROCESS_TO_JOB_OBJECT = 20, + SBOX_ERROR_SET_THREAD_TOKEN = 21, + SBOX_ERROR_GET_THREAD_CONTEXT = 22, + SBOX_ERROR_DUPLICATE_TARGET_INFO = 23, + SBOX_ERROR_SET_LOW_BOX_TOKEN = 24, + SBOX_ERROR_CREATE_FILE_MAPPING = 25, + SBOX_ERROR_DUPLICATE_SHARED_SECTION = 26, + SBOX_ERROR_MAP_VIEW_OF_SHARED_SECTION = 27, + SBOX_ERROR_APPLY_ASLR_MITIGATIONS = 28, + SBOX_ERROR_SETUP_BASIC_INTERCEPTIONS = 29, + SBOX_ERROR_SETUP_INTERCEPTION_SERVICE = 30, + SBOX_ERROR_INITIALIZE_INTERCEPTIONS = 31, + SBOX_ERROR_SETUP_NTDLL_IMPORTS = 32, + SBOX_ERROR_SETUP_HANDLE_CLOSER = 33, + SBOX_ERROR_CANNOT_GET_WINSTATION = 34, + SBOX_ERROR_CANNOT_QUERY_WINSTATION_SECURITY = 35, + SBOX_ERROR_CANNOT_GET_DESKTOP = 36, + SBOX_ERROR_CANNOT_QUERY_DESKTOP_SECURITY = 37, + SBOX_ERROR_CANNOT_SETUP_INTERCEPTION_CONFIG_BUFFER = 38, + SBOX_ERROR_CANNOT_COPY_DATA_TO_CHILD = 39, + SBOX_ERROR_CANNOT_SETUP_INTERCEPTION_THUNK = 40, + SBOX_ERROR_CANNOT_RESOLVE_INTERCEPTION_THUNK = 41, + SBOX_ERROR_CANNOT_WRITE_INTERCEPTION_THUNK = 42, + SBOX_ERROR_CANNOT_FIND_BASE_ADDRESS = 43, + SBOX_ERROR_CREATE_APPCONTAINER = 44, + SBOX_ERROR_CREATE_APPCONTAINER_ACCESS_CHECK = 45, + SBOX_ERROR_CREATE_APPCONTAINER_CAPABILITY = 46, + SBOX_ERROR_CANNOT_INIT_JOB = 47, + SBOX_ERROR_INVALID_LOWBOX_SID = 48, + SBOX_ERROR_CANNOT_CREATE_RESTRICTED_TOKEN = 49, + SBOX_ERROR_CANNOT_SET_DESKTOP_INTEGRITY = 50, + SBOX_ERROR_CANNOT_CREATE_LOWBOX_TOKEN = 51, + SBOX_ERROR_CANNOT_MODIFY_LOWBOX_TOKEN_DACL = 52, + SBOX_ERROR_CANNOT_CREATE_RESTRICTED_IMP_TOKEN = 53, + SBOX_ERROR_CANNOT_DUPLICATE_PROCESS_HANDLE = 54, + SBOX_ERROR_CANNOT_LOADLIBRARY_EXECUTABLE = 55, + SBOX_ERROR_CANNOT_FIND_VARIABLE_ADDRESS = 56, + SBOX_ERROR_CANNOT_WRITE_VARIABLE_VALUE = 57, + SBOX_ERROR_INVALID_WRITE_VARIABLE_SIZE = 58, + SBOX_ERROR_CANNOT_INIT_BROKERSERVICES = 59, + SBOX_ERROR_CANNOT_UPDATE_JOB_PROCESS_LIMIT = 60, + SBOX_ERROR_CANNOT_CREATE_LOWBOX_IMPERSONATION_TOKEN = 61, + SBOX_ERROR_UNSANDBOXED_PROCESS = 62, + SBOX_ERROR_LAST = 63, +} +#[repr(i32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum IntegrityLevel { + INTEGRITY_LEVEL_SYSTEM = 0, + INTEGRITY_LEVEL_HIGH = 1, + INTEGRITY_LEVEL_MEDIUM = 2, + INTEGRITY_LEVEL_MEDIUM_LOW = 3, + INTEGRITY_LEVEL_LOW = 4, + INTEGRITY_LEVEL_BELOW_LOW = 5, + INTEGRITY_LEVEL_UNTRUSTED = 6, + INTEGRITY_LEVEL_LAST = 7, +} +#[repr(i32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum TokenLevel { + USER_LOCKDOWN = 0, + USER_RESTRICTED = 1, + USER_LIMITED = 2, + USER_INTERACTIVE = 3, + USER_RESTRICTED_NON_ADMIN = 4, + USER_NON_ADMIN = 5, + USER_RESTRICTED_SAME_ACCESS = 6, + USER_UNPROTECTED = 7, + USER_LAST = 8, +} +#[repr(i32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum JobLevel { + JOB_LOCKDOWN = 0, + JOB_RESTRICTED = 1, + JOB_LIMITED_USER = 2, + JOB_INTERACTIVE = 3, + JOB_UNPROTECTED = 4, + JOB_NONE = 5, +} +pub type MitigationFlags = u64; +pub const MITIGATION_DEP: MitigationFlags = 1; +pub const MITIGATION_DEP_NO_ATL_THUNK: MitigationFlags = 2; +pub const MITIGATION_SEHOP: MitigationFlags = 4; +pub const MITIGATION_RELOCATE_IMAGE: MitigationFlags = 8; +pub const MITIGATION_RELOCATE_IMAGE_REQUIRED: MitigationFlags = 16; +pub const MITIGATION_HEAP_TERMINATE: MitigationFlags = 32; +pub const MITIGATION_BOTTOM_UP_ASLR: MitigationFlags = 64; +pub const MITIGATION_HIGH_ENTROPY_ASLR: MitigationFlags = 128; +pub const MITIGATION_STRICT_HANDLE_CHECKS: MitigationFlags = 256; +pub const MITIGATION_DLL_SEARCH_ORDER: MitigationFlags = 512; +pub const MITIGATION_HARDEN_TOKEN_IL_POLICY: MitigationFlags = 1024; +pub const MITIGATION_WIN32K_DISABLE: MitigationFlags = 2048; +pub const MITIGATION_EXTENSION_POINT_DISABLE: MitigationFlags = 4096; +pub const MITIGATION_DYNAMIC_CODE_DISABLE: MitigationFlags = 8192; +pub const MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT: MitigationFlags = 16384; +pub const MITIGATION_DYNAMIC_CODE_OPT_OUT_THIS_THREAD: MitigationFlags = 32768; +pub const MITIGATION_NONSYSTEM_FONT_DISABLE: MitigationFlags = 65536; +pub const MITIGATION_FORCE_MS_SIGNED_BINS: MitigationFlags = 131072; +pub const MITIGATION_IMAGE_LOAD_NO_REMOTE: MitigationFlags = 262144; +pub const MITIGATION_IMAGE_LOAD_NO_LOW_LABEL: MitigationFlags = 524288; +pub const MITIGATION_IMAGE_LOAD_PREFER_SYS32: MitigationFlags = 1048576; +pub const MITIGATION_RESTRICT_INDIRECT_BRANCH_PREDICTION: MitigationFlags = 2097152; +pub const MITIGATION_CET_DISABLED: MitigationFlags = 4194304; +pub const MITIGATION_KTM_COMPONENT: MitigationFlags = 8388608; +#[repr(i32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum SubSystem { + SUBSYS_FILES = 0, + SUBSYS_NAMED_PIPES = 1, + SUBSYS_PROCESS = 2, + SUBSYS_REGISTRY = 3, + SUBSYS_SYNC = 4, + SUBSYS_WIN32K_LOCKDOWN = 5, + SUBSYS_SIGNED_BINARY = 6, + SUBSYS_SOCKET = 7, +} +#[repr(i32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum Semantics { + FILES_ALLOW_ANY = 0, + FILES_ALLOW_READONLY = 1, + FILES_ALLOW_QUERY = 2, + FILES_ALLOW_DIR_ANY = 3, + NAMEDPIPES_ALLOW_ANY = 4, + PROCESS_MIN_EXEC = 5, + PROCESS_ALL_EXEC = 6, + EVENTS_ALLOW_ANY = 7, + EVENTS_ALLOW_READONLY = 8, + REG_ALLOW_READONLY = 9, + REG_ALLOW_ANY = 10, + FAKE_USER_GDI_INIT = 11, + SIGNED_ALLOW_LOAD = 12, + SOCKET_ALLOW_BROKER = 13, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct BrokerServices { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct TargetServices { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ProcessState { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct TargetPolicy { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct PolicyInfo { + _unused: [u8; 0], +} +extern "C" { + pub fn sbox_broker_init(broker: *mut BrokerServices) -> ResultCode; +} +extern "C" { + pub fn sbox_create_policy(broker: *mut BrokerServices) -> *mut TargetPolicy; +} +extern "C" { + pub fn sbox_release_policy(policy: *mut TargetPolicy); +} +extern "C" { + pub fn sbox_spawn_target( + broker: *mut BrokerServices, + exe_path: *const wchar_t, + command_line: *const wchar_t, + policy: *mut TargetPolicy, + last_warning: *mut ResultCode, + last_error: *mut DWORD, + target: *mut PROCESS_INFORMATION, + ) -> ResultCode; +} +extern "C" { + pub fn sbox_wait_for_all_targets(broker: *mut BrokerServices) -> ResultCode; +} +extern "C" { + pub fn sbox_target_init(target: *mut TargetServices) -> ResultCode; +} +extern "C" { + pub fn sbox_lower_token(target: *mut TargetServices); +} +extern "C" { + pub fn sbox_get_state(target: *mut TargetServices) -> *mut ProcessState; +} +extern "C" { + pub fn get_broker_services() -> *mut BrokerServices; +} +extern "C" { + pub fn get_target_services() -> *mut TargetServices; +} +extern "C" { + pub fn sbox_set_token_level( + policy: *mut TargetPolicy, + initial: TokenLevel, + lockdown: TokenLevel, + ) -> ResultCode; +} +extern "C" { + pub fn sbox_get_initial_token_level(policy: *mut TargetPolicy) -> TokenLevel; +} +extern "C" { + pub fn sbox_get_lockdown_token_level(policy: *mut TargetPolicy) -> TokenLevel; +} +extern "C" { + pub fn sbox_set_job_level( + policy: *mut TargetPolicy, + job_level: JobLevel, + ui_exceptions: u32, + ) -> ResultCode; +} +extern "C" { + pub fn sbox_get_job_level(policy: *mut TargetPolicy) -> JobLevel; +} +extern "C" { + pub fn sbox_set_job_memory_limit(policy: *mut TargetPolicy, memory_limit: size_t) + -> ResultCode; +} +extern "C" { + pub fn sbox_set_integrity_level(policy: *mut TargetPolicy, level: IntegrityLevel) + -> ResultCode; +} +extern "C" { + pub fn sbox_set_delayed_integrity_level( + policy: *mut TargetPolicy, + level: IntegrityLevel, + ) -> ResultCode; +} +extern "C" { + pub fn sbox_get_integrity_level(policy: *mut TargetPolicy) -> IntegrityLevel; +} +extern "C" { + pub fn sbox_set_alternate_desktop( + policy: *mut TargetPolicy, + alternate_winstation: bool, + ) -> ResultCode; +} +extern "C" { + pub fn sbox_create_alternate_desktop( + policy: *mut TargetPolicy, + alternate_winstation: bool, + ) -> ResultCode; +} +extern "C" { + pub fn sbox_destroy_alternate_desktop(policy: *mut TargetPolicy); +} +extern "C" { + pub fn sbox_set_lowbox(policy: *mut TargetPolicy, sid: *const wchar_t) -> ResultCode; +} +extern "C" { + pub fn sbox_set_process_mitigations( + policy: *mut TargetPolicy, + flags: MitigationFlags, + ) -> ResultCode; +} +extern "C" { + pub fn sbox_get_process_mitigations(policy: *mut TargetPolicy) -> MitigationFlags; +} +extern "C" { + pub fn sbox_set_delayed_process_mitigations( + policy: *mut TargetPolicy, + flags: MitigationFlags, + ) -> ResultCode; +} +extern "C" { + pub fn sbox_get_delayed_process_mitigations(policy: *mut TargetPolicy) -> MitigationFlags; +} +extern "C" { + pub fn sbox_set_disconnect_csrss(policy: *mut TargetPolicy) -> ResultCode; +} +extern "C" { + pub fn sbox_set_strict_interceptions(policy: *mut TargetPolicy); +} +extern "C" { + pub fn sbox_set_stdout_handle(policy: *mut TargetPolicy, handle: HANDLE) -> ResultCode; +} +extern "C" { + pub fn sbox_set_stderr_handle(policy: *mut TargetPolicy, handle: HANDLE) -> ResultCode; +} +extern "C" { + pub fn sbox_add_rule( + policy: *mut TargetPolicy, + subsystem: SubSystem, + semantics: Semantics, + pattern: *const wchar_t, + ) -> ResultCode; +} +extern "C" { + pub fn sbox_add_dll_to_unload( + policy: *mut TargetPolicy, + dll_name: *const wchar_t, + ) -> ResultCode; +} +extern "C" { + pub fn sbox_add_kernel_object_to_close( + policy: *mut TargetPolicy, + handle_type: *const wchar_t, + handle_name: *const wchar_t, + ) -> ResultCode; +} +extern "C" { + pub fn sbox_add_handle_to_share(policy: *mut TargetPolicy, handle: HANDLE); +} +extern "C" { + pub fn sbox_set_lockdown_default_dacl(policy: *mut TargetPolicy); +} +extern "C" { + pub fn sbox_add_restricting_random_sid(policy: *mut TargetPolicy); +} +extern "C" { + pub fn sbox_add_app_container_profile( + policy: *mut TargetPolicy, + package_name: *const wchar_t, + create_profile: bool, + ) -> ResultCode; +} +extern "C" { + pub fn sbox_get_policy_info(policy: *mut TargetPolicy) -> *mut PolicyInfo; +} +extern "C" { + pub fn sbox_release_policy_info(policy_info: *mut PolicyInfo); +} +extern "C" { + pub fn sbox_policy_info_json_string( + policy_info: *mut PolicyInfo, + ) -> *const ::std::os::raw::c_char; +} diff --git a/sandbox/build.rs b/sandbox/build.rs new file mode 100644 index 0000000000..adf46b1e96 --- /dev/null +++ b/sandbox/build.rs @@ -0,0 +1,52 @@ +// Copyright 2022 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +static PREBUILTS_VERSION_FILENAME: &str = "prebuilts_version"; +static SANDBOX_LIB: &str = "libsandbox.lib"; + +static BINDINGS_FILE: &str = "bindings.rs"; + +pub fn setup_windows_prebuilts() { + println!("cargo:rustc-link-lib=dbghelp"); + println!("cargo:rustc-link-lib=gdi32"); + println!("cargo:rustc-link-lib=oleaut32"); + println!("cargo:rustc-link-lib=setupapi"); + println!("cargo:rustc-link-lib=shell32"); + println!("cargo:rustc-link-lib=user32"); + println!("cargo:rustc-link-lib=winmm"); + + if std::env::var("CARGO_CFG_DEBUG_ASSERTIONS").is_ok() { + println!("cargo:rustc-link-lib=ucrtd"); + } + + println!("cargo:rustc-link-lib=libsandbox"); + + println!("cargo:rerun-if-changed={}", BINDINGS_FILE); +} + +fn main() { + if std::env::var("CARGO_CFG_WINDOWS").is_ok() { + let version = std::fs::read_to_string(PREBUILTS_VERSION_FILENAME) + .unwrap() + .trim() + .parse::() + .unwrap(); + + // TODO(b:253039132) build sandbox prebuilts locally on windows from build.rs. + let files = prebuilts::download_prebuilts("sandbox", version, &[SANDBOX_LIB]).unwrap(); + println!( + r#"cargo:rustc-link-search={};{}"#, + std::env::var("PATH").unwrap(), + files + .get(0) + .unwrap() + .parent() + .unwrap() + .as_os_str() + .to_str() + .unwrap() + ); + setup_windows_prebuilts(); + } +} diff --git a/sandbox/prebuilts_version b/sandbox/prebuilts_version new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/sandbox/prebuilts_version @@ -0,0 +1 @@ +1 diff --git a/sandbox/src/lib.rs b/sandbox/src/lib.rs new file mode 100644 index 0000000000..abc70c7ad3 --- /dev/null +++ b/sandbox/src/lib.rs @@ -0,0 +1,826 @@ +// Copyright 2022 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#![cfg(windows)] + +use std::ffi::CStr; +use std::fmt; +use std::marker::PhantomData; +use std::sync::Once; + +use base::named_pipes; +use base::AsRawDescriptor; +use base::FromRawDescriptor; +use base::SafeDescriptor; +use win_util::win32_wide_string; + +#[cfg_attr(windows, path = "../bindings.rs")] +#[allow(non_camel_case_types)] +#[allow(non_snake_case)] +#[allow(non_upper_case_globals)] +#[allow(deref_nullptr)] +pub mod bindings; + +pub mod policy; + +pub use bindings::IntegrityLevel; +pub use bindings::JobLevel; +pub use bindings::MitigationFlags; +pub use bindings::ResultCode; +pub use bindings::Semantics; +pub use bindings::SubSystem; +pub use bindings::TokenLevel; + +use bindings::DWORD; +pub use bindings::MITIGATION_BOTTOM_UP_ASLR; +pub use bindings::MITIGATION_CET_DISABLED; +pub use bindings::MITIGATION_DEP; +pub use bindings::MITIGATION_DEP_NO_ATL_THUNK; +pub use bindings::MITIGATION_DLL_SEARCH_ORDER; +pub use bindings::MITIGATION_DYNAMIC_CODE_DISABLE; +pub use bindings::MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT; +pub use bindings::MITIGATION_DYNAMIC_CODE_OPT_OUT_THIS_THREAD; +pub use bindings::MITIGATION_EXTENSION_POINT_DISABLE; +pub use bindings::MITIGATION_FORCE_MS_SIGNED_BINS; +pub use bindings::MITIGATION_HARDEN_TOKEN_IL_POLICY; +pub use bindings::MITIGATION_HEAP_TERMINATE; +pub use bindings::MITIGATION_HIGH_ENTROPY_ASLR; +pub use bindings::MITIGATION_IMAGE_LOAD_NO_LOW_LABEL; +pub use bindings::MITIGATION_IMAGE_LOAD_NO_REMOTE; +pub use bindings::MITIGATION_IMAGE_LOAD_PREFER_SYS32; +pub use bindings::MITIGATION_KTM_COMPONENT; +pub use bindings::MITIGATION_NONSYSTEM_FONT_DISABLE; +pub use bindings::MITIGATION_RELOCATE_IMAGE; +pub use bindings::MITIGATION_RELOCATE_IMAGE_REQUIRED; +pub use bindings::MITIGATION_RESTRICT_INDIRECT_BRANCH_PREDICTION; +pub use bindings::MITIGATION_SEHOP; +pub use bindings::MITIGATION_STRICT_HANDLE_CHECKS; +pub use bindings::MITIGATION_WIN32K_DISABLE; + +pub use bindings::JOB_OBJECT_UILIMIT_ALL; +pub use bindings::JOB_OBJECT_UILIMIT_DESKTOP; +pub use bindings::JOB_OBJECT_UILIMIT_DISPLAYSETTINGS; +pub use bindings::JOB_OBJECT_UILIMIT_EXITWINDOWS; +pub use bindings::JOB_OBJECT_UILIMIT_GLOBALATOMS; +pub use bindings::JOB_OBJECT_UILIMIT_HANDLES; +pub use bindings::JOB_OBJECT_UILIMIT_NONE; +pub use bindings::JOB_OBJECT_UILIMIT_READCLIPBOARD; +pub use bindings::JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS; +pub use bindings::JOB_OBJECT_UILIMIT_WRITECLIPBOARD; + +use bindings::PROCESS_INFORMATION; + +type Result = std::result::Result; + +#[derive(Debug, Copy, Clone)] +pub struct SandboxError { + result_code: ResultCode, + error_code: Option, +} + +impl fmt::Display for SandboxError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.error_code { + Some(error_code) => write!( + f, + "Sandbox error code: {:?}, win32 error code: {}", + self.result_code, error_code, + ), + None => write!(f, "Sandbox error code: {:?}", self.result_code), + } + } +} + +impl std::error::Error for SandboxError { + fn description(&self) -> &str { + "sandbox error" + } +} + +impl SandboxError { + pub fn new(result_code: ResultCode) -> SandboxError { + if result_code == bindings::ResultCode::SBOX_ERROR_GENERIC { + return SandboxError::from(std::io::Error::last_os_error()); + } + SandboxError { + result_code, + error_code: None, + } + } +} + +impl From for SandboxError { + fn from(error: std::io::Error) -> Self { + let error_code = error.raw_os_error().map(|e| e as u32); + SandboxError { + result_code: bindings::ResultCode::SBOX_ERROR_GENERIC, + error_code, + } + } +} + +pub type SandboxWarning = SandboxError; + +/// Encapsulates broker-related functionality for the sandbox. +/// +/// This struct and its methods are not thread safe, in general. Only a single +/// thread should call the methods on this struct. +#[derive(Debug, PartialEq, Clone)] +pub struct BrokerServices { + broker: *mut bindings::BrokerServices, +} + +/// Encapsulates target-related functionality for the sandbox. +#[derive(Debug, PartialEq, Clone)] +pub struct TargetServices { + target: *mut bindings::TargetServices, +} + +/// Defines sandbox policies for target processes. +pub struct TargetPolicy<'a> { + policy: TargetPolicyWrapper, + _marker: PhantomData<&'a dyn AsRawDescriptor>, +} + +struct TargetPolicyWrapper(*mut bindings::TargetPolicy); + +impl Drop for TargetPolicyWrapper { + fn drop(&mut self) { + // Safe because TargetPolicyWrapper can only be constructed with a non-null pointer. + unsafe { bindings::sbox_release_policy(self.0) }; + } +} + +pub struct ProcessInformation { + pub process: SafeDescriptor, + pub thread: SafeDescriptor, + pub process_id: u32, + pub thread_id: u32, +} + +pub struct PolicyInfo { + policy_info: *mut bindings::PolicyInfo, +} + +impl Drop for PolicyInfo { + fn drop(&mut self) { + unsafe { bindings::sbox_release_policy_info(self.policy_info) } + } +} + +/// Returns true if this process is the broker process. +pub fn is_sandbox_broker() -> bool { + // Safe because the returned raw pointer is a non-owning pointer and we are free + // to let it drop unmanaged. + unsafe { !bindings::get_broker_services().is_null() } +} + +/// Returns true if this process is a target process. +pub fn is_sandbox_target() -> bool { + // Safe because the returned raw pointer is a non-owning pointer and we are + // free to let it drop unmanaged. + unsafe { !bindings::get_target_services().is_null() } +} + +impl BrokerServices { + /// Returns an initialized broker API interface if the process is the broker. + pub fn get() -> Result> { + static INIT: Once = Once::new(); + static mut RESULT: Result<()> = Ok(()); + static mut BROKER: Option = None; + + // Initialize broker services. Should be called once before use. + // Safe because RESULT is only written once, and call_once will cause + // other readers to block until execution of the block is complete. + // Also checks for and eliminates any null pointers. + unsafe { + INIT.call_once(|| { + let broker = bindings::get_broker_services(); + if broker.is_null() { + return; + } + BROKER = Some(BrokerServices { broker }); + RESULT = BROKER.as_mut().unwrap().init(); + }); + if BROKER.is_none() { + return Ok(None); + } + match RESULT { + Err(e) => Err(e), + Ok(_) => Ok(Some(BROKER.as_mut().unwrap().clone())), + } + } + } + + /// Initializes the broker. Must be called once before calling any other + /// methods. + /// + /// Takes a &mut self because sbox_broker_init mutates the underlying broker + /// object. + fn init(&mut self) -> Result<()> { + // Safe because BrokerServices can only be constructed with a non-null + // pointer. + let result_code = unsafe { bindings::sbox_broker_init(self.broker) }; + if result_code != ResultCode::SBOX_ALL_OK { + Err(SandboxError::new(result_code)) + } else { + Ok(()) + } + } + + /// Create a new policy object. + pub fn create_policy<'a>(&self) -> TargetPolicy<'a> { + // Safe because BrokerServices can only be constructed with a non-null pointer. + let policy = unsafe { bindings::sbox_create_policy(self.broker) }; + TargetPolicy { + policy: TargetPolicyWrapper(policy), + _marker: PhantomData, + } + } + + /// Spawn a new target process. This process is created with the main thread + /// in a suspended state. + /// + /// Takes a `&mut self` because `sbox_spawn_target()` mutates the underlying + /// broker object. + pub fn spawn_target( + &mut self, + exe_path: &str, + command_line: &str, + policy: &TargetPolicy, + ) -> Result<(ProcessInformation, Option)> { + let mut last_warning = ResultCode::SBOX_ALL_OK; + let mut last_error: DWORD = 0; + let mut target = PROCESS_INFORMATION { + dwProcessId: 0, + dwThreadId: 0, + hThread: std::ptr::null_mut(), + hProcess: std::ptr::null_mut(), + }; + // Safe because the external arguments must be constructed in a safe + // way, and the rest of the arguments are pointers to valid objects + // created in this function. + let result = unsafe { + bindings::sbox_spawn_target( + self.broker, + win32_wide_string(exe_path).as_ptr(), + win32_wide_string(command_line).as_ptr(), + policy.policy.0, + &mut last_warning, + &mut last_error, + &mut target, + ) + }; + if result != ResultCode::SBOX_ALL_OK { + return Err(SandboxError { + result_code: result, + error_code: Some(last_error), + }); + } + // Safe because we are adopting the process and thread handles here, + // and they won't be used outside of the SafeDescriptor after this + // function returns. + let process = unsafe { + ProcessInformation { + process: SafeDescriptor::from_raw_descriptor(target.hProcess), + thread: SafeDescriptor::from_raw_descriptor(target.hThread), + process_id: target.dwProcessId, + thread_id: target.dwThreadId, + } + }; + if last_warning != ResultCode::SBOX_ALL_OK { + Ok(( + process, + Some(SandboxWarning { + result_code: last_warning, + error_code: Some(last_error), + }), + )) + } else { + Ok((process, None)) + } + } + + /// Waits (blocks) for all target processes to exit. + /// + /// Takes a `&mut self` because `sbox_wait_for_all_targets()` mutates the + /// underlying broker object. + pub fn wait_for_all_targets(&mut self) -> Result<()> { + // Safe because BrokerServices can only be constructed with a non-null pointer. + let result_code = unsafe { bindings::sbox_wait_for_all_targets(self.broker) }; + if result_code != ResultCode::SBOX_ALL_OK { + Err(SandboxError::new(result_code)) + } else { + Ok(()) + } + } +} + +impl TargetServices { + /// Returns an initialized target API interface if the process is the target. + pub fn get() -> Result> { + static INIT: Once = Once::new(); + static mut RESULT: Result<()> = Ok(()); + static mut TARGET: Option = None; + + // Initialize target services. Should be called once before use. + // Safe because RESULT is only written once, and call_once will cause + // other readers to block until execution of the block is complete. + // Also checks for and eliminates any null pointers. + unsafe { + INIT.call_once(|| { + let target = bindings::get_target_services(); + if target.is_null() { + return; + } + TARGET = Some(TargetServices { target }); + RESULT = TARGET.as_mut().unwrap().init() + }); + if TARGET.is_none() { + return Ok(None); + } + // Initialize target services. If TargetServices is already initialized, + // this is a no-op. + match RESULT { + Err(e) => Err(e), + Ok(_) => Ok(Some(TARGET.as_mut().unwrap().clone())), + } + } + } + + /// Initializes the target. Must be called once before calling any other + /// methods. + /// + /// Takes a `&mut self` because `sbox_target_init()` mutates the underlying + /// target object. + fn init(&mut self) -> Result<()> { + // Safe because TargetServices can only be constructed with a non-null pointer. + let result_code = unsafe { bindings::sbox_target_init(self.target) }; + if result_code != ResultCode::SBOX_ALL_OK { + Err(SandboxError::new(result_code)) + } else { + Ok(()) + } + } + + /// Discards the targets impersonation token and uses the lower token. + /// + /// Takes a `&mut self` because `sbox_lower_token()` mutates the underlying + /// target object. + pub fn lower_token(&mut self) { + // Safe because TargetServices can only be constructed with a non-null pointer. + unsafe { bindings::sbox_lower_token(self.target) }; + } +} + +impl<'a> TargetPolicy<'a> { + /// Sets the security level for the process' two tokens. + /// + /// Takes a `&mut self` because `sbox_set_token_level()` mutates the + /// underlying policy object. + pub fn set_token_level(&mut self, initial: TokenLevel, lockdown: TokenLevel) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + match unsafe { bindings::sbox_set_token_level(self.policy.0, initial, lockdown) } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Gets the initial token level. + pub fn initial_token_level(&self) -> TokenLevel { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + unsafe { bindings::sbox_get_initial_token_level(self.policy.0) } + } + + /// Gets the lockdown token level. + pub fn lockdown_token_level(&self) -> TokenLevel { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + unsafe { bindings::sbox_get_lockdown_token_level(self.policy.0) } + } + + /// Sets the security level of the job object to which the process will + /// belong. + /// + /// Takes a `&mut self` because `sbox_set_job_level()` mutates the + /// underlying policy object. + pub fn set_job_level(&mut self, job_level: JobLevel, ui_exceptions: u32) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + match unsafe { bindings::sbox_set_job_level(self.policy.0, job_level, ui_exceptions) } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Returns the job level. + pub fn job_level(&self) -> JobLevel { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + unsafe { bindings::sbox_get_job_level(self.policy.0) } + } + + /// Sets the initial integrity level of the process in the sandbox. + /// + /// Takes a `&mut self` because `sbox_set_integrity_level()` mutates the + /// underlying policy object. + pub fn set_integrity_level(&mut self, level: IntegrityLevel) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + match unsafe { bindings::sbox_set_integrity_level(self.policy.0, level) } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Sets the delayed integrity level of the process in the sandbox. + /// + /// Takes a `&mut self` because `sbox_set_delayed_integrity_level()` mutates the + /// underlying policy object. + pub fn set_delayed_integrity_level(&mut self, level: IntegrityLevel) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + match unsafe { bindings::sbox_set_delayed_integrity_level(self.policy.0, level) } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Returns the initial integrity level used. + pub fn integrity_level(&self) -> IntegrityLevel { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + unsafe { bindings::sbox_get_integrity_level(self.policy.0) } + } + + /// Specifies that the process should run on an alternate desktop. If + /// `alternate_winstation` is set to `true`, the desktop will be created on an + /// alternate windows station. + /// + /// Takes a `&mut self` because `sbox_set_alternate_desktop` mutates the + /// underlying policy object. + pub fn set_alternate_desktop(&mut self, alternate_winstation: bool) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + match unsafe { bindings::sbox_set_alternate_desktop(self.policy.0, alternate_winstation) } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Precreates the alternate desktop and winstation, if any. + /// + /// Takes a `&mut self` because `sbox_create_alternate_desktop` mutates the + /// underlying policy object. + pub fn create_alternate_desktop(&mut self, alternate_winstation: bool) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + match unsafe { + bindings::sbox_create_alternate_desktop(self.policy.0, alternate_winstation) + } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Destroys the desktop and windows station. + /// + /// Takes a `&mut self` because `sbox_destroy_alternate_desktop` mutates the + /// underlying policy object. + pub fn destroy_alternate_desktop(&mut self) { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + unsafe { bindings::sbox_destroy_alternate_desktop(self.policy.0) } + } + + /// Sets the LowBox token for sandboxed process. This is mutually exclusive + /// with the `add_app_container_profile()` method. + /// + /// Takes a `&mut self` because `sbox_set_lowbox` mutates the underlying + /// policy object. + pub fn set_lowbox(&mut self, sid: &str) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + match unsafe { bindings::sbox_set_lowbox(self.policy.0, win32_wide_string(sid).as_ptr()) } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Sets the mitigations enabled when the process is created. + /// + /// Takes a `&mut self` because `sbox_set_process_mitigations` mutates the + /// underlying policy object. + pub fn set_process_mitigations(&mut self, flags: MitigationFlags) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + match unsafe { bindings::sbox_set_process_mitigations(self.policy.0, flags) } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Returns the currently set mitigation flags. + pub fn process_mitigations(&self) -> MitigationFlags { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + unsafe { bindings::sbox_get_process_mitigations(self.policy.0) } + } + + /// Sets process mitigation flags that don't take effect before the call to + /// lower_token(). + /// + /// Takes a `&mut self` because `sbox_set_delayed_process_mitigations` + /// mutates the underlying policy object. + pub fn set_delayed_process_mitigations(&mut self, flags: MitigationFlags) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + match unsafe { bindings::sbox_set_delayed_process_mitigations(self.policy.0, flags) } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Returns the currently set delayed_ mitigation flags. + pub fn delayed_process_mitigations(&self) -> MitigationFlags { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + unsafe { bindings::sbox_get_delayed_process_mitigations(self.policy.0) } + } + + /// Disconnect the target from CSRSS when TargetServices::lower_token() is + /// called inside the target. + /// + /// Takes a `&mut self` because `sbox_set_disconnect_csrss` mutates the + /// underlying policy object. + pub fn set_disconnect_csrss(&mut self) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + match unsafe { bindings::sbox_set_disconnect_csrss(self.policy.0) } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Sets the interceptions to operate in strict mode. + /// + /// Takes a `&mut self` because `sbox_set_delayed_process_mitigations` + /// mutates the underlying policy object. + pub fn set_strict_interceptions(&mut self) { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + unsafe { bindings::sbox_set_strict_interceptions(self.policy.0) } + } + + /// Sets a file as the handle that the process should inherit for stdout. + pub fn set_stdout_from_file(&mut self, file: &'a std::fs::File) -> Result<()> { + self.set_stdout_handle(file) + } + + /// Sets a pipe as the handle that the process should inherit for stdout. + pub fn set_stdout_from_pipe(&mut self, pipe: &'a named_pipes::PipeConnection) -> Result<()> { + self.set_stdout_handle(pipe) + } + + /// Sets the handle that the process should inherit for stdout. + /// + /// Takes a `&mut self` because `sbox_set_stdout_handle()` mutates the underlying policy object. + fn set_stdout_handle(&mut self, handle: &'a dyn AsRawDescriptor) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + match unsafe { bindings::sbox_set_stdout_handle(self.policy.0, handle.as_raw_descriptor()) } + { + ResultCode::SBOX_ALL_OK => { + win_util::set_handle_inheritance( + handle.as_raw_descriptor(), + /* inheritable= */ true, + )?; + Ok(()) + } + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Sets a file as the handle that the process should inherit for stderr. + pub fn set_stderr_from_file(&mut self, file: &'a std::fs::File) -> Result<()> { + self.set_stderr_handle(file) + } + + /// Sets a pipe as the handle that the process should inherit for stderr. + pub fn set_stderr_from_pipe(&mut self, pipe: &'a named_pipes::PipeConnection) -> Result<()> { + self.set_stderr_handle(pipe) + } + + /// Sets the handle that the process should inherit for stderr. + /// + /// Takes a `&mut self` because `sbox_set_stderr_handle` mutates the underlying policy object. + fn set_stderr_handle(&mut self, handle: &'a dyn AsRawDescriptor) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + match unsafe { bindings::sbox_set_stderr_handle(self.policy.0, handle.as_raw_descriptor()) } + { + ResultCode::SBOX_ALL_OK => { + win_util::set_handle_inheritance( + handle.as_raw_descriptor(), + /* inheritable= */ true, + )?; + Ok(()) + } + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Adds a policy rule effective for processes spawned using this policy. + /// + /// # Arguments: + /// + /// * subsystem: One of the enumerated Subsystems. + /// * semantics: One of the enumerated Semantics. + /// * pattern: A specific full path or a full path with wildcard patterns. + /// + /// The valid wildcards are: + /// * `*`: Matches zero or more character. Only one in series allowed. + /// * `?`: Matches a single character. One or more in series are allowed. + /// + /// Examples: + /// * `"c:\\documents and settings\\vince\\*.dmp"` + /// * `"c:\\documents and settings\\*\\crashdumps\\*.dmp"` + /// * `"c:\\temp\\app_log_?????_chrome.txt"` + /// + /// Takes a `&mut self` because `sbox_add_rule` mutates the underlying + /// policy object. + pub fn add_rule>( + &mut self, + subsystem: SubSystem, + semantics: Semantics, + pattern: T, + ) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + // The function does not modify the pattern pointer, so that usage is safe. + match unsafe { + bindings::sbox_add_rule( + self.policy.0, + subsystem, + semantics, + win32_wide_string(pattern.as_ref()).as_ptr(), + ) + } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Adds a dll that will be unloaded in the target process before it gets + /// a chance to initialize itself. + /// + /// Takes a `&mut self` because `sbox_add_dll_to_unload` mutates the + /// underlying policy object. + pub fn add_dll_to_unload(&mut self, dll_name: &str) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + // The function does not modify the dll_name pointer, so that usage is safe. + match unsafe { + bindings::sbox_add_dll_to_unload(self.policy.0, win32_wide_string(dll_name).as_ptr()) + } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Adds a handle that will be closed in the target process after lockdown. + /// Specifying `None` for `handle_name` indicates all handles of the specified + /// type. An empty string for `handle_name` indicates the handle is unnamed. + /// + /// Takes a `&mut self` because `sbox_add_kernel_object_to_close` mutates the + /// underlying policy object. + pub fn add_kernel_object_to_close( + &mut self, + handle_type: &str, + handle_name: Option<&str>, + ) -> Result<()> { + let handle_name_wide = handle_name.map(win32_wide_string); + let handle_name_ptr = handle_name_wide + .as_ref() + .map_or(std::ptr::null(), Vec::::as_ptr); + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + // The function does not modify either of the string pointers, so that usage is safe. + // The function safely handles null pointers for the handle name. + match unsafe { + bindings::sbox_add_kernel_object_to_close( + self.policy.0, + win32_wide_string(handle_type).as_ptr(), + handle_name_ptr, + ) + } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Adds a handle that will be shared with the target process. + /// + /// Takes a `&mut self` because `sbox_add_handle_to_share()` mutates the underlying policy object. + pub fn add_handle_to_share(&mut self, handle: &'a dyn AsRawDescriptor) { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + unsafe { + bindings::sbox_add_handle_to_share(self.policy.0, handle.as_raw_descriptor()); + } + } + + /// Locks down the default DACL of the created lockdown and initial tokens + /// to restrict what other processes are allowed to access a process' kernel + /// resources. + /// + /// Takes a `&mut self` because `sbox_set_lockdown_default_dacl()` mutates + /// the underlying policy object. + pub fn set_lockdown_default_dacl(&mut self) { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + unsafe { + bindings::sbox_set_lockdown_default_dacl(self.policy.0); + } + } + + /// Adds a restricting random SID to the restricted SIDs list as well as + /// the default DACL. + /// + /// Takes a `&mut self` because `sbox_add_restricting_random_sid()` mutates + /// the underlying policy object. + pub fn add_restricting_random_sid(&mut self) { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + unsafe { + bindings::sbox_add_restricting_random_sid(self.policy.0); + } + } + + /// Configure policy to use an AppContainer profile. + /// + /// # Arguments: + /// * `package_name`: the name of the profile to use. + /// * `create_profile`: Specifying `true` for `create_profile` ensures + /// the profile exists, if set to `false` process creation will fail if the + /// profile has not already been created. + /// + /// Takes a `&mut self` because `sbox_add_dll_to_unload` mutates the + /// underlying policy object. + pub fn add_app_container_profile( + &mut self, + package_name: &str, + create_profile: bool, + ) -> Result<()> { + // Safe because TargetPolicy can only be constructed with a non-null policy pointer. + // The function does not modify the package_name pointer, so that usage is safe. + match unsafe { + bindings::sbox_add_app_container_profile( + self.policy.0, + win32_wide_string(package_name).as_ptr(), + create_profile, + ) + } { + ResultCode::SBOX_ALL_OK => Ok(()), + result_code => Err(SandboxError::new(result_code)), + } + } + + /// Returns a snapshot of the policy configuration. + pub fn policy_info(&self) -> PolicyInfo { + // Safe because TargetPolicy can only be constructed with a non-null + // policy pointer. The underlying PolicyInfo object contains a copy of + // the data from the TargetPolicy object, but does not hold any + // references to it, so the lifetimes are independent. + PolicyInfo { + policy_info: unsafe { bindings::sbox_get_policy_info(self.policy.0) }, + } + } +} + +impl PolicyInfo { + /// Returns a JSON representation of the policy snapshot. + /// This pointer has the same lifetime as the PolicyInfo object. + pub fn json(&self) -> &str { + // Safe because PolicyInfo can only be constructed with a non-null + // policy pointer. The string returned will be a valid pointer to a + // valid c string. We bind the lifetime of the string to the lifetime + // of the PolicyInfo object, as is guaranteed by the underlying + // library. This is a string representation of a snapshot of the + // policy, so it will not change. + let c_str = + unsafe { CStr::from_ptr(bindings::sbox_policy_info_json_string(self.policy_info)) }; + c_str.to_str().unwrap() + } +} + +// TODO(b/196996588): Develop more tests, especially policy-related, once we +// have a way to launch and test target processes. +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn not_the_target() { + let target = TargetServices::get().unwrap(); + assert_eq!(target, None); + } + + #[test] + fn is_the_broker() { + let broker = BrokerServices::get().unwrap(); + assert_ne!(broker, None); + } + + #[test] + fn policy_handles_live_long_enough() { + let broker = BrokerServices::get().unwrap().unwrap(); + let mut policy = broker.create_policy(); + let pipe = named_pipes::pair( + &named_pipes::FramingMode::Byte, + &named_pipes::BlockingMode::NoWait, + 0, + ) + .unwrap(); + policy.set_stdout_handle(&pipe.0).unwrap(); + policy.set_stderr_handle(&pipe.0).unwrap(); + policy.add_handle_to_share(&pipe.0); + } +} diff --git a/sandbox/src/policy.rs b/sandbox/src/policy.rs new file mode 100644 index 0000000000..7dc766ab0f --- /dev/null +++ b/sandbox/src/policy.rs @@ -0,0 +1,115 @@ +// Copyright 2022 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use crate::IntegrityLevel; +use crate::JobLevel; +use crate::Semantics; +use crate::SubSystem; +use crate::TokenLevel; +use crate::JOB_OBJECT_UILIMIT_READCLIPBOARD; +use crate::JOB_OBJECT_UILIMIT_WRITECLIPBOARD; + +/// Policy struct for describing how a sandbox `TargetPolicy` should be +/// constructed for a particular process. +pub struct Policy { + pub initial_token_level: TokenLevel, + pub lockdown_token_level: TokenLevel, + pub integrity_level: IntegrityLevel, + pub delayed_integrity_level: IntegrityLevel, + pub job_level: JobLevel, + pub ui_exceptions: u32, + pub alternate_desktop: bool, + pub alternate_winstation: bool, + pub exceptions: Vec, + pub dll_blocklist: Vec, +} + +/// Rule struct describing a sandbox rule that should be added to the +/// `TargetPolicy`. +pub struct Rule { + pub subsystem: SubSystem, + pub semantics: Semantics, + pub pattern: String, +} + +/// Policy for the main emulator process. +pub const MAIN: Policy = Policy { + // Token levels and integrity levels needed for access to hypervisor APIs. + initial_token_level: TokenLevel::USER_RESTRICTED_SAME_ACCESS, + lockdown_token_level: TokenLevel::USER_RESTRICTED_NON_ADMIN, + integrity_level: IntegrityLevel::INTEGRITY_LEVEL_MEDIUM, + // Needed for access to audio APIs. + delayed_integrity_level: IntegrityLevel::INTEGRITY_LEVEL_LOW, + // Needed for access to UI APIs. + job_level: JobLevel::JOB_LIMITED_USER, + ui_exceptions: JOB_OBJECT_UILIMIT_READCLIPBOARD | JOB_OBJECT_UILIMIT_WRITECLIPBOARD, + // Needed to display window on main desktop. + alternate_desktop: false, + alternate_winstation: false, + exceptions: vec![], + dll_blocklist: vec![], +}; + +/// Policy for the metrics process. +pub const METRICS: Policy = Policy { + // Needed for access to WinINet. + initial_token_level: TokenLevel::USER_NON_ADMIN, + lockdown_token_level: TokenLevel::USER_NON_ADMIN, + integrity_level: IntegrityLevel::INTEGRITY_LEVEL_LOW, + delayed_integrity_level: IntegrityLevel::INTEGRITY_LEVEL_LOW, + job_level: JobLevel::JOB_LOCKDOWN, + ui_exceptions: 0, + alternate_desktop: true, + alternate_winstation: true, + exceptions: vec![], + dll_blocklist: vec![], +}; + +/// Policy for a block device process. +pub const BLOCK: Policy = Policy { + initial_token_level: TokenLevel::USER_RESTRICTED_NON_ADMIN, + lockdown_token_level: TokenLevel::USER_LOCKDOWN, + // INTEGRITY_LEVEL_MEDIUM needed to open disk file. + integrity_level: IntegrityLevel::INTEGRITY_LEVEL_MEDIUM, + delayed_integrity_level: IntegrityLevel::INTEGRITY_LEVEL_UNTRUSTED, + job_level: JobLevel::JOB_LOCKDOWN, + ui_exceptions: 0, + alternate_desktop: true, + alternate_winstation: true, + exceptions: vec![], + dll_blocklist: vec![], +}; + +/// Policy for the network process. +pub const NET: Policy = Policy { + // Needed to connect to crash handler. + initial_token_level: TokenLevel::USER_INTERACTIVE, + lockdown_token_level: TokenLevel::USER_LOCKDOWN, + // Process won't start below this level as loading ntdll will fail. + integrity_level: IntegrityLevel::INTEGRITY_LEVEL_LOW, + delayed_integrity_level: IntegrityLevel::INTEGRITY_LEVEL_UNTRUSTED, + job_level: JobLevel::JOB_LOCKDOWN, + ui_exceptions: 0, + alternate_desktop: true, + alternate_winstation: true, + exceptions: vec![], + dll_blocklist: vec![], +}; + +/// Policy for the slirp process. +pub const SLIRP: Policy = Policy { + // Needed to connect to crash handler. + initial_token_level: TokenLevel::USER_INTERACTIVE, + // Needed for access to winsock. + lockdown_token_level: TokenLevel::USER_LIMITED, + // Needed for access to winsock. + integrity_level: IntegrityLevel::INTEGRITY_LEVEL_LOW, + delayed_integrity_level: IntegrityLevel::INTEGRITY_LEVEL_UNTRUSTED, + job_level: JobLevel::JOB_LOCKDOWN, + ui_exceptions: 0, + alternate_desktop: true, + alternate_winstation: true, + exceptions: vec![], + dll_blocklist: vec![], +}; diff --git a/tools/impl/test_config.py b/tools/impl/test_config.py index e32e3648da..1e79ed7b4a 100755 --- a/tools/impl/test_config.py +++ b/tools/impl/test_config.py @@ -130,6 +130,7 @@ CRATE_OPTIONS: Dict[str, List[TestOption]] = { ], "libvda": [TestOption.DO_NOT_BUILD], # b/202293971 "rutabaga_gfx": [TestOption.DO_NOT_BUILD_ARMHF], # b/210015864 + "sandbox": [TestOption.DO_NOT_RUN], "vhost": [TestOption.DO_NOT_RUN_ON_FOREIGN_KERNEL, TestOption.UNIT_AS_INTEGRATION_TEST], "vm_control": [TestOption.DO_NOT_BUILD_ARMHF], # b/210015864 }