cua-driver Linux X11 MPX 孤儿设备回收:基于版本化属主的 master 指针/键盘对自动恢复机制

发布时间:2026/9/14 5:29:12
cua-driver Linux X11 MPX 孤儿设备回收:基于版本化属主的 master 指针/键盘对自动恢复机制 cua-driver Linux X11 MPX 孤儿设备回收基于版本化属主的 master 指针/键盘对自动恢复机制【免费下载链接】cuaScale computer-use 2.0 with open-source drivers, cross-OS fleets, and benchmarks for training, evaluation, and data generation.项目地址: https://gitcode.com/GitHub_Trending/cua/cua本篇技术指南聚焦 cua-driverLinux 端 X11 背景输入注入与 MPX 真实指针路径在进程异常退出后遗留的 XInput master 设备对master pointer/keyboard pair问题完整讲解其版本化属主versioned ownership恢复契约、reap_orphaned_master_pointers的源码实现以及如何在真实 Xorg 桌面上通过聚焦回归门禁 linux-mpx-recovery.py 复现与验证整个生命周期。读完你将掌握为什么裸 PID 不足以安全回收设备、恢复为何只发生在独立守护进程启动时、以及如何产出可供 CI 归档的proof.json证据。问题背景无焦点窃取输入路径留下的孤儿设备cua-driver 在 Linux X11 上实现无焦点窃取no focus steal输入依赖 XInput2XI2的 MPX 机制驱动通过XIAddMaster创建一个临时 master pointer/keyboard 对再经由/dev/uinput创建真实指针从设备slave pointer挂到该 master 下配合设备级 shield grab 将拖动事件送达目标窗口而不触发窗口管理器抢焦点。这条路径的完整实现位于 platform-linux/src/input/mod.rs。正常情况下每次输入操作结束都会执行forget_master_pointer通过XIRemoveMaster回收设备。但文档关联文档 linux-mpx-recovery.md对应 issue #3337记录了一个现实缺陷在真实 Xorg 21.1.11 桌面 libinput /dev/uinput环境下正常的背景滚动操作能正确投递并移除临时 master 设备但当滚动操作持续较长时间、进程被 SIGTERM 中断时master pointer/keyboard 对会在属主进程退出后残留下来成为 X 服务器上的孤儿设备。文档同时指出该环境下 GTK 键盘 fixture 在回收前仍能正常接收按键因此它并未复现 issue 中单独报告的 Chrome 键盘症状——即孤儿设备的表现随客户端而异必须从设备生命周期层面系统性解决而不是针对某个应用打补丁。为什么解析旧设备名里的 PID不安全初看最直接的方案是从遗留的 master 设备名中解析出 PID若进程不存在则删除设备。但文档明确否决了这一方案因为回收逻辑必须区分以下几种难以仅凭 PID 区分的情形情形说明属主进程存活live peer设备仍被正在运行的驱动会话使用绝不能回收PID 复用PID reuse旧进程已死但 PID 被新进程占用解析到相同 PID 可能误判为存活或误删新进程的设备进程元数据不可访问hidepid挂载、权限不足会导致/proc/{pid}条目不可读缺失条目不等于进程不存在不同主机或 PID 命名空间多个主机/容器共享同一个 X server 时命名空间内的 PID 不具备全局唯一性旧版/未知属主设备旧版本遗留的CUA legacy mp-123-1一类设备名无法安全解析属主因此文档给出的原则是从旧设备名中裸解析 PID 不足以判定属主未知或旧版属主的设备必须保持原样由人工在核实属主后手动回收自动恢复绝不触碰它们。恢复契约把属主编码进 XIAddMaster 的设备名版本化属主名格式文档定义的新版 master 设备名编码了一个版本化属主域versioned ownership domain包含五个要素属主域domain由内核启动标识/proc/sys/kernel/random/boot_id、PID 命名空间/proc/self/ns/pid与有效用户geteuid经 SHA-256 派生出的 16 字节哈希PID属主进程 ID4 字节进程启动时刻start time取自/proc/{pid}/stat的starttime字段8 字节用于识别 PID 复用唯一 nonce每次创建递增的 8 字节计数器设备角色后缀pointer/keyboard/uinput pointer。实际生成的名称形如源码 mpx_owner.rsCUA v1.{domain_b64url}.{pid(4B)start_ticks(8B)_b64url}.{nonce_b64url}编码采用 URL-safe 无填充 base64。其中pid start_ticks打包为 12 字节starttime是内核 jiffies 计数同一 PID 若被复用新进程的starttime必然不同从而可靠识别 PID 复用。所有权随 XIAddMaster 原子写入文档特别强调属主信息是XIAddMaster调用参数的一部分即直接写入 master 设备名而不是先创建设备、再通过独立的 XI2 property 写入。这样 SIGKILL 无法打断设备创建与属主标记之间的窗口——不存在未标记属主的新 master 残留的可能。对应源码在 input/mod.rs 的ensure_master_pointer_for_session先创建 uinput 从设备失败则不触碰 X 层级再以含属主名的XIAddMaster创建 master 对最后挂接 slave。恢复的触发与执行条件只在独立守护进程standalone daemon启动时执行恢复run_serve在绑定 Unix socket 前调用recover_orphaned_mpx_devices()见 serve.rs只回收属主被证明确实不存在或PID 已被复用的匹配设备对设备 ID 校验与移除必须处于同一个 X server grab 下XGrabServer防止枚举与移除之间设备 ID 被其他 X 客户端复用。明确不执行恢复的情形嵌入式 SDK 直接启动不执行恢复recover_orphaned_mpx_devices的注释明确要求其不参与 registry 构造只读命令如describe/list-tools绝不能变更 X11 设备见 platform-linux/src/lib.rsWayland 行为完全不变恢复函数在检测到WAYLAND_DISPLAY时直接返回SIGTERM/SIGKILL 中断遗留的设备会一直保留直到下一个独立守护进程启动。源码级深入属主判定与回收的实现细节属主域构造mpx_owner.rsOwner::current()构造当前进程属主时做了两道命名空间自检解析/proc/self/stat得到首字段 PID若与std::process::id()不一致说明 procfs 暴露的是另一个 PID 命名空间直接报错再读取/proc/{self_pid}/stat的starttime与自身比对不一致同样报错procfs does not identify this PID namespace。随后以boot_id、PID 命名空间 inode 与geteuid拼接后 SHA-256取前 16 字节作为domain。这三个输入共同界定了同一内核启动 同一 PID 命名空间 同一有效用户的属主域使跨主机/跨容器共享 X server 时互不误伤。starttime的解析start_ticks还处理了一个经典陷阱/proc/{pid}/stat的comm字段可能包含空格与右括号必须用rsplit_once())取最后一个)之后的字段再取第 20 个空白分隔字段starttime 是整体第 22 字段即 tail 的索引 19。该解析有专门单测覆盖name with ) parentheses这类畸形 commmpx_owner.rs。名称解析的严格性from_pointer_namempx_owner.rs必须以CUA v1.开头、pointer结尾且三段均为合法 URL-safe base64 且长度精确16/12/8 字节PID 必须满足0 pid i32::MAX拒绝任何额外后缀单测验证了keyboard、XTEST pointer、pointer extra、CUA legacy mp-123-1 pointer、CUA v1.bad.bad.bad pointer、ordinary pointer全部返回Nonempx_owner.rs名称长度受 uinput 约束UINPUT_MAX_NAME_SIZE80 字节、evdev 断言留 1 字节实际可用 78 字节单测确认即使 PID 取i32::MAX、start_ticks取u64::MAX完整名称仍不超限。过期判定stale_inmpx_owner.rs判定顺序设计为失败保守fail-closed宁可保留domain不一致 → 非过期外来属主域直接保留/proc/{pid}/stat可读且starttime与设备名记录的不同 →过期PID 已被复用读取返回NotFound→ 要求内核独立报告kill(pid, 0)返回ESRCH才算过期。之所以额外要求ESRCH是因为hidepid或权限不足会让/proc条目看起来不存在只有内核明确说没有这个进程才可信其他错误PermissionDenied、InvalidData、Interrupted→ 一律视为未过期。单测矩阵印证了这一契约live_peer_and_unverifiable_metadata_are_preserved存活属主、元数据不可读、外来 domain 均保留与 dead_owner_and_reused_pid_are_staleESRCH 缺席、starttime 变化均为过期。未知属主时的旧版回退input/mod.rs若Owner::current()失败procfs 身份无法确认master_pointer_name回退到旧版命名CUA {cursor} mp-{pid}-{nonce}。该回退刻意保持旧版清理行为——未知 procfs 身份的设备不能参与自动恢复因为恢复方无法安全判定其是否过期。回收主流程input/mod.rsreap_orphaned_master_pointers的执行序列存在WAYLAND_DISPLAY→ 直接返回Owner::current()失败 → 直接返回不冒险XGrabServer抢占服务器grab 期间只做本地 procfs 读取与kill(pid, 0)探测不涉及网络或任意文件系统读取XIQueryDevice枚举全部设备仅处理XIMasterPointer类型逐个用Owner::from_pointer_name解析名称stale_in(owner)为真则调用remove_master_pointerXIRemoveMasterreturn_mode XIAttachToMaster归还到 Virtual core pointer/keyboard见 input/mod.rs并记录 tracing 日志XUngrabServerXSync出错仅告警不中断。该函数仅由独立守护进程启动路径调用serve.rs与 platform-linux/src/lib.rs 中read-only 命令不得变更 X11 设备的约束严格对应。在真实 Xorg 上复现聚焦原生门禁环境前提文档强调本门禁必须使用一次性disposable真实 Xorg 桌面且满足libinput 驱动可用/dev/uinput可读可写guest 本地 uinput 访问安装xinput与 Python GTK3 绑定且存在会话总线session bus。Xvfb 与 TigerVNC 不支持本测试所需的设备热插拔路径——Xvfb 没有 udev/libinput 热插拔uinput 设备永远不会成为 X 输入 slaveXtigervnc 只暴露内建 VNC/XTEST 设备源码supports_parallel_pointer_injection与is_xvfb_process_running对此有显式探测见 input/mod.rs。脚本启动时也会断言DISPLAY存在、WAYLAND_DISPLAY未设置、/dev/uinput可读写。构建驱动并运行 harnesscargo build --locked -p cua-driver --manifest-path libs/cua-driver/rust/Cargo.toml python3 libs/cua-driver/tests/linux-mpx-recovery.py \ --driver libs/cua-driver/rust/target/debug/cua-driver \ --output /tmp/mpx-recovery-evidence \ --source-sha $(git rev-parse HEAD)要点说明输出目录必须是不存在的新目录harness 使用exist_okFalse防止污染旧证据--source-sha必须与 harness 自身所在仓库的git rev-parse HEAD一致否则拒绝运行防证据与候选版本不匹配--driver指向刚构建的cua-driver可执行文件harness 只会启动它自己拥有的daemon 与 GTK fixture信号只发给 harness 所属进程不会触碰外部进程。Harness 内部结构harnesstests/linux-mpx-recovery.py通过--fixture参数自举启动一个 GTK3 窗口Gtk.Entry可访问名 Keyboard recovery oracle记录按键文本事件Gtk.DrawingArea可访问名 MPX scroll canvas650×400注册SCROLL_MASK | SMOOTH_SCROLL_MASK记录滚动事件所有事件以 JSON Lines 追加写入fixture.jsonlevent字段 时间戳 载荷。daemon 通过cua-driver serve --socket ... --dangerously-bypass-approvals启动测试环境专用参数停止通过cua-driver --socket ... stop每次 XInput 快照用xinput list --short落盘窗口状态用get_window_state截图取证。覆盖的验证矩阵场景断言正常输入与干净退出normal-input-and-clean-exit启动后创建 master 对正常滚动投递到 fixture干净停止后不再存在属主为自身 PID 的设备背景按键拒绝无delivery_mode的press_key返回background_unavailable时走 foreground 路径否则验证文本到达SIGTERM 中断恢复term-restart-recovery长滚动amount50进行中 SIGTERM 杀掉 daemon → 设备残留为孤儿 → 只读describe不触发回收 → 新 daemon 启动后孤儿被回收、既有未属主 master 被保留、键盘可用SIGKILL 中断恢复kill-restart-recovery同上信号换 SIGKILL暂停的存活对端保留并恢复paused-live-peer-preserved-and-resumed对端 daemon SIGSTOP 暂停主 daemon 停止再重启暂停对端设备不被回收SIGCONT 后滚动与键盘恢复正常只读 describe 不回收cua-driver describe scroll后孤儿设备数量不变最终不变量final时刻的 master 集合等于baseline-after-startupproof.json记录passed: true所有案例逐一写入输出目录最终产出proof.json含source_sha、各case的passed与证据计数供 CI 与人工审计。测试体系与边界注意事项聚焦属主单测mpx_owner模块内嵌了名称往返、长度上限、非法后缀/前缀、存活/元数据不可读/外来域保留、死亡与 PID 复用判定、proc-stat 畸形 comm 解析等单测mpx_owner.rs它们不依赖真实 X 环境可在任何 CI 上快速运行规范 Linux 桌面 harness 需单独运行文档要求对最终候选版本另行执行规范的 Linux 桌面端到端 harness如 scripts/ci/linux/run-rust-e2e.sh 及 gnome/kde/xorg 变体聚焦门禁只作为原生证据的补充Wayland 不受影响回收逻辑对 Wayland 会话完全不生效行为无任何改变KDE/X11 防护源码在 KDE Plasma X11 会话下会整体禁用 uinput 热插拔路径kde_x11_uinput_hotplug_is_unsafe见 input/mod.rs避免 Qt 客户端崩溃恢复逻辑与之正交恢复滞后性SIGTERM/SIGKILL 遗留的设备在下一个独立 daemon 启动前不会被回收嵌入式 SDK 直接启动也不执行回收这是文档明确的契约而非缺陷。总结cua-driver 的 Linux MPX 恢复方案把属主身份从进程外部信息收敛为XIAddMaster调用中原子的设备名编码以 boot_id PID 命名空间 有效用户派生属主域以 PID starttime识别进程与 PID 复用以 nonce 区分同进程的多次创建再配合X server grab procfs 读取 内核 ESRCH 三重证据的保守判定实现了既能在守护进程重启时自动清理由 SIGTERM/SIGKILL 造成的孤儿 master 设备对、又绝不误伤存活对端、外来属主域与旧版设备的恢复契约。读者可借助 linux-mpx-recovery.py 在真实 Xorg 桌面上完整复现这一生命周期并产出可归档的proof.json原生证据。【免费下载链接】cuaScale computer-use 2.0 with open-source drivers, cross-OS fleets, and benchmarks for training, evaluation, and data generation.项目地址: https://gitcode.com/GitHub_Trending/cua/cua创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考