sandbox: upstream sandbox

The crate depends on a prebuilt library.

BUG=b:213170185
TEST=presubmit

Change-Id: I810baa14afa53e88f6b2a739481a6d3656efc2e5
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3784338
Commit-Queue: Vikram Auradkar <auradkar@google.com>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
Vikram Auradkar 2022-07-26 22:19:25 -07:00 committed by crosvm LUCI
parent 990a0ce365
commit 44a07970cb
9 changed files with 1471 additions and 0 deletions

12
Cargo.lock generated
View file

@ -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"

View file

@ -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 = "*"

14
sandbox/Cargo.toml Normal file
View file

@ -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" }

448
sandbox/bindings.rs Normal file
View file

@ -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;
}

52
sandbox/build.rs Normal file
View file

@ -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::<u32>()
.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();
}
}

View file

@ -0,0 +1 @@
1

826
sandbox/src/lib.rs Normal file
View file

@ -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<T> = std::result::Result<T, SandboxError>;
#[derive(Debug, Copy, Clone)]
pub struct SandboxError {
result_code: ResultCode,
error_code: Option<u32>,
}
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<std::io::Error> 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<Option<BrokerServices>> {
static INIT: Once = Once::new();
static mut RESULT: Result<()> = Ok(());
static mut BROKER: Option<BrokerServices> = 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<SandboxWarning>)> {
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<Option<TargetServices>> {
static INIT: Once = Once::new();
static mut RESULT: Result<()> = Ok(());
static mut TARGET: Option<TargetServices> = 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<T: AsRef<str>>(
&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::<u16>::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);
}
}

115
sandbox/src/policy.rs Normal file
View file

@ -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<Rule>,
pub dll_blocklist: Vec<String>,
}
/// 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![],
};

View file

@ -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
}