# How MXC Recovers Orphaned DACL State: Crash-Safe ACL Restoration

> Learn how MXC recovers orphaned DACL state by scanning crash-safe JSON files, verifying process liveness, and reverting ACLs for terminated processes ensuring secure sandbox operations.

- Repository: [Microsoft/mxc](https://github.com/microsoft/mxc)
- Tags: internals
- Published: 2026-06-07

---

**MXC recovers orphaned DACL state by scanning crash-safe JSON state files at startup, validating process liveness via PID and image name verification, and automatically reverting access control entries for terminated processes before any new sandbox operations begin.**

When the Microsoft MXC (Microsoft eXperience Container) sandbox modifies host filesystem permissions, a system crash can leave Discretionary Access Control Lists (DACLs) in a partially modified state. To prevent permanent security drift, the `microsoft/mxc` repository implements a deterministic orphaned-state recovery mechanism that runs automatically on every startup. This article examines the crash-safe state persistence, process liveness validation, and atomic cleanup logic that ensures host integrity even after unexpected termination.

## The Orphaned State Problem

MXC protects the host filesystem by augmenting directory DACLs for AppContainer sandboxes. Because these modifications are applied after state persistence, a crash between writing the state file and completing the ACL changes—or between applying ACLs and cleaning up—leaves orphaned modifications on the host. These orphaned entries could permanently alter access permissions if not automatically detected and reverted.

## Crash-Safe State File Architecture

### Atomic Write Mechanism

Before each Access Control Entry (ACE) is applied, the `DaclManager` writes a complete description of the pending change to a JSON state file. The implementation in [`src/core/wxc_common/src/filesystem_dacl.rs`](https://github.com/microsoft/mxc/blob/main/src/core/wxc_common/src/filesystem_dacl.rs) uses crash-safe atomic writes: data is first written to a temporary `<run-id>.json.tmp` file, explicitly fsynced, then renamed to `<run-id>.json`. This ensures that a valid state file exists only when the write is complete, as implemented in the `write_state_file` function (lines 33-42).

### State File Location and Format

State files are stored in `%LOCALAPPDATA%\Microsoft\MXC\dacl-restore` by default, or under the path specified by the `MXC_DACL_STATE_DIR` environment variable. Each file is named with a unique **run-id** and contains the full description of pending DACL modifications, enabling precise restoration of the original access control state.

## Startup Recovery Hook

At the very beginning of `wxc::main`, MXC calls `recover_orphaned_state()` (defined in [`src/core/wxc/src/main.rs`](https://github.com/microsoft/mxc/blob/main/src/core/wxc/src/main.rs), lines 40-47). This execution occurs before any COM initialization or sandbox probing, guaranteeing that leftover state is cleaned up before new work begins. If recovery fails, the process logs the error and continues, ensuring that startup is not blocked by transient filesystem issues.

## The Recovery Process

### Scanning and Parsing State Files

The `recover_orphaned_state` function opens the state directory and iterates over every `.json` entry (lines 5-25 in [`filesystem_dacl.rs`](https://github.com/microsoft/mxc/blob/main/filesystem_dacl.rs)). For each file:

- It attempts to parse the JSON into a `StateFile` structure
- If parsing fails, the file is renamed to `.json.corrupt` for operator inspection (lines 26-35), preventing crash loops from malformed data

### Process Liveness Validation

Before restoring any ACEs, MXC verifies that the owning process is actually dead to avoid interfering with active sandboxes. The `process_alive_with_image` function (lines 95-114) performs three validation checks:

1. **PID validation**: Ensures the stored Process ID is non-zero
2. **Image name matching**: Compares the stored `image_name` against the actual process image
3. **Creation timestamp verification**: Validates the recorded `FILETIME` creation time against the live process, preventing false positives from PID reuse

This multi-factor check ensures that MXC never restores DACLs for a process that has legitimately recycled a previously used PID.

### ACE Restoration and Cleanup

For processes confirmed dead, the manager calls `restore_one` to revert each previously applied ACE (lines 64-78). The function tracks:

- `aces_restored`: Successfully reverted entries
- `report.errors`: Human-readable diagnostics for failures

If restoration succeeds for all ACEs in a state file, the file is deleted. If some ACEs fail due to transient errors like file-handle contention, a new state file is written containing only the still-pending entries, ensuring idempotent retry on the next startup (lines 79-86).

## Implementation Example

The following Rust code demonstrates how to invoke the recovery mechanism programmatically:

```rust
use wxc_common::filesystem_dacl::{recover_orphaned_state, RecoveryReport};

fn main() {
    match recover_orphaned_state() {
        Ok(report) => {
            println!(
                "DACL recovery: {} file(s) processed, {} ACE(s) restored",
                report.files_processed, report.aces_restored
            );
            for err in report.errors {
                eprintln!("Recovery error: {err}");
            }
        }
        Err(e) => eprintln!("Fatal recovery failure: {e}"),
    }
}

```

For testing scenarios, you can verify cleanup behavior after deliberately killing a sandbox process:

```rust
#[test]
fn orphan_cleanup() {
    // … spawn a sandbox, apply some DACLs, then kill the process …
    let report = recover_orphaned_state().expect("recovery should not panic");
    assert!(report.aces_restored > 0);
}

```

## Summary

- MXC writes crash-safe JSON state files before applying DACL modifications, using atomic rename operations in `write_state_file` to guarantee file integrity
- The `recover_orphaned_state` function runs unconditionally at startup in `wxc::main`, processing all pending state files before any sandbox operations begin
- Process liveness validation uses PID, image name, and creation timestamp to prevent accidental restoration against recycled process identifiers
- Failed restorations generate new state files for idempotent retry, while successful cleanups delete the original files and report statistics via `RecoveryReport`

## Frequently Asked Questions

### What happens if the JSON state file is corrupted?

If `recover_orphaned_state` encounters a file that cannot be parsed as valid JSON, it renames the file to `.json.corrupt` and continues processing other files. This prevents the recovery mechanism from crashing while preserving the corrupted data for operator inspection.

### How does MXC prevent restoring DACLs for a process that reused a PID?

The `process_alive_with_image` function validates three criteria: non-zero PID, matching process image name, and identical creation `FILETIME`. By comparing the stored creation timestamp against the live process's actual creation time, MXC distinguishes between the original process and a new process that happens to reuse the same PID.

### Where are the orphaned state files stored?

State files are stored in `%LOCALAPPDATA%\Microsoft\MXC\dacl-restore` by default. Administrators can override this location by setting the `MXC_DACL_STATE_DIR` environment variable before launching the MXC process.

### Is the recovery process idempotent?

Yes. If restoration of specific ACEs fails due to transient errors like file-handle contention, MXC writes a new state file containing only the pending entries. On the next startup, the recovery process will attempt to restore these remaining ACEs again, continuing until all orphaned modifications are successfully reverted or the files are manually cleared.