故障记录:Remmina SSH 终端中 vi 编辑文件混入其他文件内容
状态:已修复(2026-10-24,gen316),用户实测通过。
修订说明:本笔记 2026-10-24 18:32 的初版结论有误(根因方向错、解法无效、文件是否被污染判断错)。本文已按源码级取证重写,旧结论保留在第 8 节供对照。
1. 故障现象
- 环境:本机 NixOS(max2-nixos)用 Remmina 1.4.43 通过 SSH 连接远端。
- 出问题的会话:
Oracle-Ghost-ghost(ghost@ghost.atibm.com,Oracle ARM,hostnametrilium-260324-1c1g)。 - 操作:
cd /www/nginx; vi docker-compose.yml。 - 异常:vi 屏幕里混入了
/www/hermes/data/data/config.yaml的整段内容(即本机 Hermes 配置)。 - 关键对比:
cat docker-compose.yml看是正常的。
2. 取证:远端文件其实从未被污染
检查项 |
实测结果 |
结论 |
|
|
mtime 不是当天 ⇒ 那次 vi 保存没有落盘 |
|
|
YAML 完全合法,没有混入任何内容 |
|
空 |
那台机器上没有 config.yaml |
|
|
|
⇒ config.yaml 是本机 /home/cat/.hermes/config.yaml 的内容,被 remmina 显示到了远端 vi 的屏幕上。无需回滚/重建远端 compose。
3. 根本原因(源码级,已用机器码坐实)
问题在 src/remmina_ssh.c 的 remmina_ssh_cp_to_ch_cb() —— 这是键盘/输入方向(本地 fd → SSH channel)的中继函数,不是终端输出方向。
gchar *buf = (gchar *) g_malloc ( sizeof(gchar) * 0x200000 ); // 分配了 2MB
...
sz = read(fd, buf, sizeof(buf)); // ← buf 是 gchar* 指针,sizeof(buf) == 8
if (sz > 0) {
ret = ssh_channel_write(channel, buf, sz);
if (ret != sz) { // ← 写不全就丢整块
g_free(buf);
return -1;
}
}
sizeof(buf)是指针宽度 8(x86_64),不是 2MB ⇒ 每次 POLLIN 只读 8 字节。- 再叠加「
ssh_channel_write返回值一旦不等就return -1丢掉整块」,输入流被切成 8 字节碎片且有丢包。 - vim 启动时会发
ESC[6n(查询光标位置)、ESC[>c(查询终端版本),实测抓到:^[[6n | ^[[>c | ^[[6n。 - 这些查询的响应被切碎/丢失 ⇒ vim 解析失步 ⇒ 残片被当作键入字符落进编辑缓冲区 ⇒ 出现「混入别的文件内容」的假象。
机器码对照(同一函数,objdump -d)
旧 1.4.43(有 bug) |
修复后 |
|
缓冲区 |
|
|
read 长度 |
|
|
写出逻辑 |
|
循环 |
4. 修复来源(关键:升级到最新版并不能解决)
上游修复是 commit e57fbb60「Fix SSH terminal relay buffer reads」(2026-05-30,master 分支),改法是 gchar buf[8192] + while (offset < sz) 循环写完,顺带修了 temp_node 未判空就取 ->protected 的崩溃。
版本 / 节点 |
日期 |
含修复? |
v1.4.41 |
2025-08-11 |
否 |
v1.4.42 |
2026-02-14 |
否 |
v1.4.43(本机,也是当前最新 tag) |
2026-02-20 |
否 —— 自带此 bug |
master(修复 commit) |
2026-05-30 |
是 |
之后的正式 release |
— |
至今没有 |
⇒ nixpkgs 跟随 tag,升级 nixpkgs 没用,只能本地打补丁。
5. 落地做法(本机已生效)
- 把上游 patch 存为
/etc/nixos/modules/desktop/remote/remmina-ssh-relay-fix.patch(在 v1.4.43 源码上patch -p1 --dry-run干净通过)。 - 在
/etc/nixos/modules/desktop/remote/remmina.nix加 overlay(结构同本机ark-absolute-path-dnd-fix.nix):nixpkgs.overlays = [ (final: prev: { remmina = prev.remmina.overrideAttrs (old: { patches = (old.patches or [ ]) ++ [ ./remmina-ssh-relay-fix.patch ]; }); }) ]; nixos-rebuild switch remmina-ssh-relay-fix⇒ gen316(2026-10-24_18-53-51)成功。- 必须完全退出 Remmina 再重开(GtkApplication 单实例;已在跑的进程仍持有旧二进制 inode,关标签页不够)。
版本号不会变:remmina --version 仍显示 1.4.43(补丁不改版本号),所以别用版本号判断是否生效。
自查是否真的生效(最硬判据):
objdump -d /run/current-system/sw/bin/remmina | grep -A3 remmina_ssh_cp_to_ch_cb
在 read@plt 之前看到 mov $0x2000,%edx = 已修;看到 mov $0x8,%edx = 还是旧版。
6. 撤销方式
注释掉 remmina.nix 里 patches 那一行 + rebuild,即回原版 1.4.43(bug 复现)。
7. 一句话记录
Remmina 1.4.43 的 SSH 输入中继函数把 read 长度写成 sizeof(指针)=8 字节,终端输入被切碎,vim 的终端查询响应错位、残片被当按键落进缓冲区,看上去像「文件混入了别的内容」;远端文件实际从未被污染。上游修复 commit e57fbb60 尚未进任何 release(v1.4.43 就是最新版),所以靠升级无效,已在 NixOS 侧用本地 patch + overlay 修好(gen316),实测通过。
8. 旧结论对照(已被推翻,勿再沿用)
初版写的 |
实际情况 |
根因 = SSH 插件「读取/转发终端响应」时缓冲区碎片化 |
方向错了:坏的是键盘输入方向( |
空 |
只是让 vim 少发终端查询,没有修中继缺陷;输入仍被切 8 字节。远端现有的是 |
升级到 v1.4.42+ 可根治 |
错。修复 commit 2026-05-30,晚于最新 tag v1.4.43(2026-02-20)⇒ 升级无效,必须打补丁 |
vi 保存后文件已被污染 |
错。mtime 未变、889 字节、 |
|
这条正确,保留 |
9. 附:排查中的一次弯路(教训)
修复后我(Hermes)还额外跑了 libssh 模拟流、sha256 完整性对照、短写注入压测、无头 remmina trace,想「再坐实因果」——这些全是多余的:
- remmina 的
read发生在它自己的 PTY master 上,由 X/VTE 驱动的键盘事件喂入;从外部往 SSH channel 灌数据根本绕过了cp_to_ch_cb,测了也证明不了这个 bug。 - 其中一轮「8 字节 vs 8192 字节」两组 sha256 都一致,本身就说明没打到缺陷点,该立即停而不是继续加测试。
- 正确路径:源码/反汇编定位 → 上游 commit → 打补丁 → rebuild → 用户在真机验 → 收尾。判据够了就收。
10. 相关台账
- 本机台账:
hermes-nixos-workspace/docs/remmina-ssh-vi-content-mixing.md - 系统侧提交:
/etc/nixoscommitb2de011 - 看板卡片:
t_894b260e(nixos 板,done) - 上游出处:
https://gitlab.com/Remmina/Remmina/-/commit/e57fbb60