From 39fec50df20e1bfcc2d071b596bedcb4d3625054 Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Thu, 3 Sep 2026 07:33:56 -0400 Subject: [PATCH] wolfsshd: Cover -D option parsing on Windows in CI The Windows StartSSHD() path rebuilds argv from GetCommandLineW(). A regression there left -D foreground mode walking the raw wide command line, so -f and -p were ignored and the daemon used its built-in defaults. Nothing in CI caught that. Add sshd_dash_d_test.ps1: it starts wolfsshd with -D and a config file at a non-default path whose Port line differs from the -p value, then checks the listener binds the -p port and not the config port. That holds only when -D mode parsed both -f and -p. Run it from the Windows build job next to the existing LoginGraceTime check. --- .github/workflows/windows-check.yml | 6 ++ apps/wolfsshd/test/sshd_dash_d_test.ps1 | 115 ++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 apps/wolfsshd/test/sshd_dash_d_test.ps1 diff --git a/.github/workflows/windows-check.yml b/.github/workflows/windows-check.yml index 35ba8644..11292e22 100644 --- a/.github/workflows/windows-check.yml +++ b/.github/workflows/windows-check.yml @@ -88,6 +88,12 @@ jobs: timeout-minutes: 2 run: .\sshd_login_grace_test.ps1 -SshdExe "$env:SSHD_PATH" + - name: Test wolfsshd -D option parsing on Windows + working-directory: ${{ github.workspace }}\wolfssh\apps\wolfsshd\test + shell: pwsh + timeout-minutes: 2 + run: .\sshd_dash_d_test.ps1 -SshdExe "$env:SSHD_PATH" + # Build and run the self-contained unit tests with the MSVC AddressSanitizer. # This is the only job that executes wolfSSH tests under a sanitizer on # Windows, where the USE_WINDOWS_API console code (e.g. wolfSSH_DoOSC) is diff --git a/apps/wolfsshd/test/sshd_dash_d_test.ps1 b/apps/wolfsshd/test/sshd_dash_d_test.ps1 new file mode 100644 index 00000000..9be75fe4 --- /dev/null +++ b/apps/wolfsshd/test/sshd_dash_d_test.ps1 @@ -0,0 +1,115 @@ +#!/usr/bin/env pwsh +# +# Windows regression test for wolfsshd -D (foreground) option parsing. +# +# On Windows, StartSSHD() rebuilds argv from GetCommandLineW(); a bug in that +# path left -D (foreground) mode parsing the raw wide command line, so -f and +# -p were silently ignored and the daemon fell back to its compiled-in +# defaults. This test starts wolfsshd with -D and a config file at a +# non-default path whose Port line differs from the -p value, then checks that +# the listener comes up on the -p port. That only happens if -D mode parsed +# both -f (to find the config) and -p (to override the config's Port). +# +# No Windows user account or authorized key is required: the check is that the +# daemon binds the requested port, not that a session authenticates. +# +# Usage: +# pwsh sshd_dash_d_test.ps1 -SshdExe [-Port N] [-ConfPort N] +# (SshdExe also accepts the SSHD_PATH environment variable.) + +param( + [string]$SshdExe = $env:SSHD_PATH, + [int]$Port = 22335, + [int]$ConfPort = 22336 +) + +$ErrorActionPreference = "Stop" +$exitCode = 1 + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$repoRoot = (Resolve-Path (Join-Path $scriptDir "..\..\..")).Path +$keyPath = (Resolve-Path (Join-Path $repoRoot "keys\server-key.pem")).Path +$confFile = Join-Path $scriptDir "sshd_config_test_dash_d" +$authFile = Join-Path $scriptDir "authorized_keys_test_dash_d" + +if (-not $SshdExe -or -not (Test-Path $SshdExe)) { + Write-Host "ERROR: wolfsshd.exe not found (pass -SshdExe or set SSHD_PATH)" + exit 1 +} + +if ($Port -eq $ConfPort) { + Write-Host "ERROR: -Port and -ConfPort must differ so the test can tell them apart" + exit 1 +} + +# The config's Port is deliberately not the port we probe. If -p is parsed it +# wins (wolfsshd only reads the config Port when none was given on the command +# line), so a listener on $Port proves the -p override took effect. +@" +Port $ConfPort +Protocol 2 +PermitRootLogin yes +PasswordAuthentication yes +UseDNS no +HostKey $keyPath +AuthorizedKeysFile $authFile +"@ | Out-File -FilePath $confFile -Encoding ASCII + +"" | Out-File -FilePath $authFile -Encoding ASCII + +# -D selects the non-service (foreground) path on Windows. +$sshd = Start-Process -FilePath $SshdExe ` + -ArgumentList "-D", "-f", "`"$confFile`"", "-p", "$Port" ` + -NoNewWindow -PassThru + +try { + $up = $false + for ($i = 0; $i -lt 20; $i++) { + if ($sshd.HasExited) { + throw "wolfsshd exited early (code $($sshd.ExitCode)); -D option parsing likely failed" + } + try { + $probe = New-Object System.Net.Sockets.TcpClient + $probe.Connect("127.0.0.1", $Port) + $probe.Close() + $up = $true + break + } + catch { + Start-Sleep -Milliseconds 500 + } + } + if (-not $up) { + throw "wolfsshd did not listen on the -p port $Port; -D did not honor -f/-p" + } + + # The config Port must not have been used: nothing should answer there. + $confBound = $false + try { + $probe = New-Object System.Net.Sockets.TcpClient + $probe.Connect("127.0.0.1", $ConfPort) + $probe.Close() + $confBound = $true + } + catch { + # expected: no listener on the config Port + } + if ($confBound) { + throw "wolfsshd listened on the config Port $ConfPort; -p override was not applied" + } + + Write-Host "PASS: -D mode parsed -f and -p (listening on $Port, not $ConfPort)" + $exitCode = 0 +} +catch { + Write-Host "FAIL: $_" + $exitCode = 1 +} +finally { + if ($sshd -and -not $sshd.HasExited) { + Stop-Process -Id $sshd.Id -Force -ErrorAction SilentlyContinue + } + Remove-Item -Path $confFile, $authFile -Force -ErrorAction SilentlyContinue +} + +exit $exitCode