Codex Handbook
windows-sandbox-rs/src/bin/setup_main/win/firewall.rs 605 lines
use anyhow::Result;use std::io::Write;use windows::Win32::Foundation::S_OK;use windows::Win32::Foundation::VARIANT_TRUE;use windows::Win32::NetworkManagement::WindowsFirewall::INetFwPolicy2;use windows::Win32::NetworkManagement::WindowsFirewall::INetFwRule3;use windows::Win32::NetworkManagement::WindowsFirewall::INetFwRules;use windows::Win32::NetworkManagement::WindowsFirewall::NET_FW_ACTION_BLOCK;use windows::Win32::NetworkManagement::WindowsFirewall::NET_FW_IP_PROTOCOL_ANY;use windows::Win32::NetworkManagement::WindowsFirewall::NET_FW_IP_PROTOCOL_TCP;use windows::Win32::NetworkManagement::WindowsFirewall::NET_FW_IP_PROTOCOL_UDP;use windows::Win32::NetworkManagement::WindowsFirewall::NET_FW_MODIFY_STATE;use windows::Win32::NetworkManagement::WindowsFirewall::NET_FW_MODIFY_STATE_OK;use windows::Win32::NetworkManagement::WindowsFirewall::NET_FW_PROFILE2_ALL;use windows::Win32::NetworkManagement::WindowsFirewall::NET_FW_RULE_DIR_OUT;use windows::Win32::NetworkManagement::WindowsFirewall::NetFwPolicy2;use windows::Win32::NetworkManagement::WindowsFirewall::NetFwRule;use windows::Win32::System::Com::CLSCTX_INPROC_SERVER;use windows::Win32::System::Com::COINIT_APARTMENTTHREADED;use windows::Win32::System::Com::CoCreateInstance;use windows::Win32::System::Com::CoInitializeEx;use windows::Win32::System::Com::CoUninitialize;use windows::core::BSTR;use windows::core::Interface;use codex_windows_sandbox::SetupErrorCode;use codex_windows_sandbox::SetupFailure;// This is the stable identifier we use to find/update the rule idempotently.// It intentionally does not change between installs.const OFFLINE_BLOCK_RULE_NAME: &str = "codex_sandbox_offline_block_outbound";const OFFLINE_BLOCK_LOOPBACK_TCP_RULE_NAME: &str = "codex_sandbox_offline_block_loopback_tcp";const OFFLINE_BLOCK_LOOPBACK_UDP_RULE_NAME: &str = "codex_sandbox_offline_block_loopback_udp";// Friendly text shown in the firewall UI.const OFFLINE_BLOCK_RULE_FRIENDLY: &str = "Codex Sandbox Offline - Block Non-Loopback Outbound";const OFFLINE_BLOCK_LOOPBACK_TCP_RULE_FRIENDLY: &str =    "Codex Sandbox Offline - Block Loopback TCP (Except Proxy)";const OFFLINE_BLOCK_LOOPBACK_UDP_RULE_FRIENDLY: &str = "Codex Sandbox Offline - Block Loopback UDP";const OFFLINE_PROXY_ALLOW_RULE_NAME: &str = "codex_sandbox_offline_allow_loopback_proxy";const LOOPBACK_REMOTE_ADDRESSES: &str = "127.0.0.0/8,::/127";const NON_LOOPBACK_REMOTE_ADDRESSES: &str = "0.0.0.0-126.255.255.255,128.0.0.0-255.255.255.255,::,::2-ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff";struct BlockRuleSpec<'a> {    internal_name: &'a str,    friendly_desc: &'a str,    protocol: i32,    local_user_spec: &'a str,    offline_sid: &'a str,    remote_addresses: Option<&'a str>,    remote_ports: Option<&'a str>,}pub fn ensure_offline_proxy_allowlist(    offline_sid: &str,    proxy_ports: &[u16],    allow_local_binding: bool,    log: &mut dyn Write,) -> Result<()> {    let local_user_spec = format!("O:LSD:(A;;CC;;;{offline_sid})");    let hr = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) };    if hr.is_err() {        return Err(anyhow::Error::new(SetupFailure::new(            SetupErrorCode::HelperFirewallComInitFailed,            format!("CoInitializeEx failed: {hr:?}"),        )));    }    let result = unsafe {        (|| -> Result<()> {            let policy: INetFwPolicy2 = CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER)                .map_err(|err| {                    anyhow::Error::new(SetupFailure::new(                        SetupErrorCode::HelperFirewallPolicyAccessFailed,                        format!("CoCreateInstance NetFwPolicy2 failed: {err:?}"),                    ))                })?;            ensure_local_policy_rules_take_effect(&policy)?;            let rules = policy.Rules().map_err(|err| {                anyhow::Error::new(SetupFailure::new(                    SetupErrorCode::HelperFirewallPolicyAccessFailed,                    format!("INetFwPolicy2::Rules failed: {err:?}"),                ))            })?;            if allow_local_binding {                // Remove the legacy overlapping allow rule before returning to the local-binding                // mode so stale proxy exceptions do not linger.                remove_rule_if_present(&rules, OFFLINE_PROXY_ALLOW_RULE_NAME, log)?;                remove_rule_if_present(&rules, OFFLINE_BLOCK_LOOPBACK_UDP_RULE_NAME, log)?;                remove_rule_if_present(&rules, OFFLINE_BLOCK_LOOPBACK_TCP_RULE_NAME, log)?;                return Ok(());            }            ensure_block_rule(                &rules,                &BlockRuleSpec {                    internal_name: OFFLINE_BLOCK_LOOPBACK_UDP_RULE_NAME,                    friendly_desc: OFFLINE_BLOCK_LOOPBACK_UDP_RULE_FRIENDLY,                    protocol: NET_FW_IP_PROTOCOL_UDP.0,                    local_user_spec: &local_user_spec,                    offline_sid,                    remote_addresses: Some(LOOPBACK_REMOTE_ADDRESSES),                    remote_ports: None,                },                log,            )?;            // Install a broad TCP loopback block before narrowing it to the allowed proxy port            // complement. If the narrowing update fails, the sandbox remains fail-closed.            ensure_block_rule(                &rules,                &BlockRuleSpec {                    internal_name: OFFLINE_BLOCK_LOOPBACK_TCP_RULE_NAME,                    friendly_desc: OFFLINE_BLOCK_LOOPBACK_TCP_RULE_FRIENDLY,                    protocol: NET_FW_IP_PROTOCOL_TCP.0,                    local_user_spec: &local_user_spec,                    offline_sid,                    remote_addresses: Some(LOOPBACK_REMOTE_ADDRESSES),                    remote_ports: None,                },                log,            )?;            // Remove the legacy overlapping allow rule only after the explicit block rules are in            // place so transitions back to proxy-only mode do not fail open.            remove_rule_if_present(&rules, OFFLINE_PROXY_ALLOW_RULE_NAME, log)?;            if let Some(blocked_remote_ports) = blocked_loopback_tcp_remote_ports(proxy_ports) {                ensure_block_rule(                    &rules,                    &BlockRuleSpec {                        internal_name: OFFLINE_BLOCK_LOOPBACK_TCP_RULE_NAME,                        friendly_desc: OFFLINE_BLOCK_LOOPBACK_TCP_RULE_FRIENDLY,                        protocol: NET_FW_IP_PROTOCOL_TCP.0,                        local_user_spec: &local_user_spec,                        offline_sid,                        remote_addresses: Some(LOOPBACK_REMOTE_ADDRESSES),                        remote_ports: Some(&blocked_remote_ports),                    },                    log,                )?;            }            Ok(())        })()    };    unsafe {        CoUninitialize();    }    result}pub fn ensure_offline_outbound_block(offline_sid: &str, log: &mut dyn Write) -> Result<()> {    let local_user_spec = format!("O:LSD:(A;;CC;;;{offline_sid})");    let hr = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) };    if hr.is_err() {        return Err(anyhow::Error::new(SetupFailure::new(            SetupErrorCode::HelperFirewallComInitFailed,            format!("CoInitializeEx failed: {hr:?}"),        )));    }    let result = unsafe {        (|| -> Result<()> {            let policy: INetFwPolicy2 = CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER)                .map_err(|err| {                    anyhow::Error::new(SetupFailure::new(                        SetupErrorCode::HelperFirewallPolicyAccessFailed,                        format!("CoCreateInstance NetFwPolicy2 failed: {err:?}"),                    ))                })?;            ensure_local_policy_rules_take_effect(&policy)?;            let rules = policy.Rules().map_err(|err| {                anyhow::Error::new(SetupFailure::new(                    SetupErrorCode::HelperFirewallPolicyAccessFailed,                    format!("INetFwPolicy2::Rules failed: {err:?}"),                ))            })?;            // Block all outbound IP protocols for this user.            ensure_block_rule(                &rules,                &BlockRuleSpec {                    internal_name: OFFLINE_BLOCK_RULE_NAME,                    friendly_desc: OFFLINE_BLOCK_RULE_FRIENDLY,                    protocol: NET_FW_IP_PROTOCOL_ANY.0,                    local_user_spec: &local_user_spec,                    offline_sid,                    remote_addresses: Some(NON_LOOPBACK_REMOTE_ADDRESSES),                    remote_ports: None,                },                log,            )?;            Ok(())        })()    };    unsafe {        CoUninitialize();    }    result}fn remove_rule_if_present(    rules: &INetFwRules,    internal_name: &str,    log: &mut dyn Write,) -> Result<()> {    let name = BSTR::from(internal_name);    if unsafe { rules.Item(&name) }.is_ok() {        unsafe { rules.Remove(&name) }.map_err(|err| {            anyhow::Error::new(SetupFailure::new(                SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                format!("Rules::Remove failed for {internal_name}: {err:?}"),            ))        })?;        log_line(log, &format!("firewall rule removed name={internal_name}"))?;    }    Ok(())}fn ensure_local_policy_rules_take_effect(policy: &INetFwPolicy2) -> Result<()> {    let mut modify_state = NET_FW_MODIFY_STATE::default();    let result = unsafe {        (Interface::vtable(policy).LocalPolicyModifyState)(            Interface::as_raw(policy),            &mut modify_state,        )    };    validate_local_policy_modify_result(result, modify_state)}fn validate_local_policy_modify_result(    result: windows::core::HRESULT,    modify_state: NET_FW_MODIFY_STATE,) -> Result<()> {    if result.is_err() {        // The COM query itself failed, so Windows never gave us a policy answer.        return Err(anyhow::Error::new(SetupFailure::new(            SetupErrorCode::HelperFirewallPolicyAccessFailed,            format!("INetFwPolicy2::LocalPolicyModifyState failed: {result:?}"),        )));    }    if result != S_OK {        // S_FALSE means the answer only holds for some active profiles, not all of them.        return Err(anyhow::Error::new(SetupFailure::new(            SetupErrorCode::HelperFirewallPolicyIneffective,            format!(                "local firewall policy modifications do not apply to every current profile: LocalPolicyModifyState result={result:?}"            ),        )));    }    if modify_state == NET_FW_MODIFY_STATE_OK {        return Ok(());    }    // Windows answered uniformly, and that answer says local rule edits are ineffective.    Err(anyhow::Error::new(SetupFailure::new(        SetupErrorCode::HelperFirewallPolicyIneffective,        format!(            "local firewall policy modifications will not take effect: LocalPolicyModifyState={modify_state:?}"        ),    )))}fn ensure_block_rule(    rules: &INetFwRules,    spec: &BlockRuleSpec<'_>,    log: &mut dyn Write,) -> Result<()> {    let name = BSTR::from(spec.internal_name);    let rule: INetFwRule3 = match unsafe { rules.Item(&name) } {        Ok(existing) => existing.cast().map_err(|err| {            anyhow::Error::new(SetupFailure::new(                SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                format!("cast existing firewall rule to INetFwRule3 failed: {err:?}"),            ))        })?,        Err(_) => {            let new_rule: INetFwRule3 =                unsafe { CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER) }.map_err(                    |err| {                        anyhow::Error::new(SetupFailure::new(                            SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                            format!("CoCreateInstance NetFwRule failed: {err:?}"),                        ))                    },                )?;            unsafe { new_rule.SetName(&name) }.map_err(|err| {                anyhow::Error::new(SetupFailure::new(                    SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                    format!("SetName failed: {err:?}"),                ))            })?;            // Set all properties before adding the rule so we don't leave half-configured rules.            configure_rule(&new_rule, spec)?;            unsafe { rules.Add(&new_rule) }.map_err(|err| {                anyhow::Error::new(SetupFailure::new(                    SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                    format!("Rules::Add failed: {err:?}"),                ))            })?;            new_rule        }    };    // Always re-apply fields to keep the setup idempotent.    configure_rule(&rule, spec)?;    let remote_addresses_log = spec.remote_addresses.unwrap_or("*");    let remote_ports_log = spec.remote_ports.unwrap_or("*");    log_line(        log,        &format!(            "firewall rule configured name={} protocol={} RemoteAddresses={remote_addresses_log} RemotePorts={remote_ports_log} LocalUserAuthorizedList={}",            spec.internal_name, spec.protocol, spec.local_user_spec        ),    )?;    Ok(())}fn configure_rule(rule: &INetFwRule3, spec: &BlockRuleSpec<'_>) -> Result<()> {    unsafe {        rule.SetDescription(&BSTR::from(spec.friendly_desc))            .map_err(|err| {                anyhow::Error::new(SetupFailure::new(                    SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                    format!("SetDescription failed: {err:?}"),                ))            })?;        rule.SetDirection(NET_FW_RULE_DIR_OUT).map_err(|err| {            anyhow::Error::new(SetupFailure::new(                SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                format!("SetDirection failed: {err:?}"),            ))        })?;        rule.SetAction(NET_FW_ACTION_BLOCK).map_err(|err| {            anyhow::Error::new(SetupFailure::new(                SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                format!("SetAction failed: {err:?}"),            ))        })?;        rule.SetEnabled(VARIANT_TRUE).map_err(|err| {            anyhow::Error::new(SetupFailure::new(                SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                format!("SetEnabled failed: {err:?}"),            ))        })?;        rule.SetProfiles(NET_FW_PROFILE2_ALL.0).map_err(|err| {            anyhow::Error::new(SetupFailure::new(                SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                format!("SetProfiles failed: {err:?}"),            ))        })?;        configure_rule_network_scope(rule, spec)?;        rule.SetLocalUserAuthorizedList(&BSTR::from(spec.local_user_spec))            .map_err(|err| {                anyhow::Error::new(SetupFailure::new(                    SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                    format!("SetLocalUserAuthorizedList failed: {err:?}"),                ))            })?;    }    // Read-back verification: ensure we actually wrote the expected SID scope.    let actual = unsafe { rule.LocalUserAuthorizedList() }.map_err(|err| {        anyhow::Error::new(SetupFailure::new(            SetupErrorCode::HelperFirewallRuleVerifyFailed,            format!("LocalUserAuthorizedList (read-back) failed: {err:?}"),        ))    })?;    let actual_str = actual.to_string();    if !actual_str.contains(spec.offline_sid) {        return Err(anyhow::Error::new(SetupFailure::new(            SetupErrorCode::HelperFirewallRuleVerifyFailed,            format!(                "offline firewall rule user scope mismatch: expected SID {}, got {actual_str}",                spec.offline_sid            ),        )));    }    Ok(())}fn configure_rule_network_scope(rule: &INetFwRule3, spec: &BlockRuleSpec<'_>) -> Result<()> {    unsafe {        rule.SetProtocol(spec.protocol).map_err(|err| {            anyhow::Error::new(SetupFailure::new(                SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                format!("SetProtocol failed: {err:?}"),            ))        })?;        let remote_addresses = spec.remote_addresses.unwrap_or("*");        rule.SetRemoteAddresses(&BSTR::from(remote_addresses))            .map_err(|err| {                anyhow::Error::new(SetupFailure::new(                    SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                    format!("SetRemoteAddresses failed: {err:?}"),                ))            })?;        if let Some(remote_ports) = spec.remote_ports {            rule.SetRemotePorts(&BSTR::from(remote_ports))                .map_err(|err| {                    anyhow::Error::new(SetupFailure::new(                        SetupErrorCode::HelperFirewallRuleCreateOrAddFailed,                        format!("SetRemotePorts failed: {err:?}"),                    ))                })?;        }    }    Ok(())}fn blocked_loopback_tcp_remote_ports(proxy_ports: &[u16]) -> Option<String> {    let mut allowed_ports = proxy_ports        .iter()        .copied()        .filter(|port| *port != 0)        .collect::<Vec<_>>();    allowed_ports.sort_unstable();    allowed_ports.dedup();    let mut blocked_ranges = Vec::new();    let mut start = 1_u32;    for port in allowed_ports {        let port = u32::from(port);        if port < start {            continue;        }        if port > start {            blocked_ranges.push(port_range_string(start, port - 1));        }        start = port + 1;    }    if start <= u32::from(u16::MAX) {        blocked_ranges.push(port_range_string(start, u32::from(u16::MAX)));    }    if blocked_ranges.is_empty() {        None    } else {        Some(blocked_ranges.join(","))    }}fn port_range_string(start: u32, end: u32) -> String {    if start == end {        start.to_string()    } else {        format!("{start}-{end}")    }}fn log_line(log: &mut dyn Write, msg: &str) -> Result<()> {    let ts = chrono::Utc::now().to_rfc3339();    writeln!(log, "[{ts}] {msg}")?;    Ok(())}#[cfg(test)]mod tests {    use pretty_assertions::assert_eq;    use windows::Win32::Foundation::S_FALSE;    use windows::Win32::NetworkManagement::WindowsFirewall::NET_FW_MODIFY_STATE_GP_OVERRIDE;    use super::*;    #[test]    fn configured_remote_address_literals_are_accepted_by_firewall_com() {        let hr = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) };        assert!(hr.is_ok(), "CoInitializeEx failed: {hr:?}");        let candidates = [            LOOPBACK_REMOTE_ADDRESSES,            NON_LOOPBACK_REMOTE_ADDRESSES,            "*",        ];        let results = candidates.map(|remote_addresses| unsafe {            let rule: windows::core::Result<INetFwRule3> =                CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER);            rule.and_then(|rule| {                rule.SetRemoteAddresses(&BSTR::from(remote_addresses))?;                rule.RemoteAddresses()            })            .map(|stored| stored.to_string())        });        unsafe {            CoUninitialize();        }        for (remote_addresses, result) in candidates.into_iter().zip(results) {            assert!(                result.is_ok(),                "firewall rejected RemoteAddresses={remote_addresses:?}: {result:?}"            );        }    }    #[test]    fn production_firewall_rule_network_scopes_are_accepted_by_firewall_com() {        let hr = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) };        assert!(hr.is_ok(), "CoInitializeEx failed: {hr:?}");        let local_user_spec = "O:LSD:(A;;CC;;;S-1-5-18)";        let offline_sid = "S-1-5-18";        let blocked_remote_ports =            blocked_loopback_tcp_remote_ports(&[8080]).expect("proxy-port complement should exist");        let specs = [            BlockRuleSpec {                internal_name: OFFLINE_BLOCK_LOOPBACK_UDP_RULE_NAME,                friendly_desc: OFFLINE_BLOCK_LOOPBACK_UDP_RULE_FRIENDLY,                protocol: NET_FW_IP_PROTOCOL_UDP.0,                local_user_spec,                offline_sid,                remote_addresses: Some(LOOPBACK_REMOTE_ADDRESSES),                remote_ports: None,            },            BlockRuleSpec {                internal_name: OFFLINE_BLOCK_LOOPBACK_TCP_RULE_NAME,                friendly_desc: OFFLINE_BLOCK_LOOPBACK_TCP_RULE_FRIENDLY,                protocol: NET_FW_IP_PROTOCOL_TCP.0,                local_user_spec,                offline_sid,                remote_addresses: Some(LOOPBACK_REMOTE_ADDRESSES),                remote_ports: Some(&blocked_remote_ports),            },            BlockRuleSpec {                internal_name: OFFLINE_BLOCK_RULE_NAME,                friendly_desc: OFFLINE_BLOCK_RULE_FRIENDLY,                protocol: NET_FW_IP_PROTOCOL_ANY.0,                local_user_spec,                offline_sid,                remote_addresses: Some(NON_LOOPBACK_REMOTE_ADDRESSES),                remote_ports: None,            },        ];        let results = specs.each_ref().map(|spec| unsafe {            let rule: windows::core::Result<INetFwRule3> =                CoCreateInstance(&NetFwRule, None, CLSCTX_INPROC_SERVER);            match rule {                Ok(rule) => configure_rule_network_scope(&rule, spec),                Err(err) => Err(err.into()),            }        });        unsafe {            CoUninitialize();        }        for (spec, result) in specs.into_iter().zip(results) {            assert!(                result.is_ok(),                "firewall rejected network scope for rule={} protocol={} remote_addresses={:?} remote_ports={:?}: {result:?}",                spec.internal_name,                spec.protocol,                spec.remote_addresses,                spec.remote_ports            );        }    }    #[test]    fn local_policy_modify_state_accepts_effective_policy() {        assert!(validate_local_policy_modify_result(S_OK, NET_FW_MODIFY_STATE_OK).is_ok());    }    #[test]    fn local_policy_modify_state_rejects_ineffective_policy() {        let err = validate_local_policy_modify_result(S_OK, NET_FW_MODIFY_STATE_GP_OVERRIDE)            .expect_err("group-policy override should fail sandbox firewall setup");        let failure = err            .downcast_ref::<SetupFailure>()            .expect("expected setup failure");        assert_eq!(            failure.code,            SetupErrorCode::HelperFirewallPolicyIneffective        );    }    #[test]    fn local_policy_modify_state_rejects_partial_profile_coverage() {        let err = validate_local_policy_modify_result(S_FALSE, NET_FW_MODIFY_STATE_OK)            .expect_err("partial profile coverage should fail sandbox firewall setup");        let failure = err            .downcast_ref::<SetupFailure>()            .expect("expected setup failure");        assert_eq!(            failure.code,            SetupErrorCode::HelperFirewallPolicyIneffective        );    }}