Files
codex/codex-rs/windows-sandbox-rs/src/wfp.rs
T
iceweasel-oaiandGitHub 8121710ffe install WFP filters for Windows sandbox setup (#20101)
## Summary

This PR installs a first wave of WFP (Windows Filtering Platform)
filters that reduce the surface area of network egress vulnerabilities
for the Windows Sandbox.

- Add persistent Windows Filtering Platform provider, sublayer, and
filters for the Windows sandbox offline account.
- Install WFP filters during elevated full setup, log failures
non-fatally, and emit setup metrics when analytics are enabled.
- Bump the Windows sandbox setup version so existing users rerun full
setup and receive the new filters.

## What WFP is
Windows Filtering Platform (WFP) is the low-level Windows networking
policy engine underneath things like Windows Firewall. It lets
privileged code install persistent filtering rules at specific network
stack layers, with conditions like "only traffic from this Windows
account" or "only this remote port," and an action like block.

In this change, we create a Codex-owned persistent WFP provider and
sublayer, then install block filters scoped to the Windows sandbox's
offline user account via `ALE_USER_ID`. That means the filters are
targeted at sandboxed processes running as that account, rather than
globally affecting the host.

## Initial filter set
We are starting with 12 concrete WFP filters across a few high-value
bypass surfaces. The table below describes the filter families rather
than one filter per row:

| Area | Concrete filters | Purpose |
| --- | --- | --- |
| ICMP | 4 filters: ICMP v4/v6 on `ALE_AUTH_CONNECT` and
`ALE_RESOURCE_ASSIGNMENT` | Block direct ping-style network reachability
checks from the offline account. |
| DNS | 2 filters: remote port `53` on `ALE_AUTH_CONNECT_V4/V6` | Block
direct DNS queries that bypass our intended proxy/offline path. |
| DNS-over-TLS | 2 filters: remote port `853` on
`ALE_AUTH_CONNECT_V4/V6` | Block encrypted DNS attempts that could
bypass ordinary DNS interception. |
| SMB / NetBIOS | 4 filters: remote ports `445` and `139` on
`ALE_AUTH_CONNECT_V4/V6` | Block Windows file-sharing/network share
traffic from sandboxed processes. |

For IPv4/IPv6 coverage, the port-based filters are installed on both
`ALE_AUTH_CONNECT_V4` and `ALE_AUTH_CONNECT_V6`. ICMP also gets both
connect-layer and resource-assignment-layer coverage because ICMP
traffic is shaped differently from ordinary TCP/UDP port traffic.

## Validation
- `cargo fmt -p codex-windows-sandbox` (completed with existing
stable-rustfmt warnings about `imports_granularity = Item`)
- `cargo test -p codex-windows-sandbox wfp::tests`
- `cargo test -p codex-windows-sandbox` (fails in existing legacy
PowerShell sandbox tests because `Microsoft.PowerShell.Utility` could
not be loaded; WFP tests passed before that failure)
2026-04-30 12:39:01 -07:00

410 lines
15 KiB
Rust

#![cfg(target_os = "windows")]
#[path = "wfp_filter_specs.rs"]
mod filter_specs;
use crate::to_wide;
use anyhow::Result;
use std::ffi::OsStr;
use std::mem::zeroed;
use std::ptr::null;
use std::ptr::null_mut;
use windows_sys::core::GUID;
use windows_sys::Win32::Foundation::FWP_E_ALREADY_EXISTS;
use windows_sys::Win32::Foundation::FWP_E_FILTER_NOT_FOUND;
use windows_sys::Win32::Foundation::FWP_E_NOT_FOUND;
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Foundation::HLOCAL;
use windows_sys::Win32::Foundation::LocalFree;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_ACTRL_MATCH_FILTER;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_ACTION_BLOCK;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_BYTE_BLOB;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_VALUE0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_CONDITION_VALUE0_0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_EMPTY;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_MATCH_EQUAL;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_SECURITY_DESCRIPTOR_TYPE;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_UINT16;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_UINT8;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWP_VALUE0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_ACTION0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_ACTION0_0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_CONDITION_ALE_USER_ID;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_CONDITION_IP_PROTOCOL;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_CONDITION_IP_REMOTE_PORT;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_DISPLAY_DATA0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_FILTER0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_FILTER0_0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_FILTER_CONDITION0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_FILTER_FLAG_PERSISTENT;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_PROVIDER0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_PROVIDER_FLAG_PERSISTENT;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_SESSION0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_SUBLAYER0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FWPM_SUBLAYER_FLAG_PERSISTENT;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FwpmEngineClose0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FwpmEngineOpen0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FwpmFilterAdd0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FwpmFilterDeleteByKey0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FwpmProviderAdd0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FwpmSubLayerAdd0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FwpmTransactionAbort0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FwpmTransactionBegin0;
use windows_sys::Win32::NetworkManagement::WindowsFilteringPlatform::FwpmTransactionCommit0;
use windows_sys::Win32::Security::Authorization::BuildExplicitAccessWithNameW;
use windows_sys::Win32::Security::Authorization::BuildSecurityDescriptorW;
use windows_sys::Win32::Security::Authorization::EXPLICIT_ACCESS_W;
use windows_sys::Win32::Security::Authorization::GRANT_ACCESS;
use windows_sys::Win32::Security::PSECURITY_DESCRIPTOR;
use windows_sys::Win32::System::Rpc::RPC_C_AUTHN_DEFAULT;
use windows_sys::Win32::System::Threading::INFINITE;
use filter_specs::ConditionSpec;
use filter_specs::FILTER_SPECS;
use filter_specs::FilterSpec;
const SESSION_NAME: &str = "Codex Windows Sandbox WFP";
const PROVIDER_NAME: &str = "Codex Windows Sandbox WFP";
const PROVIDER_DESCRIPTION: &str = "Persistent WFP provider for Codex Windows sandbox filters";
const SUBLAYER_NAME: &str = "Codex Windows Sandbox WFP";
const SUBLAYER_DESCRIPTION: &str = "Persistent WFP sublayer for Codex Windows sandbox filters";
// WFP identifies persistent providers, sublayers, and filters by stable GUIDs.
// These values are Codex-owned identities; do not regenerate them unless we
// intentionally want to orphan old objects and create a new WFP namespace.
const PROVIDER_KEY: GUID = GUID::from_u128(0x2e31d31c_3948_4753_9117_e5d1a6496f41);
const SUBLAYER_KEY: GUID = GUID::from_u128(0xe65054fd_4d32_4c7c_95ef_621f0cf6431a);
/// Installs the persistent Codex WFP filters for `account`.
///
/// This is intended to run from the already-elevated setup helper. Callers
/// should treat any returned error as non-fatal to the rest of setup.
pub fn install_wfp_filters_for_account(account: &str) -> Result<usize> {
let engine = Engine::open()?;
let mut transaction = engine.begin_transaction()?;
ensure_provider(engine.handle)?;
ensure_sublayer(engine.handle)?;
let user_condition = UserMatchCondition::for_account(account)?;
let mut installed_filter_count = 0;
for spec in FILTER_SPECS {
delete_filter_if_present(engine.handle, &spec.key)?;
add_filter(engine.handle, spec, &user_condition)?;
installed_filter_count += 1;
}
transaction.commit()?;
Ok(installed_filter_count)
}
/// Owns an open WFP engine handle and closes it on drop.
struct Engine {
handle: HANDLE,
}
impl Engine {
fn open() -> Result<Self> {
let session_name = to_wide(OsStr::new(SESSION_NAME));
let mut session: FWPM_SESSION0 = unsafe { zeroed() };
session.displayData = FWPM_DISPLAY_DATA0 {
name: session_name.as_ptr() as *mut _,
description: null_mut(),
};
session.txnWaitTimeoutInMSec = INFINITE;
let mut handle = HANDLE::default();
let result = unsafe {
FwpmEngineOpen0(
null(),
RPC_C_AUTHN_DEFAULT as u32,
null(),
&session,
&mut handle,
)
};
ensure_success(result, "FwpmEngineOpen0")?;
Ok(Self { handle })
}
fn begin_transaction(&self) -> Result<Transaction<'_>> {
let result = unsafe { FwpmTransactionBegin0(self.handle, 0) };
ensure_success(result, "FwpmTransactionBegin0")?;
Ok(Transaction {
engine: self,
committed: false,
})
}
}
impl Drop for Engine {
fn drop(&mut self) {
unsafe {
FwpmEngineClose0(self.handle);
}
}
}
/// Aborts an open WFP transaction unless it was explicitly committed.
struct Transaction<'a> {
engine: &'a Engine,
committed: bool,
}
impl Transaction<'_> {
fn commit(&mut self) -> Result<()> {
let result = unsafe { FwpmTransactionCommit0(self.engine.handle) };
ensure_success(result, "FwpmTransactionCommit0")?;
self.committed = true;
Ok(())
}
}
impl Drop for Transaction<'_> {
fn drop(&mut self) {
if !self.committed {
unsafe {
FwpmTransactionAbort0(self.engine.handle);
}
}
}
}
/// Builds the ALE_USER_ID condition blob that scopes filters to one account.
struct UserMatchCondition {
security_descriptor: PSECURITY_DESCRIPTOR,
blob: FWP_BYTE_BLOB,
}
impl UserMatchCondition {
fn for_account(account: &str) -> Result<Self> {
let account_w = to_wide(OsStr::new(account));
let mut access: EXPLICIT_ACCESS_W = unsafe { zeroed() };
unsafe {
BuildExplicitAccessWithNameW(
&mut access,
account_w.as_ptr(),
FWP_ACTRL_MATCH_FILTER,
GRANT_ACCESS,
0,
);
}
let mut security_descriptor: PSECURITY_DESCRIPTOR = null_mut();
let mut security_descriptor_len = 0;
let result = unsafe {
BuildSecurityDescriptorW(
null(),
null(),
1,
&access,
0,
null(),
null_mut(),
&mut security_descriptor_len,
&mut security_descriptor,
)
};
ensure_success(result, "BuildSecurityDescriptorW")?;
Ok(Self {
security_descriptor,
blob: FWP_BYTE_BLOB {
size: security_descriptor_len,
data: security_descriptor as *mut u8,
},
})
}
}
impl Drop for UserMatchCondition {
fn drop(&mut self) {
if !self.security_descriptor.is_null() {
unsafe {
LocalFree(self.security_descriptor as HLOCAL);
}
}
}
}
/// Ensures the persistent Codex WFP provider exists.
fn ensure_provider(engine: HANDLE) -> Result<()> {
let provider_name = to_wide(OsStr::new(PROVIDER_NAME));
let provider_description = to_wide(OsStr::new(PROVIDER_DESCRIPTION));
let provider = FWPM_PROVIDER0 {
providerKey: PROVIDER_KEY,
displayData: FWPM_DISPLAY_DATA0 {
name: provider_name.as_ptr() as *mut _,
description: provider_description.as_ptr() as *mut _,
},
flags: FWPM_PROVIDER_FLAG_PERSISTENT,
providerData: empty_blob(),
serviceName: null_mut(),
};
let result = unsafe { FwpmProviderAdd0(engine, &provider, null_mut()) };
ensure_success_or(result, "FwpmProviderAdd0", &[FWP_E_ALREADY_EXISTS as u32])
}
/// Ensures the persistent Codex sublayer exists under the Codex provider.
fn ensure_sublayer(engine: HANDLE) -> Result<()> {
let sublayer_name = to_wide(OsStr::new(SUBLAYER_NAME));
let sublayer_description = to_wide(OsStr::new(SUBLAYER_DESCRIPTION));
let provider_key = PROVIDER_KEY;
let sublayer = FWPM_SUBLAYER0 {
subLayerKey: SUBLAYER_KEY,
displayData: FWPM_DISPLAY_DATA0 {
name: sublayer_name.as_ptr() as *mut _,
description: sublayer_description.as_ptr() as *mut _,
},
flags: FWPM_SUBLAYER_FLAG_PERSISTENT,
providerKey: &provider_key as *const _ as *mut _,
providerData: empty_blob(),
weight: 0x8000,
};
let result = unsafe { FwpmSubLayerAdd0(engine, &sublayer, null_mut()) };
ensure_success_or(result, "FwpmSubLayerAdd0", &[FWP_E_ALREADY_EXISTS as u32])
}
/// Adds one blocking WFP filter from the static filter spec list.
fn add_filter(engine: HANDLE, spec: &FilterSpec, user_condition: &UserMatchCondition) -> Result<()> {
let filter_name = to_wide(OsStr::new(spec.name));
let filter_description = to_wide(OsStr::new(spec.description));
let mut filter_conditions = build_conditions(spec.conditions, user_condition);
let provider_key = PROVIDER_KEY;
let filter = FWPM_FILTER0 {
filterKey: spec.key,
displayData: FWPM_DISPLAY_DATA0 {
name: filter_name.as_ptr() as *mut _,
description: filter_description.as_ptr() as *mut _,
},
flags: FWPM_FILTER_FLAG_PERSISTENT,
providerKey: &provider_key as *const _ as *mut _,
providerData: empty_blob(),
layerKey: spec.layer_key,
subLayerKey: SUBLAYER_KEY,
weight: empty_value(),
numFilterConditions: filter_conditions.len() as u32,
filterCondition: filter_conditions.as_mut_ptr(),
action: FWPM_ACTION0 {
r#type: FWP_ACTION_BLOCK,
Anonymous: FWPM_ACTION0_0 { filterType: zero_guid() },
},
Anonymous: FWPM_FILTER0_0 { rawContext: 0 },
reserved: null_mut(),
filterId: 0,
effectiveWeight: empty_value(),
};
let mut filter_id = 0_u64;
let result = unsafe { FwpmFilterAdd0(engine, &filter, null_mut(), &mut filter_id) };
ensure_success(result, &format!("FwpmFilterAdd0({})", spec.name))
}
/// Converts our compact condition specs into WFP filter conditions.
fn build_conditions(
specs: &[ConditionSpec],
user_condition: &UserMatchCondition,
) -> Vec<FWPM_FILTER_CONDITION0> {
specs
.iter()
.map(|spec| match spec {
ConditionSpec::User => FWPM_FILTER_CONDITION0 {
fieldKey: FWPM_CONDITION_ALE_USER_ID,
matchType: FWP_MATCH_EQUAL,
conditionValue: FWP_CONDITION_VALUE0 {
r#type: FWP_SECURITY_DESCRIPTOR_TYPE,
Anonymous: FWP_CONDITION_VALUE0_0 {
sd: &user_condition.blob as *const _ as *mut _,
},
},
},
ConditionSpec::Protocol(protocol) => FWPM_FILTER_CONDITION0 {
fieldKey: FWPM_CONDITION_IP_PROTOCOL,
matchType: FWP_MATCH_EQUAL,
conditionValue: FWP_CONDITION_VALUE0 {
r#type: FWP_UINT8,
Anonymous: FWP_CONDITION_VALUE0_0 { uint8: *protocol },
},
},
ConditionSpec::RemotePort(port) => FWPM_FILTER_CONDITION0 {
fieldKey: FWPM_CONDITION_IP_REMOTE_PORT,
matchType: FWP_MATCH_EQUAL,
conditionValue: FWP_CONDITION_VALUE0 {
r#type: FWP_UINT16,
Anonymous: FWP_CONDITION_VALUE0_0 { uint16: *port },
},
},
})
.collect()
}
/// Deletes an old copy of a filter before re-adding it.
fn delete_filter_if_present(engine: HANDLE, key: &GUID) -> Result<()> {
let result = unsafe { FwpmFilterDeleteByKey0(engine, key) };
ensure_success_or(
result,
"FwpmFilterDeleteByKey0",
&[FWP_E_FILTER_NOT_FOUND as u32, FWP_E_NOT_FOUND as u32],
)
}
fn ensure_success(result: u32, operation: &str) -> Result<()> {
ensure_success_or(result, operation, &[])
}
fn ensure_success_or(result: u32, operation: &str, allowed: &[u32]) -> Result<()> {
if result == 0 || allowed.contains(&result) {
Ok(())
} else {
Err(anyhow::anyhow!(
"{operation} failed: {}",
format_error_code(result)
))
}
}
fn format_error_code(result: u32) -> String {
format!("0x{result:08X}")
}
fn empty_blob() -> FWP_BYTE_BLOB {
FWP_BYTE_BLOB {
size: 0,
data: null_mut(),
}
}
fn empty_value() -> FWP_VALUE0 {
FWP_VALUE0 {
r#type: FWP_EMPTY,
Anonymous: unsafe { zeroed() },
}
}
fn zero_guid() -> GUID {
GUID::from_u128(0)
}
#[cfg(test)]
mod tests {
use super::FILTER_SPECS;
use pretty_assertions::assert_eq;
use std::collections::BTreeSet;
#[test]
fn filter_keys_are_unique() {
let keys = FILTER_SPECS
.iter()
.map(|spec| (spec.key.data1, spec.key.data2, spec.key.data3, spec.key.data4))
.collect::<BTreeSet<_>>();
assert_eq!(keys.len(), FILTER_SPECS.len());
}
#[test]
fn filter_names_are_unique() {
let names = FILTER_SPECS.iter().map(|spec| spec.name).collect::<BTreeSet<_>>();
assert_eq!(names.len(), FILTER_SPECS.len());
}
}