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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::ptr;
use std::sync::Arc;

use crate::learning_mode_core::DenialAnalyzer;
use crate::learning_mode_windows::{EtlDenialAnalyzer, LearningModeError, LEARNING_MODE_API_SET};
use crate::learning_mode_windows::{EtlDenialAnalyzer, LearningModeApi, LearningModeError};
use windows::Win32::Foundation::{
CloseHandle, GetLastError, SetHandleInformation, ERROR_CALL_NOT_IMPLEMENTED, E_NOTIMPL, HANDLE,
HANDLE_FLAG_INHERIT, WAIT_OBJECT_0, WAIT_TIMEOUT,
Expand Down Expand Up @@ -315,14 +315,23 @@ impl BaseContainerRunner {
*PRESENT.get_or_init(|| is_api_set_implemented(SECURITY_ENVIRONMENT_API_SET))
}

/// Whether the native PSEC plus Learning Mode API set is available.
/// Whether native capture has both PSEC and a complete Learning Mode ABI.
pub fn is_native_capture_available() -> bool {
#[cfg(test)]
if let Ok(forced) = std::env::var("MXC_FORCE_NATIVE_CAPTURE_USABLE") {
return forced == "1";
}

Self::is_base_container_api_present() && is_api_set_implemented(LEARNING_MODE_API_SET)
Self::native_capture_available_with(Self::is_base_container_api_present(), || {
LearningModeApi::load().is_ok()
})
}

fn native_capture_available_with(
base_container_available: bool,
learning_mode_abi_available: impl FnOnce() -> bool,
) -> bool {
base_container_available && learning_mode_abi_available()
}

/// Whether PSEC can enforce `filesystem.deniedPaths`.
Expand Down Expand Up @@ -2103,6 +2112,22 @@ mod tests {
use crate::process_security_environment_spec::process_security_environment_layout as psec_layout;
use std::sync::atomic::{AtomicUsize, Ordering};

#[test]
fn native_capture_probe_requires_base_container_and_complete_learning_mode_abi() {
assert!(!BaseContainerRunner::native_capture_available_with(
false,
|| panic!("Learning Mode must not be probed when PSEC is unavailable")
));
assert!(!BaseContainerRunner::native_capture_available_with(
true,
|| false
));
assert!(BaseContainerRunner::native_capture_available_with(
true,
|| true
));
}

#[test]
fn basecontainer_network_audit_records_proxyless_proxy_and_setup_failure() {
for (default, proxy_port, status, expected_default) in [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
//! The ordering the OS requires is:
//!
//! 1. `CreateProcessSecurityEnvironment(spec)` β†’ env handle
//! 2. `StartLearningModeTrace(env)` β†’ trace handle (**before** the child launches, so no
//! early denials are missed)
//! 2. invoke the selected Learning Mode start export for `env` β†’ trace handle
//! (**before** the child launches, so no early denials are missed)
//! 3. attach `env` as `PROC_THREAD_ATTRIBUTE_SECURITY_ENVIRONMENT`, then call
//! `CreateProcessW` (**runner's job**; the session exposes the handle via
//! [`CaptureSession::environment`])
Expand All @@ -31,7 +31,9 @@ use windows::Win32::Foundation::HANDLE;

#[cfg(test)]
use crate::learning_mode_windows::LearningModeApi;
use crate::learning_mode_windows::{start_trace, LearningModeError, LearningModeTraceHandle};
use crate::learning_mode_windows::{
start_trace, LearningModeError, LearningModeTraceHandle, LearningModeTraceSources,
};

#[cfg(test)]
use crate::process_container_common::secenv::SecurityEnvironmentApi;
Expand Down Expand Up @@ -59,8 +61,9 @@ impl CaptureSession {
///
/// # Errors
/// - [`LearningModeError::HResultCall`] if `CreateProcessSecurityEnvironment` fails.
/// - [`LearningModeError::HResultCall`] if `StartLearningModeTrace` fails β€” in which
/// case the just-created environment is closed before returning so it is not leaked.
/// - [`LearningModeError::HResultCall`] if the selected Learning Mode start export
/// fails β€” in which case the just-created environment is closed before returning
/// so it is not leaked.
pub fn begin(sandbox_specification: &[u8], flags: u32) -> Result<Self, LearningModeError> {
let environment = secenv::create(sandbox_specification, flags)?;
// SAFETY: `environment` was just created and remains live until this
Expand Down Expand Up @@ -118,6 +121,18 @@ impl CaptureSession {
}
}

/// Event sources collected by the native trace.
///
/// Callers can use this to distinguish a legacy access-only trace from an
/// option-aware trace that also collected network decisions.
#[must_use]
pub fn trace_sources(&self) -> LearningModeTraceSources {
self.trace
.as_ref()
.expect("a live CaptureSession always owns its trace")
.sources()
}

/// Stop the trace and deliver it to `output_path` (or skip delivery when
/// `None`), retry transient delivery failures, close the trace, then close
/// the security environment. Call **after** the child has exited.
Expand Down Expand Up @@ -212,6 +227,19 @@ mod tests {
result
}

unsafe extern "system" fn fake_start_with_options(
_: HANDLE,
_: i32,
out: *mut HANDLE,
) -> HRESULT {
record("start_options");
let result = HRESULT(START_RESULT.load(Ordering::SeqCst));
if result.is_ok() {
unsafe { *out = dangling_handle() };
}
result
}

unsafe extern "system" fn fake_stop(_: HANDLE, _: *const u16) -> HRESULT {
record("stop");
if STOP_FAILURES_REMAINING
Expand Down Expand Up @@ -250,6 +278,14 @@ mod tests {
LearningModeApi::from_raw_parts(fake_start, fake_stop, fake_trace_close)
}

fn fake_options_learning_mode_api() -> LearningModeApi {
LearningModeApi::from_options_raw_parts(
fake_start_with_options,
fake_stop,
fake_trace_close,
)
}

fn begin_session() -> Result<CaptureSession, LearningModeError> {
CaptureSession::begin_with_apis(
fake_secenv_api(),
Expand All @@ -267,11 +303,35 @@ mod tests {
// The environment must be created first so the trace keys on a live handle.
assert_eq!(take_events(), vec!["create", "start"]);
assert_eq!(session.environment(), dangling_handle());
assert_eq!(
session.trace_sources(),
LearningModeTraceSources::AccessOnly
);

// Tidy up deterministically so Drop bookkeeping does not leak into siblings.
drop(session);
}

#[test]
fn begin_exposes_option_aware_trace_sources() {
let _guard = reset();
let session = CaptureSession::begin_with_apis(
fake_secenv_api(),
fake_options_learning_mode_api(),
b"PSEC-fake-spec",
crate::process_container_common::secenv::PROCESS_SECURITY_ENVIRONMENT_FLAG_NONE,
)
.expect("begin should succeed with option-aware fakes");

assert_eq!(take_events(), vec!["create", "start_options"]);
assert_eq!(
session.trace_sources(),
LearningModeTraceSources::AccessAndNetwork
);

drop(session);
}

#[test]
fn begin_start_failure_closes_environment_and_leaves_no_trace() {
let _guard = reset();
Expand Down
Loading
Loading