Consume the Windows Learning Mode trace API: FFI loader + capture lifecycle#661
Consume the Windows Learning Mode trace API: FFI loader + capture lifecycle#661richiemsft wants to merge 13 commits into
Conversation
…ty probe Introduce the `learning_mode_windows` crate: a runtime-loaded FFI adapter for the AppInfo-brokered Learning Mode trace exports in processmodel.dll (StartLearningModeTrace / StopLearningModeTrace), mirroring the existing Experimental_CreateProcessInSandbox loader. - LoadLibraryExW(LOAD_LIBRARY_SEARCH_SYSTEM32) + GetProcAddress resolution of the two flat C exports; module left resident (never FreeLibrary), matching the sibling adapter. - is_learning_mode_api_available() capability probe; typed LearningModeError with actionable ExportMissing/DllLoad/ApiCall variants for clean degrade when the OS build lacks the API. - Opaque LearningModeTraceHandle and safe start_trace/stop_trace wrappers over the BOOL/GetLastError exports (Stop takes a path; None discards). - Compiles on every platform (non-Windows stub probe returns false). Grounded in OS PRs #16184172 + #16197586 (final Start takes an HPROCESS_SECURITY_ENVIRONMENT handle). Unit tests + clippy/fmt clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a manual validation example that prints the capability-probe result and resolves the two processmodel.dll exports. Validated on the GE_CURRENT DirectWinPD x64 image ge-directwinpd-260719: is_learning_mode_api_available returns true and both StartLearningModeTrace / StopLearningModeTrace resolve. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add the process security-environment exports (CreateProcessSecurityEnvironment, CreateProcessAsUserInsideSecurityEnvironment, CloseProcessSecurityEnvironment) that produce the HPROCESS_SECURITY_ENVIRONMENT handle StartLearningModeTrace keys on. The runtime loader mirrors the trace-export pattern (LoadLibraryExW SYSTEM32 + GetProcAddress) with an Experimental_-prefixed fallback and a per-export diagnostic probe. Extend lm_probe to report the resolved security-environment export names. Validated on a GE_CURRENT DirectWinPD x64 build: all three exports resolve under their plain (graduated) names and SecurityEnvironmentApi::load succeeds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sequence create-env -> start-trace -> [runner launches child] -> stop-trace -> close-env with correct ordering. CaptureSession::begin performs the pre-launch steps (create the security environment, start the trace before the child runs) and exposes the environment handle for CreateProcessAsUserInsideSecurityEnvironment; finish() seals the ETL then closes the environment, attempting both even if the first fails. Drop is a best-effort teardown (discard trace, close env) so an early launch failure or unwind never leaks broker-side state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drive the full 2-phase capture lifecycle against a real child, independent of the MXC runner and the captureDenials config: build a minimal FlatBuffer SandboxSpec with the learning-mode capability, CaptureSession::begin (create env + start trace), launch cmd.exe via CreateProcessAsUserInsideSecurityEnvironment, wait, then finish (seal ETL + close env) and assert a non-empty ETL was produced. Validated on a GE_CURRENT DirectWinPD x64 build: spec accepted directly (no text tech-spec compile), child exited 0, and a 3650-byte ETL was sealed. Adds sandbox_spec + flatbuffers as Windows dev-dependencies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Adds a new Rust workspace member (learning_mode_windows) that dynamically loads the Windows Learning Mode trace and process security-environment exports from processmodel.dll, and provides an RAII capture lifecycle intended to be reused by a future captureDenials runner integration.
Changes:
- Adds
learning_mode_windowsas a workspace crate and dependency entry. - Implements runtime FFI loaders for Learning Mode trace exports and 2-phase security-environment exports (including
Experimental_name fallbacks) plus capability probes. - Introduces an RAII
CaptureSessionto sequence create-env → start-trace → stop-trace → close-env, along withlm_probe/lm_capturevalidation examples.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Cargo.toml | Adds the new backend crate to the workspace and workspace dependencies. |
| src/Cargo.lock | Records the new workspace package entry for learning_mode_windows. |
| src/backends/learning_mode/windows/Cargo.toml | Defines the new crate and its Windows-only deps/dev-deps. |
| src/backends/learning_mode/windows/src/lib.rs | Public crate surface, cfg-gated module structure, and shared error type. |
| src/backends/learning_mode/windows/src/ffi.rs | Runtime loader + safe wrapper for StartLearningModeTrace / StopLearningModeTrace. |
| src/backends/learning_mode/windows/src/secenv.rs | Runtime loader + wrapper for 2-phase security-environment process creation exports. |
| src/backends/learning_mode/windows/src/lifecycle.rs | RAII CaptureSession orchestration of the capture lifecycle. |
| src/backends/learning_mode/windows/examples/lm_probe.rs | Manual probe example to validate export resolution on real OS builds. |
| src/backends/learning_mode/windows/examples/lm_capture.rs | End-to-end example driving the full lifecycle and asserting an ETL is produced. |
- ffi::stop_trace: encode the output path with OsStrExt::encode_wide instead of to_string_lossy, so non-Unicode path data is preserved and the ETL lands at the exact caller-requested path. - CaptureSession::environment: fail fast on the internal invariant violation instead of returning a NULL HANDLE, surfacing misuse at the call site rather than as a confusing GetLastError from a downstream Win32 call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/backends/learning_mode/windows/src/lifecycle.rs:152
- The tests below only cover result combination; neither this new
Droppath nor its required stop-before-close ordering is exercised. A regression that skips one cleanup call or reverses the order would still pass. Please add mock/injectable FFI callbacks and verifyDropcallsstop_trace(None)beforeclose, including cleanup-call failures.
impl Drop for CaptureSession {
fn drop(&mut self) {
// Best-effort teardown for the early-exit / unwind path: discard the trace
// (NULL output path) before closing the environment. Errors are unrecoverable
// here and are intentionally ignored — `finish` is the fallible path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 10 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/backends/learning_mode/windows/src/lifecycle.rs:74
- The lifecycle tests only exercise result aggregation, so the key RAII guarantees here—closing the environment when trace startup fails and stopping the trace before closing on
Drop—are untested; the hardware example covers only the successfulfinishpath. Add an injectable/test API implementation and assert call order and cleanup for these failure paths.
let trace = match unsafe { learning_mode_api.start_trace(environment.raw()) } {
Ok(trace) => trace,
Err(start_err) => {
return match secenv_api.close(environment) {
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 10 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
src/backends/learning_mode/windows/src/lifecycle.rs:158
- The core RAII guarantee on this path is not covered by the added unit tests: they only exercise
combine_teardown_results, so a regression that omits cleanup or reverses the required discard-before-close order would still pass. Add injectable/fake API function tables and assert thatDropcallsStopLearningModeTrace(..., NULL)beforeCloseProcessSecurityEnvironment, including when the first cleanup call fails.
if let Some(trace) = self.trace.take() {
let _ = self.learning_mode_api.stop_trace(trace, None);
}
if let Some(environment) = self.environment.take() {
let _ = self.secenv_api.close(environment);
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
📖 Description
Adds the Windows runtime FFI and RAII lifecycle for consuming the new OS Learning Mode
trace API exported by
processmodel.dll— the foundation for the upcomingcaptureDenialsfeature (capture a sandboxed process's access denials to an ETL forlater analysis).
This PR is foundation only; it does not yet wire any runner. It introduces a new
crate,
learning_mode_windows(src/backends/learning_mode/windows):ffi.rs) —LearningModeApiloads/resolvesStartLearningModeTrace/StopLearningModeTraceviaLoadLibraryExW(SYSTEM32)+GetProcAddress, with ais_learning_mode_api_available()capability probe. Mirrors the existingExperimental_CreateProcessInSandboxloader pattern.secenv.rs) —SecurityEnvironmentApi(
CreateProcessSecurityEnvironment/CreateProcessAsUserInsideSecurityEnvironment/CloseProcessSecurityEnvironment) that produces theHPROCESS_SECURITY_ENVIRONMENThandle
StartLearningModeTracekeys on, plus a per-export diagnostic probe with anExperimental_-prefixed fallback.lifecycle.rs) —CaptureSessionsequences create-env →start-trace → [caller launches child] → stop-trace → close-env in the required order,
with best-effort
Dropteardown (discard trace + close env) on the early-exit/unwindpath.
lm_probe(export-resolution probe) andlm_capture(drivesthe entire lifecycle against a real child and asserts an ETL is produced).
The API surface is resolved at runtime and degrades cleanly on OS builds that lack it
(the probe returns
false,load()returns a typed error). The crate compiles on allplatforms (non-Windows is a stub).
Not in this PR (follow-ups): the
captureDenialsconfig, the production runner pathin
appcontainer_common, and ETL parsing. The runner wiring will reuse BaseContainer'sFlatBuffer
sandbox_specbuilder and swap the one-shot launch forCaptureSession+launch_fn; it depends on thecaptureDenialsconfig landing first.🔗 References
Part of the Windows learning-mode /
captureDenialseffort. Follow-up PRs will add thecaptureDenialsconfig + schema, the runner integration, and ETL →DeniedResourceparsing.
🔍 Validation
cargo test -p learning_mode_windows— 5 unit tests pass.cargo clippy -p learning_mode_windows --all-targets -- -D warnings— clean.cargo fmt -p learning_mode_windows -- --check— clean.lm_probe: all trace + security-environment exports resolve under their plain(graduated) names;
LearningModeApi::load/SecurityEnvironmentApi::loadsucceed.lm_capture: the full lifecycle ran end-to-end — MXC's own FlatBuffer"SBOX"specwas accepted directly by
CreateProcessSecurityEnvironment(no text tech-speccompile), the child launched inside the environment and exited 0, and
StopLearningModeTracesealed a 3650-byte ETL.✅ Checklist
Cargo.lock, thedependency-feed-checkcheck passes (theCargo.lockchange only adds already-present workspace crates —flatbuffers,sandbox_spec— as dev-deps of the new member; no new external crates)📋 Issue Type
Microsoft Reviewers: Open in CodeFlow