fix(cua-driver-rs/windows): UIPI integrity-mismatch detection + focus-aware HWND retargeting for PostMessage input#1613
Conversation
…e HWND retargeting for PostMessage input Two interlocking fixes for the silent-no-op failure mode where `type_text` / `hotkey` / `click` return success when nothing actually happened. Both gaps were caught during overnight stress testing (`WINDOWS_VM_STRESS_TEST_FINDINGS.md`) against Notepad++ 8.9.5 and LibreOffice Writer 26.2.3.2. ## Bug 1: UIPI integrity mismatch silently dropped messages Daemon at Medium IL → target at High IL: `PostMessage(WM_KEYDOWN/WM_CHAR/ WM_LBUTTONDOWN)` is silently dropped by the target's message pump. The call returns TRUE, the sender has no way to detect this. Any app whose manifest auto-elevates (most Program-Files installs of Notepad++, system-scope VS Code, etc.) hits this. **Fix**: `post_message_blocked_by_uipi(hwnd)` in `input/mod.rs` opens the target's process token (`PROCESS_QUERY_LIMITED_INFORMATION`), reads `TokenIntegrityLevel`, compares against the current process's integrity RID. When target > current, returns a diagnostic string the caller surfaces as an actionable error. Called from the entry of `post_char` / `post_type_text` / `post_type_text_with_delay` / `post_key` / `post_click_on`. ## Bug 2: WM_CHAR posted to top-level frame, not focused child Notepad++'s Scintilla editor is a child window. Notepad++'s top-level WindowProc doesn't forward WM_CHAR to Scintilla — Scintilla needs to receive WM_CHAR directly on its own HWND. Same shape applies to WordPad/RichEdit, the FAR file manager's edit pane, and any other app that puts its text surface in a child HWND. Without this retargeting, `type_text` returns success but characters land in no editor. **Fix**: `focused_descendant(parent)` in `input/keyboard.rs` uses `AttachThreadInput` + `GetFocus` to read the target thread's focused HWND, validates with `IsChild` that it's actually a descendant of the caller's `parent` (defends against unrelated foreground windows), and retargets `PostMessage` to the child. Detaches immediately after the focus read — attaching for the duration would distort the input-state visibility of both threads. ## What this does NOT fix (documented as follow-ups) - **hotkey modifier state bug**: `PostMessage(WM_KEYDOWN, VK_CONTROL)` does not set the system-wide modifier state that apps poll via `GetKeyState`. LibreOffice's `TranslateAccelerator` sees plain `a`/`b` with no Ctrl held, treats them as text. Fix needs SendInput from the UIAccess'd worker, plus AttachThreadInput for focus. Out of scope. - **launch_app loses launcher-stub pid chain** (GIMP `gimp-3.exe` re-execs into another process; cua-driver doesn't follow). Out of scope. - **Chromium/Electron content opacity** — VS Code's entire workbench invisible to UIA. Would need Chromium UIA bridge handshake. Out of scope. ## VM-test status Both helpers build clean (`cargo build -p cua-driver -p cua-driver-uia --release` on the Windows VM). The integrity check is **defensively correct** — but doesn't fire on the dev VM because `fbonacci` runs at High IL (unfiltered admin token), so all daemons + targets are at High IL and `target > own` is never true. To verify the integrity check actually fires, replicate the test on a VM with a standard non-admin user. The focus-aware retargeting was prototyped but VM SSH dropped before I could verify it against elevated Notepad++ this round — opening as **draft** for follow-up verification. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughWindows input injection now detects User Interface Privilege Isolation (UIPI) blocking by comparing process integrity levels before posting messages. Keyboard injection retargets WM_CHAR to the focused child window. Both keyboard and mouse injection functions bail early when the target process has higher integrity, preventing silent message drops. ChangesUIPI Integrity Checking and Input Injection
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
VM verification — focus-aware retargeting confirmed ✅Retested against elevated Notepad++ 8.9.5 on the dev VM (post-reboot, RDP reconnected):
The retargeting via The integrity-check half doesn't fire on this VM (all processes at High IL because the dev user is in Administrators with unfiltered token), but it's defensively correct and would fire on a standard non-admin setup. Verifying that separately is out of scope for this PR. |
…module (build hotfix) #1613 imported AttachThreadInput from KeyboardAndMouse — windows-rs actually exposes it from System::Threading. Squash-merge fails to compile: error[E0432]: unresolved import windows::Win32::UI::Input::KeyboardAndMouse::AttachThreadInput The corrected source was tested on the VM via scp during #1613 verification, but the committed version on main is broken. This patch restores the build. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…module (build hotfix) (#1617) #1613 imported AttachThreadInput from KeyboardAndMouse — windows-rs actually exposes it from System::Threading. Squash-merge fails to compile: error[E0432]: unresolved import windows::Win32::UI::Input::KeyboardAndMouse::AttachThreadInput The corrected source was tested on the VM via scp during #1613 verification, but the committed version on main is broken. This patch restores the build. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Two interlocking fixes for the silent-no-op failure mode where
type_text/hotkey/clickreturn ✅ when nothing actually happened. Both gaps caught in overnight stress testing against Notepad++ 8.9.5 and LibreOffice Writer 26.2.3.2.Bug 1: UIPI integrity mismatch silently drops messages
Daemon at Medium IL → target at High IL:
PostMessage(WM_KEYDOWN/WM_CHAR/WM_LBUTTONDOWN)is silently dropped by the target's message pump. The call returnsTRUE, sender can't detect the drop. Any app whose manifest auto-elevates (most Program-Files installs of Notepad++, system-scope VS Code, etc.) hits this.Fix:
post_message_blocked_by_uipi(hwnd)ininput/mod.rs. Opens the target's process token, readsTokenIntegrityLevel, compares against the current process's integrity RID. When target > current, returns a diagnostic string the caller surfaces as an actionable error. Called from the entry ofpost_char/post_type_text/post_key/post_click_on.Bug 2: WM_CHAR posted to top-level frame, not focused child
Notepad++'s Scintilla editor is a child window. Notepad++'s top-level WindowProc doesn't forward WM_CHAR to Scintilla. Same shape: WordPad's RichEdit, FAR's edit pane, anything with embedded editors. Without retargeting,
type_textreturns success but characters land nowhere.Fix:
focused_descendant(parent)ininput/keyboard.rs. UsesAttachThreadInput+GetFocusto read the target thread's focused HWND, validates withIsChildthat it's actually a descendant ofparent, retargetsPostMessageto that child. Detaches immediately after the focus read.Empirical findings (overnight stress test on Win11 VM)
type_textreturns ✅, text never appearstype_textworks,hotkey ctrl+breturns ✅ but no boldtype_text+hotkeyreturn ✅, neither landsWhat this PR does NOT fix (followups)
PostMessage(WM_KEYDOWN, VK_CONTROL)doesn't set system-wide modifier state. LibreOffice/Notepad++/most TranslateAccelerator-based apps treat the keystrokes as plain text. Universal Win32 bug. Fix needs SendInput from the UIAccess'd worker.gimp-3.exere-execs into another process; cua-driver doesn't follow. Same shape partial-blocks LibreOffice swriter.exe → soffice.bin.Test status
cargo build -p cua-driver -p cua-driver-uia --releaseclean on Windows VMfbonacciruns at High IL (unfiltered admin token), so all daemons + targets ended up at High andtarget > ownwas never true. Verifying on a non-admin VM is the next step.References
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes