Skip to content

Commit 255b479

Browse files
committed
ci: sync bundled winredirect drivers
1 parent 9b306c0 commit 255b479

6 files changed

Lines changed: 320 additions & 1 deletion

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env python3
2+
3+
from __future__ import annotations
4+
5+
import argparse
6+
import hashlib
7+
import sys
8+
from pathlib import Path
9+
10+
11+
class PEFormatError(ValueError):
12+
pass
13+
14+
15+
def read_u16(data: bytes, offset: int) -> int:
16+
return int.from_bytes(data[offset:offset + 2], "little")
17+
18+
19+
def read_u32(data: bytes, offset: int) -> int:
20+
return int.from_bytes(data[offset:offset + 4], "little")
21+
22+
23+
def canonicalize_signed_pe(path: Path) -> bytes:
24+
data = bytearray(path.read_bytes())
25+
if len(data) < 0x40 or data[:2] != b"MZ":
26+
raise PEFormatError(f"{path}: missing DOS header")
27+
28+
pe_offset = read_u32(data, 0x3C)
29+
if pe_offset + 24 > len(data) or data[pe_offset:pe_offset + 4] != b"PE\x00\x00":
30+
raise PEFormatError(f"{path}: missing PE header")
31+
32+
optional_offset = pe_offset + 24
33+
magic = read_u16(data, optional_offset)
34+
if magic == 0x10B:
35+
data_directory_offset = optional_offset + 96
36+
elif magic == 0x20B:
37+
data_directory_offset = optional_offset + 112
38+
else:
39+
raise PEFormatError(f"{path}: unsupported optional header magic 0x{magic:04x}")
40+
41+
checksum_offset = optional_offset + 64
42+
if checksum_offset + 4 > len(data):
43+
raise PEFormatError(f"{path}: truncated optional header")
44+
data[checksum_offset:checksum_offset + 4] = b"\x00" * 4
45+
46+
security_directory_offset = data_directory_offset + 8 * 4
47+
if security_directory_offset + 8 > len(data):
48+
raise PEFormatError(f"{path}: truncated data directories")
49+
50+
certificate_offset = read_u32(data, security_directory_offset)
51+
certificate_size = read_u32(data, security_directory_offset + 4)
52+
data[security_directory_offset:security_directory_offset + 8] = b"\x00" * 8
53+
54+
if certificate_offset == 0 or certificate_size == 0:
55+
return bytes(data)
56+
57+
certificate_end = certificate_offset + certificate_size
58+
if certificate_end > len(data):
59+
raise PEFormatError(f"{path}: certificate table exceeds file size")
60+
61+
return bytes(data[:certificate_offset] + data[certificate_end:])
62+
63+
64+
def canonical_sha256(path: Path) -> str:
65+
return hashlib.sha256(canonicalize_signed_pe(path)).hexdigest()
66+
67+
68+
def main() -> int:
69+
parser = argparse.ArgumentParser(
70+
description="Compare two PE files after stripping Authenticode-only differences.",
71+
)
72+
parser.add_argument("left", type=Path)
73+
parser.add_argument("right", type=Path)
74+
args = parser.parse_args()
75+
76+
try:
77+
left_hash = canonical_sha256(args.left)
78+
right_hash = canonical_sha256(args.right)
79+
except (OSError, PEFormatError) as exc:
80+
print(exc, file=sys.stderr)
81+
return 2
82+
83+
print(f"{args.left}: {left_hash}")
84+
print(f"{args.right}: {right_hash}")
85+
return 0 if left_hash == right_hash else 1
86+
87+
88+
if __name__ == "__main__":
89+
raise SystemExit(main())
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
name: sync winredirect driver
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
- dev
8+
paths:
9+
- '.github/scripts/compare_signed_pe.py'
10+
- '.github/workflows/winredirect-driver-sync.yml'
11+
- 'internal/winredirect/driver/**'
12+
13+
permissions:
14+
contents: write
15+
16+
concurrency:
17+
group: winredirect-driver-${{ github.event.pull_request.head.repo.full_name }}-${{ github.event.pull_request.head.ref }}
18+
cancel-in-progress: true
19+
20+
jobs:
21+
sync:
22+
name: Refresh bundled drivers
23+
if: github.event.pull_request.head.repo.full_name == github.repository
24+
runs-on: windows-2025
25+
env:
26+
REPO_DIR: C:\Users\sekai\Projects\sing-tun
27+
steps:
28+
- name: Checkout PR head
29+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
30+
with:
31+
fetch-depth: 0
32+
ref: ${{ github.event.pull_request.head.ref }}
33+
34+
- name: Install Windows SDK and WDK
35+
shell: pwsh
36+
run: |
37+
$sdkHeader = 'C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um\Windows.h'
38+
$wdkHeader = 'C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\km\ntddk.h'
39+
40+
if (-not (Test-Path $sdkHeader)) {
41+
winget install --source winget --exact --id Microsoft.WindowsSDK.10.0.26100 --accept-source-agreements --accept-package-agreements --disable-interactivity --log "$env:RUNNER_TEMP\sdk-install.log"
42+
Write-Host "Windows SDK install exit code: $LASTEXITCODE"
43+
}
44+
45+
if (-not (Test-Path $wdkHeader)) {
46+
winget install --source winget --exact --id Microsoft.WindowsWDK.10.0.26100 --accept-source-agreements --accept-package-agreements --disable-interactivity --log "$env:RUNNER_TEMP\wdk-install.log"
47+
Write-Host "WDK install exit code: $LASTEXITCODE"
48+
}
49+
50+
if (-not (Test-Path $sdkHeader)) {
51+
throw "Windows SDK header not found after installation: $sdkHeader"
52+
}
53+
if (-not (Test-Path $wdkHeader)) {
54+
throw "WDK header not found after installation: $wdkHeader"
55+
}
56+
57+
- name: Mirror repository to stable Windows path
58+
shell: pwsh
59+
run: |
60+
if (Test-Path $env:REPO_DIR) {
61+
Remove-Item -LiteralPath $env:REPO_DIR -Recurse -Force
62+
}
63+
New-Item -ItemType Directory -Path (Split-Path -Parent $env:REPO_DIR) -Force | Out-Null
64+
robocopy $env:GITHUB_WORKSPACE $env:REPO_DIR /MIR /NFL /NDL /NJH /NJS /NP
65+
$robocopyExitCode = $LASTEXITCODE
66+
if ($robocopyExitCode -gt 7) {
67+
throw "robocopy failed with exit code $robocopyExitCode"
68+
}
69+
Write-Host "robocopy completed with exit code $robocopyExitCode"
70+
exit 0
71+
72+
- name: Print toolchain versions
73+
shell: pwsh
74+
run: |
75+
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
76+
$vsInstall = & $vswhere -latest -products * -property installationPath | Select-Object -First 1
77+
$msbuild = & $vswhere -latest -products * -requires Microsoft.Component.MSBuild -find 'MSBuild\**\Bin\MSBuild.exe' | Select-Object -First 1
78+
if (-not $vsInstall) {
79+
throw 'Visual Studio installation path not found'
80+
}
81+
if (-not $msbuild) {
82+
throw 'MSBuild.exe not found'
83+
}
84+
85+
$vcToolsRoot = Join-Path $vsInstall 'VC\Tools\MSVC'
86+
$vcToolsVersion = (Get-ChildItem $vcToolsRoot | Sort-Object Name | Select-Object -Last 1).Name
87+
$kitsRoot = 'C:\Program Files (x86)\Windows Kits\10\Include'
88+
$sdkHeader = 'C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um\Windows.h'
89+
$wdkHeader = 'C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\km\ntddk.h'
90+
91+
function Show-FileVersion([string] $label, [string] $path) {
92+
if (Test-Path $path) {
93+
$item = Get-Item $path
94+
Write-Host "$label path: $path"
95+
Write-Host "$label version: $($item.VersionInfo.FileVersion)"
96+
} else {
97+
Write-Host "$label path: missing ($path)"
98+
}
99+
}
100+
101+
Write-Host "Visual Studio install: $vsInstall"
102+
Write-Host "MSBuild path: $msbuild"
103+
& $msbuild -version
104+
Write-Host "VC tools root: $vcToolsRoot"
105+
Write-Host "VC tools version: $vcToolsVersion"
106+
107+
Show-FileVersion 'cl Hostx64 x64' (Join-Path $vcToolsRoot "$vcToolsVersion\bin\Hostx64\x64\cl.exe")
108+
Show-FileVersion 'cl Hostx64 ARM64' (Join-Path $vcToolsRoot "$vcToolsVersion\bin\Hostx64\arm64\cl.exe")
109+
Show-FileVersion 'link Hostx64 x64' (Join-Path $vcToolsRoot "$vcToolsVersion\bin\Hostx64\x64\link.exe")
110+
Show-FileVersion 'link Hostx64 ARM64' (Join-Path $vcToolsRoot "$vcToolsVersion\bin\Hostx64\arm64\link.exe")
111+
112+
Write-Host 'Windows Kits include directories:'
113+
Get-ChildItem $kitsRoot | Sort-Object Name | ForEach-Object { Write-Host " $($_.Name)" }
114+
Write-Host "Windows SDK header present: $(Test-Path $sdkHeader)"
115+
Write-Host "WDK header present: $(Test-Path $wdkHeader)"
116+
117+
- name: Build, verify reproducibility, and refresh tracked drivers
118+
id: sync
119+
shell: pwsh
120+
working-directory: ${{ env.REPO_DIR }}
121+
run: |
122+
git config --global --add safe.directory $env:REPO_DIR
123+
124+
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
125+
$msbuild = & $vswhere -latest -products * -requires Microsoft.Component.MSBuild -find 'MSBuild\**\Bin\MSBuild.exe' | Select-Object -First 1
126+
if (-not $msbuild) {
127+
throw 'MSBuild.exe not found'
128+
}
129+
130+
$env:CL = '/Brepro'
131+
$env:LINK = '/Brepro'
132+
$artifactDir = Join-Path $env:RUNNER_TEMP 'winredirect-driver-sync'
133+
$failureDir = Join-Path $artifactDir 'failure'
134+
Remove-Item -LiteralPath $artifactDir -Recurse -Force -ErrorAction SilentlyContinue
135+
New-Item -ItemType Directory -Path $artifactDir -Force | Out-Null
136+
137+
function Invoke-DriverBuild([string] $platform) {
138+
Remove-Item -LiteralPath "internal/winredirect/driver/build/$platform" -Recurse -Force -ErrorAction SilentlyContinue
139+
Remove-Item -LiteralPath "internal/winredirect/driver/intermediate/$platform" -Recurse -Force -ErrorAction SilentlyContinue
140+
& $msbuild 'internal/winredirect/driver/winredirect.vcxproj' '/t:Rebuild' '/p:Configuration=Release' "/p:Platform=$platform" '/p:SpectreMitigation=false' '/v:minimal'
141+
if ($LASTEXITCODE -ne 0) {
142+
throw "MSBuild failed for $platform with exit code $LASTEXITCODE"
143+
}
144+
}
145+
146+
$targets = @(
147+
@{ platform = 'x64'; repo = 'internal/winredirect/amd64/winredirect.sys' },
148+
@{ platform = 'ARM64'; repo = 'internal/winredirect/arm64/winredirect.sys' }
149+
)
150+
151+
Write-Host 'ARM and x86 are intentionally skipped here: WDK 10.0.26100 on GitHub-hosted CI does not support them.'
152+
153+
$changed = $false
154+
foreach ($target in $targets) {
155+
$platform = $target.platform
156+
$tracked = $target.repo
157+
$buildOutput = "internal/winredirect/driver/build/$platform/Release/winredirect.sys"
158+
$run1 = Join-Path $artifactDir "$platform-run1.sys"
159+
$run2 = Join-Path $artifactDir "$platform-run2.sys"
160+
161+
Invoke-DriverBuild $platform
162+
Copy-Item -LiteralPath $buildOutput -Destination $run1 -Force
163+
164+
Invoke-DriverBuild $platform
165+
Copy-Item -LiteralPath $buildOutput -Destination $run2 -Force
166+
167+
& python '.github/scripts/compare_signed_pe.py' $run1 $run2
168+
switch ($LASTEXITCODE) {
169+
0 {
170+
Write-Host "$platform reproduced after stripping Authenticode data."
171+
}
172+
1 {
173+
New-Item -ItemType Directory -Path $failureDir -Force | Out-Null
174+
Copy-Item -LiteralPath $run1 -Destination (Join-Path $failureDir "$platform-run1.sys") -Force
175+
Copy-Item -LiteralPath $run2 -Destination (Join-Path $failureDir "$platform-run2.sys") -Force
176+
throw "$platform build is not reproducible beyond signing metadata."
177+
}
178+
default {
179+
throw "reproducibility comparison failed for $platform with exit code $LASTEXITCODE"
180+
}
181+
}
182+
183+
& python '.github/scripts/compare_signed_pe.py' $run2 $tracked
184+
switch ($LASTEXITCODE) {
185+
0 {
186+
Write-Host "$platform matches the tracked driver after stripping Authenticode data."
187+
}
188+
1 {
189+
Write-Host "$platform differs beyond signing metadata; replacing tracked driver."
190+
Copy-Item -LiteralPath $run2 -Destination $tracked -Force
191+
git add -- $tracked
192+
$changed = $true
193+
}
194+
default {
195+
throw "comparison failed for $platform with exit code $LASTEXITCODE"
196+
}
197+
}
198+
}
199+
200+
if ($changed) {
201+
"changed=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
202+
} else {
203+
"changed=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
204+
}
205+
206+
- name: Upload failed reproducibility artifacts
207+
if: failure()
208+
uses: actions/upload-artifact@v4
209+
with:
210+
name: winredirect-repro-failure-${{ github.run_id }}-${{ github.run_attempt }}
211+
path: ${{ runner.temp }}\winredirect-driver-sync\failure\*.sys
212+
if-no-files-found: warn
213+
214+
- name: Commit and push refreshed drivers
215+
if: steps.sync.outputs.changed == 'true'
216+
shell: pwsh
217+
working-directory: ${{ env.REPO_DIR }}
218+
run: |
219+
git config user.name 'github-actions[bot]'
220+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
221+
222+
git diff --cached --quiet
223+
if ($LASTEXITCODE -eq 0) {
224+
exit 0
225+
}
226+
227+
git commit -m 'winredirect: update bundled drivers'
228+
git push origin "HEAD:${{ github.event.pull_request.head.ref }}"
-8 Bytes
Binary file not shown.
-4.57 KB
Binary file not shown.

internal/winredirect/driver/winredirect.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,8 +439,9 @@ void EvtIoDeviceControl(
439439
if (NT_SUCCESS(status)) {
440440
WINREDIRECT_CONFIG* config = (WINREDIRECT_CONFIG*)inBuf;
441441
NET_LUID tunLuid = {0};
442+
const GUID nullGuid = {0};
442443
KIRQL oldIrql;
443-
if (config->RedirectPort == 0 || config->ProxyPID == 0 || InlineIsEqualGUID(&GUID_NULL, &config->TunGuid)) {
444+
if (config->RedirectPort == 0 || config->ProxyPID == 0 || InlineIsEqualGUID(&nullGuid, &config->TunGuid)) {
444445
status = STATUS_INVALID_PARAMETER;
445446
} else {
446447
status = ConvertInterfaceGuidToLuid(&config->TunGuid, &tunLuid);

internal/winredirect/driver/winredirect.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
</ClCompile>
8080
<Link>
8181
<AdditionalDependencies>Fwpkclnt.lib;$(DDK_LIB_PATH)netio.lib;$(DDK_LIB_PATH)wdmsec.lib;$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;%(AdditionalDependencies)</AdditionalDependencies>
82+
<GenerateDebugInformation>false</GenerateDebugInformation>
8283
</Link>
8384
<Inf>
8485
<Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>

0 commit comments

Comments
 (0)