Make the device load, run, and wire-respond on the Windows NT 3.1 floor - #22
Merged
Conversation
NT 3.1 (the native-Win32 floor, July 1993) rejected the binary at three points the Win32s/Win9x tiers structurally never surfaced, each found by on-target QEMU validation: - The PE was stamped OS/subsystem version 4.0 (mingw default); NT 3.1's loader refuses a newer stamp with ERROR_BAD_FORMAT. Stamp 3.10 in the link step - still loads on every later Windows, which accept older stamps. - SetHandleInformation (NT 3.51/Win95) was a static kernel32 import in exec_ops.c. Resolve it through the feat.c capability probe, with the classic DuplicateHandle non-inheritable-copy fallback for NT 3.1. - lstrcpynA is absent from NT 3.1's kernel32 (only lstrcpy/A/W exist). Supply McpStrCpyN (strutil.c), a DBCS-aware bounded copy, and drop the static import across the call sites. mcp-w32s.exe still imports only kernel32 + user32, stays i386/console, and the full build plus every string-helper-affected suite is green (the two exec/serial suites fail identically with and without this change - the known wine-vs-native divergence, not a regression). Verified live: the device now loads and runs on real NT 3.1 Advanced Server under QEMU. The remaining serial-port-open issue is tracked separately in the host plan. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The NT 3.1 floor refuses CreateFileA on the bare "COMn" DOS-device alias where later Windows resolves it; retry once via the canonical "\\.\COMn" device-namespace form before failing (skipped if already prefixed). Also surface the real CreateFileA GetLastError() + the port tried in the error string, so an on-target open failure is diagnosable in a single boot (error 2 => not enumerated; 5 => in use; 87 => bad name form). No new import (wsprintfA is already user32); object stays opcode-clean. Diagnostic+fallback build for the parked #40 COM1-open blocker. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root-caused the NT 3.1 'failed to open serial port (error 87)': it was SetCommState, not CreateFile. NT's MODE opens COM1 and accepts 19200/n/8/1 fine (it does GetCommState -> modify -> SetCommState), but the device built its DCB from a memset(0) and called SetCommState cold -- a zeroed DCB has XonChar == XoffChar == 0, which the 1993 serial driver rejects with ERROR_INVALID_PARAMETER. Modern NT is lenient; NT 3.1 is not. Fix: seed the DCB from GetCommState, then change only baud/bytesize/parity/ stop + the flow-control flags (the robust idiom MODE/Terminal use). Also replace the mislabeled "CreateFileA error" string with the precise failing stage (CreateFileA/GetCommState/SetCommState/SetCommTimeouts) + error code, so one boot pinpoints the failure. \\.\COMn name fallback retained. No new import (GetCommState is kernel32); object stays opcode-clean. Pending on-target confirmation on the NT 3.1 guest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… fallback McpStrCpyN (the NT 3.1 bounded copy, used at ~50 sites) had no direct test. Add a host theft PBT (tests/host/theft_strutil.c, 5 properties x 50k, ASan): NUL-terminated & length <= n-1; result is a byte-prefix of src; no DBCS character split (re-walk with a cp932-style CharNextA shim lands exactly on the terminator); bounded write (dst sized exactly n, ASan red-zones catch overflow); edges (dst==NULL, n<=0). Mirror on-target via prop.h (tests/test_strutil.c). F2's ClearHandleInherit DuplicateHandle fallback (taken only when NT 3.1 lacks SetHandleInformation) was untested. Expose a TEST_BUILD hook and add test_exec_ops cases: force the probe NULL, confirm GetHandleInformation shows HANDLE_FLAG_INHERIT cleared on a still-valid handle; the API path; the NULL/invalid guards. Also fix a pre-existing host-pbt breakage: the theft_catalog build line lacked src/strutil.c, so catalog.c's McpStrCpyN calls were undefined refs (broken since the F3 swap) -- host-pbt was failing in CI on this branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The weed gate-bypass audit found ClearHandleInherit fails open: it dropped HANDLE_FLAG_INHERIT on the parent-only pipe ends but ignored failure on both routes (SetHandleInformation return discarded; DuplicateHandle fallback left the original inheritable handle on failure), so a child could inherit a parent-only pipe end. Make it return BOOL and abort the spawn (goto fail_pipes, before CreateProcessA) if any clear fails, rather than launching with an inheritable parent end. Never-path on a fresh handle, benign blast radius, but fail-closed matches the project's restraint posture. Tests: assert the success routes return TRUE; a stubbed SetHandleInformation returning FALSE is now DETECTED (failclosed_unit) and aborts ExecOpRun with "could not isolate parent pipe handles" before any child spawn (failclosed_spawn). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI's function-level allowlist check has been red since the NT-floor work because the import set drifted: F2 made SetHandleInformation a GetProcAddress probe (no longer a static import) and added DuplicateHandle (fallback); F3 dropped lstrcpynA for McpStrCpyN; the COM1 fix added GetCommState. Update the allowlist to match exactly: +DuplicateHandle +GetCommState, -SetHandleInformation -lstrcpynA. Both additions verified present in the Win32s 1.25a thunk export surface (W32SCOMB.DLL exports GetCommState[697] and DuplicateHandle[2015], alongside the already-listed SetCommState/SetCommTimeouts), per the file header's baseline-image method. Local replication of the CI check: 58 imports, zero drift. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The \\.\COMn fallback used an unbounded wsprintfA into devName[40], safe only by the implicit port[32] cross-module bound. Build it explicitly bounded instead: lstrcpyA the fixed 4-char prefix, then McpStrCpyN the port name into the remainder (sizeof(devName)-4), independent of the caller's buffer size. No import change (lstrcpyA/McpStrCpyN already used); allowlist still exact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the #40 native-Win32 (NT 3.1) floor: the device now loads, runs, and
wire-responds on real Windows NT 3.1 build 511 over the OS-serial path
(on-target acceptance PASS).
Fixes
SetHandleInformationrouted through afeat.cruntime probe with aDuplicateHandlefallback (NT 3.1 lacks the API); fail-closed — the spawnaborts before
CreateProcessAif a parent-only pipe end can't be madenon-inheritable (weed gate-bypass finding).
lstrcpynA→McpStrCpyN(NT 3.1's kernel32 lackslstrcpynA), aDBCS-aware bounded copy, swapped at ~50 sites.
GetCommState-first DCB seeding (a from-zeroed DCB'sXonChar==XoffCharwas rejected by the 1993 serial driver with err 87);per-stage open-error reporting;
\\.\COMnname fallback.Gates
test gaps) → implement (filled:
theft_strutil5 props ×50k + on-target;fail-closed handle-isolation tests) → weed (zero drift; tier gate holds).
build-and-test+bridge+conformance+real-Windows).{kernel32, user32}; allowlist reconciled (+DuplicateHandle+
GetCommState, verified in the Win32s 1.25aW32SCOMBthunk).🤖 Generated with Claude Code