软件:Remmina SSH 终端中 vi 编辑文件混入其他文件内容

故障记录: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,hostname trilium-260324-1c1g)。
  • 操作:cd /www/nginx; vi docker-compose.yml。
  • 异常:vi 屏幕里混入了 /www/hermes/data/data/config.yaml 的整段内容(即本机 Hermes 配置)。
  • 关键对比:cat docker-compose.yml 看是正常的。

2. 取证:远端文件其实从未被污染

检查项

实测结果

结论

stat docker-compose.yml

2026-05-30 00:03:59,889 字节

mtime 不是当天 ⇒ 那次 vi 保存没有落盘

docker compose config

COMPOSE_OK

YAML 完全合法,没有混入任何内容

find / -xdev -name config.yaml

空

那台机器上没有 config.yaml

ls /www/hermes/

No such file or directory

/www/hermes 路径在该机不存在

⇒ 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)

修复后

缓冲区

mov $0x200000,%edi + call g_malloc(堆)

sub $0x1000,%rsp ×2(栈上 8KB,带 %fs:0x28 金丝雀)

read 长度

mov $0x8,%edx

mov $0x2000,%edx(8192)

写出逻辑

cmp / jne ⇒ 短写即 return -1

循环 add %rdx,%rbx / cmp %r15,%rbx / jl 回跳,写完为止

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. 落地做法(本机已生效)

  1. 把上游 patch 存为 /etc/nixos/modules/desktop/remote/remmina-ssh-relay-fix.patch(在 v1.4.43 源码上 patch -p1 --dry-run 干净通过)。
  2. 在 /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 ];
        });
      })
    ];
  3. nixos-rebuild switch remmina-ssh-relay-fix ⇒ gen316(2026-10-24_18-53-51)成功。
  4. 必须完全退出 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 插件「读取/转发终端响应」时缓冲区碎片化

方向错了:坏的是键盘输入方向(cp_to_ch_cb),不是终端输出方向

空 ~/.vimrc 可规避

只是让 vim 少发终端查询,没有修中继缺陷;输入仍被切 8 字节。远端现有的是 set paste(2026-05-17 创建),与「空 vimrc」不是一回事

升级到 v1.4.42+ 可根治

错。修复 commit 2026-05-30,晚于最新 tag v1.4.43(2026-02-20)⇒ 升级无效,必须打补丁

vi 保存后文件已被污染

错。mtime 未变、889 字节、docker compose config 校验通过 ⇒ 文件干净

vi -n 无效(与 Swap 无关)

这条正确,保留

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/nixos commit b2de011
  • 看板卡片:t_894b260e(nixos 板,done)
  • 上游出处:https://gitlab.com/Remmina/Remmina/-/commit/e57fbb60