Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/core/wxc_common/tests/proxy_env_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,36 @@ fn cooperative_env_scrubs_all_caller_supplied_proxy_keys() {
assert_eq!(value_for(&result, "ftp_proxy"), Some(PROXY_URL));
}

// Protects clients (b), (c), and (d): the three key-set constants are the
// contract, so this derives the expected state of every managed key from them
// instead of naming keys by hand. A hand-written per-key expectation can
// silently disagree with the constant that actually drives the behavior --
// which is how an assertion that FTP_PROXY stays absent survived FTP_PROXY
// being added to PROXY_SET_KEYS, leaving the suite asserting both at once.
#[test]
fn every_managed_key_lands_in_the_state_its_constants_dictate() {
let hostile = "http://attacker.example:9999";
let caller: Vec<String> = PROXY_ENV_KEYS
.iter()
.map(|key| format!("{key}={hostile}"))
.collect();

let result = apply_cooperative_proxy_env(&caller, PROXY_URL);

for key in PROXY_ENV_KEYS {
let expected = if PROXY_SET_KEYS.contains(key) {
Some(PROXY_URL)
} else if PROXY_NEUTRALIZE_KEYS.contains(key) {
Some("")
} else {
None
};
assert_eq!(value_for(&result, key), expected, "managed key {key}");
}

assert!(!result.iter().any(|entry| entry.contains(hostile)));
}

// Protects client (d): duplicate and mixed-case proxy keys are an obvious
// evasion attempt. Neither the second copy nor an unusual casing survives with
// the workload's value.
Expand Down
6 changes: 3 additions & 3 deletions tests/configs/wslc_filesystem.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"containerId": "wslc-filesystem-test",
"containment": "wslc",
"process": {
"commandLine": "set -e; ls -la /mnt/c/workspace >/dev/null && echo 'PASS: filesystem mount visible' || { echo 'FAIL: /mnt/c/workspace not visible'; exit 1; }; cpus=$(nproc); echo \"CPU_COUNT=$cpus\"; [ \"$cpus\" = \"2\" ] && echo 'PASS: cpuCount enforced (nproc=2)' || { echo \"FAIL: expected nproc=2, got $cpus\"; exit 1; }; mem_kb=$(awk '/MemTotal:/ {print $2}' /proc/meminfo); mem_mb=$((mem_kb/1024)); echo \"MEM_TOTAL_MB=$mem_mb\"; [ $mem_mb -ge 820 ] && [ $mem_mb -le 1075 ] && echo 'PASS: memoryMb enforced (~1024MB, kernel-visible 80-105%)' || { echo \"FAIL: expected 820-1075MB, got ${mem_mb}MB\"; exit 1; }",
"cwd": "C:\\workspace"
"commandLine": "set -e; ls -la /mnt/c/wslcfs >/dev/null && echo 'PASS: filesystem mount visible' || { echo 'FAIL: /mnt/c/wslcfs not visible'; exit 1; }; cpus=$(nproc); echo \"CPU_COUNT=$cpus\"; [ \"$cpus\" = \"2\" ] && echo 'PASS: cpuCount enforced (nproc=2)' || { echo \"FAIL: expected nproc=2, got $cpus\"; exit 1; }; mem_kb=$(awk '/MemTotal:/ {print $2}' /proc/meminfo); mem_mb=$((mem_kb/1024)); echo \"MEM_TOTAL_MB=$mem_mb\"; [ $mem_mb -ge 820 ] && [ $mem_mb -le 1075 ] && echo 'PASS: memoryMb enforced (~1024MB, kernel-visible 80-105%)' || { echo \"FAIL: expected 820-1075MB, got ${mem_mb}MB\"; exit 1; }",
"cwd": "C:\\wslcfs"
},
"filesystem": {
"readwritePaths": ["C:\\workspace"]
"readwritePaths": ["C:\\wslcfs"]
},
"network": {
"defaultPolicy": "allow"
Expand Down
4 changes: 2 additions & 2 deletions tests/configs/wslc_network_proxy.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"containerId": "wslc-network-proxy",
"containment": "wslc",
"process": {
"commandLine": "set -e; ( while true; do printf 'HTTP/1.0 200 OK\\r\\nContent-Length: 9\\r\\nConnection: close\\r\\n\\r\\nPROXY_HIT' | nc -l -p 8888 -w 2 >/dev/null 2>&1; done ) & sleep 1; echo \"PROXY_ENV HTTP_PROXY=[$HTTP_PROXY] http_proxy=[$http_proxy] NO_PROXY=[$NO_PROXY] no_proxy=[$no_proxy]\"; BODY=$(wget -q -O - http://example.com/ 2>/dev/null || true); echo \"CLIENT_GOT=[$BODY]\"; case \"$BODY\" in *PROXY_HIT*) echo WSLC_PROXY_FUNCTIONAL_OK ;; *) echo WSLC_PROXY_FAIL ;; esac",
"env": ["HTTP_PROXY=http://attacker.invalid:1", "http_proxy=http://attacker.invalid:1", "NO_PROXY=*", "no_proxy=attacker.invalid"]
"commandLine": "set -e; ( while true; do printf 'HTTP/1.0 200 OK\\r\\nContent-Length: 9\\r\\nConnection: close\\r\\n\\r\\nPROXY_HIT' | nc -l -p 8888 -w 2 >/dev/null 2>&1; done ) & sleep 1; echo \"PROXY_ENV HTTP_PROXY=[$HTTP_PROXY] http_proxy=[$http_proxy] FTP_PROXY=[$FTP_PROXY] ftp_proxy=[$ftp_proxy] NO_PROXY=[$NO_PROXY] no_proxy=[$no_proxy]\"; BODY=$(wget -q -O - http://example.com/ 2>/dev/null || true); echo \"CLIENT_GOT=[$BODY]\"; case \"$BODY\" in *PROXY_HIT*) echo WSLC_PROXY_FUNCTIONAL_OK ;; *) echo WSLC_PROXY_FAIL ;; esac",
"env": ["HTTP_PROXY=http://attacker.invalid:1", "http_proxy=http://attacker.invalid:1", "FTP_PROXY=http://attacker.invalid:1", "ftp_proxy=http://attacker.invalid:1", "NO_PROXY=*", "no_proxy=attacker.invalid"]
},
"network": {
"defaultPolicy": "allow",
Expand Down
4 changes: 2 additions & 2 deletions tests/configs/wslc_readonly_mount.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"containerId": "wslc-readonly-mount",
"containment": "wslc",
"process": {
"commandLine": "cat /mnt/c/workspace/test.txt && echo 'Read succeeded' && echo 'write test' > /mnt/c/workspace/readonly_write_test.txt && echo 'Write succeeded (unexpected)' || echo 'Write blocked (expected for readonly)'"
"commandLine": "cat /mnt/c/wslcro/test.txt && echo 'Read succeeded' && echo 'write test' > /mnt/c/wslcro/readonly_write_test.txt && echo 'Write succeeded (unexpected)' || echo 'Write blocked (expected for readonly)'"
},
"filesystem": {
"readonlyPaths": ["C:\\workspace"]
"readonlyPaths": ["C:\\wslcro"]
},
"network": {
"defaultPolicy": "block"
Expand Down
37 changes: 33 additions & 4 deletions tests/scripts/run_wslc_all_tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,39 @@ $null = $results.Add((Run-WslcTest "wslc_stderr.json" -OutputContains "stdout me
$null = $results.Add((Run-WslcTest "wslc_large_output.json"))

Write-Host "`n--- Filesystem Tests ---" -ForegroundColor Cyan
# wslc_filesystem.json also asserts cpuCount + memoryMb enforcement via nproc and /proc/meminfo.
$null = $results.Add((Run-WslcTest "wslc_filesystem.json" `
-OutputMatches "(?s)PASS: filesystem mount visible.*PASS: cpuCount enforced.*PASS: memoryMb enforced"))
$null = $results.Add((Run-WslcTest "wslc_readonly_mount.json" -OutputContains "Read succeeded"))

# Fixed paths must match tests\configs\wslc_filesystem.json and
# tests\configs\wslc_readonly_mount.json.
$fsFixtureDir = "C:\wslcfs"
$readonlyFixtureDir = "C:\wslcro"
$readonlyFixture = Join-Path $readonlyFixtureDir "test.txt"

function Remove-FilesystemFixtures {
Remove-Item -Recurse -Force $fsFixtureDir -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force $readonlyFixtureDir -ErrorAction SilentlyContinue
}

# Both filesystem configs mount a host directory the suite owns outright, so a
# run needs nothing hand-made and never touches C:\workspace, where the
# prerequisites above tell the developer to keep alpine.tar. wslc_filesystem
# only needs its mount target to exist; wslc_readonly_mount also reads
# test.txt, seeded the way the LXC and bubblewrap suites seed theirs
# (run_lxc_filesystem_test.sh:21, run_bwrap_filesystem_test.sh:25). The
# finally removes both roots, including the probe file a wrongly-writable
# mount would leave behind.
Remove-FilesystemFixtures
try {
$null = New-Item -ItemType Directory -Path $fsFixtureDir -Force
$null = New-Item -ItemType Directory -Path $readonlyFixtureDir -Force
Set-Content -Path $readonlyFixture -Value "test content" -Encoding ascii

# wslc_filesystem.json also asserts cpuCount + memoryMb enforcement via nproc and /proc/meminfo.
$null = $results.Add((Run-WslcTest "wslc_filesystem.json" `
-OutputMatches "(?s)PASS: filesystem mount visible.*PASS: cpuCount enforced.*PASS: memoryMb enforced"))
$null = $results.Add((Run-WslcTest "wslc_readonly_mount.json" -OutputContains "Read succeeded"))
} finally {
Remove-FilesystemFixtures
}

Write-Host "`n--- Object Validation Tests ---" -ForegroundColor Cyan
# Object-based validation (roadmap D6): a directory under readwritePaths and a
Expand Down
9 changes: 9 additions & 0 deletions tests/scripts/run_wslc_proxy_test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ if ($pass -and ($childLine -match 'attacker\.invalid')) {
$reason = "caller-supplied proxy env var leaked to the child (attacker.invalid not scrubbed)"
}

# The FTP family is set to the proxy as well, so an image-baked ftp_proxy
# cannot send FTP traffic somewhere the operator did not choose. Matched
# case-sensitively: -notmatch would let the upper-case entry satisfy both
# checks and leave the lower-case one untested.
if ($pass -and (($childLine -cnotmatch 'FTP_PROXY=\[http://127\.0\.0\.1:8888\]') -or ($childLine -cnotmatch 'ftp_proxy=\[http://127\.0\.0\.1:8888\]'))) {
$pass = $false
$reason = "runner did not inject/scrub the FTP proxy family (expected http://127.0.0.1:8888)"
}

# NO_PROXY / no_proxy must be neutralized to empty so an image-baked or
# caller-supplied exemption (e.g. NO_PROXY=*) cannot disable the proxy.
if ($pass -and (($childLine -notmatch 'NO_PROXY=\[\]') -or ($childLine -notmatch 'no_proxy=\[\]'))) {
Expand Down
Loading