Skip to content

Consume the Windows Learning Mode trace API: FFI loader + capture lifecycle#661

Open
richiemsft wants to merge 13 commits into
mainfrom
user/saulg/consume-learning-mode-api
Open

Consume the Windows Learning Mode trace API: FFI loader + capture lifecycle#661
richiemsft wants to merge 13 commits into
mainfrom
user/saulg/consume-learning-mode-api

Conversation

@richiemsft

@richiemsft richiemsft commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

📖 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 upcoming
captureDenials feature (capture a sandboxed process's access denials to an ETL for
later 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):

  • Trace FFI (ffi.rs) — LearningModeApi loads/resolves StartLearningModeTrace /
    StopLearningModeTrace via LoadLibraryExW(SYSTEM32) + GetProcAddress, with a
    is_learning_mode_api_available() capability probe. Mirrors the existing
    Experimental_CreateProcessInSandbox loader pattern.
  • 2-phase security-environment FFI (secenv.rs) — SecurityEnvironmentApi
    (CreateProcessSecurityEnvironment / CreateProcessAsUserInsideSecurityEnvironment /
    CloseProcessSecurityEnvironment) that produces the HPROCESS_SECURITY_ENVIRONMENT
    handle StartLearningModeTrace keys on, plus a per-export diagnostic probe with an
    Experimental_-prefixed fallback.
  • RAII lifecycle (lifecycle.rs) — CaptureSession sequences create-env →
    start-trace → [caller launches child] → stop-trace → close-env in the required order,
    with best-effort Drop teardown (discard trace + close env) on the early-exit/unwind
    path.
  • Validation exampleslm_probe (export-resolution probe) and lm_capture (drives
    the 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 all
platforms (non-Windows is a stub).

Not in this PR (follow-ups): the captureDenials config, the production runner path
in appcontainer_common, and ETL parsing. The runner wiring will reuse BaseContainer's
FlatBuffer sandbox_spec builder and swap the one-shot launch for CaptureSession +
launch_fn; it depends on the captureDenials config landing first.

🔗 References

Part of the Windows learning-mode / captureDenials effort. Follow-up PRs will add the
captureDenials config + schema, the runner integration, and ETL → DeniedResource
parsing.

🔍 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.
  • Hardware-validated on a GE_CURRENT DirectWinPD x64 build:
    • lm_probe: all trace + security-environment exports resolve under their plain
      (graduated) names; LearningModeApi::load / SecurityEnvironmentApi::load succeed.
    • lm_capture: the full lifecycle ran end-to-end — MXC's own FlatBuffer "SBOX" spec
      was accepted directly by CreateProcessSecurityEnvironment (no text tech-spec
      compile), the child launched inside the environment and exited 0, and
      StopLearningModeTrace sealed a 3650-byte ETL.

✅ Checklist

  • Signed the Contributor License Agreement
  • Linked to an issue
  • Updated documentation (if applicable)
  • Updated Copilot instructions (if build, architecture, or conventions changed)
  • If this PR changes Cargo.lock, the dependency-feed-check check passes (the Cargo.lock change only adds already-present workspace crates — flatbuffers, sandbox_spec — as dev-deps of the new member; no new external crates)

📋 Issue Type

  • Bug fix
  • Feature
  • Task
Microsoft Reviewers: Open in CodeFlow

richiemsft and others added 5 commits July 20, 2026 13:00
…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>
@richiemsft
richiemsft requested a review from a team as a code owner July 20, 2026 20:03
Copilot AI review requested due to automatic review settings July 20, 2026 20:03
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_windows as 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 CaptureSession to sequence create-env → start-trace → stop-trace → close-env, along with lm_probe/lm_capture validation 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.

Comment thread src/backends/learning_mode/windows/src/lifecycle.rs
Comment thread src/backends/learning_mode/windows/src/ffi.rs Outdated
- 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>
@richiemsft

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
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
Copilot AI review requested due to automatic review settings July 23, 2026 21:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.

Comment thread src/backends/learning_mode/windows/src/lifecycle.rs Outdated
Comment thread src/backends/learning_mode/windows/src/lib.rs
Comment thread src/backends/learning_mode/windows/src/lifecycle.rs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
Copilot AI review requested due to automatic review settings July 23, 2026 22:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Drop path 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 verify Drop calls stop_trace(None) before close, 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.

Comment thread src/backends/learning_mode/windows/src/secenv.rs
Comment thread src/Cargo.toml
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
Copilot AI review requested due to automatic review settings July 23, 2026 22:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 successful finish path. 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) {

Comment thread src/backends/learning_mode/windows/src/ffi.rs Outdated
Comment thread src/backends/learning_mode/windows/src/lifecycle.rs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
Copilot AI review requested due to automatic review settings July 23, 2026 23:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated 1 comment.

Comment thread src/backends/learning_mode/windows/src/secenv.rs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
Copilot AI review requested due to automatic review settings July 23, 2026 23:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 that Drop calls StopLearningModeTrace(..., NULL) before CloseProcessSecurityEnvironment, 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);

Comment thread src/backends/learning_mode/windows/src/secenv.rs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: dfea4d42-e8a5-42ef-978a-6434e8bf89ec
Copilot AI review requested due to automatic review settings July 24, 2026 00:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants