htop 鼠标失效、vim 行错乱:一次 sshpass 引发的终端故障排查

问题描述

在 Windows Terminal 通过 SSH 连接到一台 openEuler 22.03 ARM 开发板(RK3528)时,出现两个异常:

  • htop 运行后鼠标点击无响应或乱跳,无法正常操作
  • vim 打开文件编辑时段落错乱、行跳位,无法正常使用

但在同一台机器上用 Xshell 8 连接,htop 和 vim 完全正常。
另外用 Windows Terminal 连接另一台配置完全相同的 openEuler 服务器,也完全正常。

环境信息

  • 问题机器:openEuler 22.03 LTS-SP4,aarch64,自定义 RK35xx 内核
  • 正常机器:openEuler 22.03 LTS-SP4,aarch64,标准 openEuler 内核
  • 客户端:Windows Terminal(内置 OpenSSH 客户端)

排查过程

第一步:对比 TERM 环境变量

最直观的怀疑是终端类型不匹配。对比两个客户端的环境变量:

  • Windows Terminal 连接时:TERM=xterm-256color
  • Xshell 8 连接时:TERM=xterm

初步怀疑是 TERM=xterm-256color 导致问题,建议在服务端 .bashrc 中强制覆盖:

1
2
3
if [[ -n "$SSH_TTY" ]]; then
export TERM=xterm
fi

但Windows Terminal 连接另一台相同系统的机器时,TERM 同样是 xterm-256color,htop 却完全正常。因此排除 TERM 变量的影响。


第二步:全面对比两台机器的软件栈

在两台机器上分别执行以下检查:

1
2
3
4
5
htop --version
rpm -q ncurses
md5sum /usr/bin/htop
infocmp xterm-256color | grep -E "mouse|km"
cat ~/.config/htop/htoprc

结果:两台机器的 htop 版本(3.1.2)、ncurses 版本(6.3-15)、terminfo 数据库、htoprc 配置完全一致,连二进制的 MD5 校验值都相同。软件层面无差异。


第三步:排查 PROMPT_COMMAND 与 OSC 转义序列

深入对比两台机器的 Shell 初始化脚本,发现:

正常机器/etc/profile.d/hiscmd.sh,在 /etc/profile 阶段提前设置:

1
export PROMPT_COMMAND='__hiscmd_log'

问题机器没有该脚本,/etc/bashrc 检测到 PROMPT_COMMAND 为空后设置:

1
PROMPT_COMMAND='printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/\~}"'

问题机器每次显示提示符都会向终端发送 OSC 标题序列 \033]0;...\007,而正常机器不会输出任何终端控制序列。

怀疑这条序列破坏了 Windows Terminal 的 VT 状态机,导致后续鼠标跟踪序列被错误解析。

验证:在连接后手动执行:

1
PROMPT_COMMAND='' htop

结果:无效,问题依然存在。 排除 PROMPT_COMMAND 的影响。


第四步:发现 OpenSSH 服务端版本差异

对比两台机器的 SSH 服务端版本:

1
ssh -V
  • 问题机器OpenSSH_8.8p1
  • 正常机器OpenSSH_10.2p1(自定义编译,无签名)

版本差距近两个大版本。怀疑 Windows Terminal 内置的新版 OpenSSH 客户端与旧版 8.8p1 服务端存在 PTY 协商兼容性问题,导致终端模式参数传递不完整。


第五步:升级问题机器的 OpenSSH 至 10.2p1

由于官方源只有 8.8p1,正常机器的 RPM 安装包也已删除,决定从正常机器直接打包二进制文件传输:

1
2
3
4
5
6
7
8
9
10
11
# 在正常机器上打包
tar czf /tmp/openssh10_bins.tar.gz \
/usr/sbin/sshd \
/usr/libexec/openssh/sshd-auth \
/usr/libexec/openssh/sshd-session \
/usr/libexec/openssh/sftp-server \
/usr/libexec/openssh/ssh-keysign \
/usr/bin/ssh /usr/bin/scp /usr/bin/sftp /usr/bin/ssh-keygen

# 传输到问题机器
scp -P 8022 root@SERVER_B:/tmp/openssh10_bins.tar.gz /tmp/

升级前先在 2222 端口启动测试实例验证可用性:

1
2
3
4
5
6
# 安装新版辅助组件(10.2p1 新增的权限分离进程)
cp sshd-session sshd-auth /usr/libexec/openssh/

# 测试启动
/tmp/openssh10/usr/sbin/sshd -p 2222 -f /etc/ssh/sshd_config \
-o "PidFile=/tmp/sshd_test.pid"

通过 Windows Terminal 连接 2222 端口测试:htop 鼠标完全正常!

随后替换系统主程序并重启服务(注意运行中的二进制需先删除再复制):

1
2
3
rm /usr/sbin/sshd
cp /tmp/openssh10/usr/sbin/sshd /usr/sbin/sshd
systemctl restart sshd

通过 Windows Terminal 重新连接 22 端口:htop 鼠标仍然乱跳。

OpenSSH 版本升级后问题依然存在,排除版本因素。


第六步:对比 systemd 管理与手动启动的差异

此时发现关键现象:

  • Windows Terminal 连接 22 端口(systemd 管理)→ 异常
  • Windows Terminal 连接 2222 端口(手动启动)→ 正常
  • 两个实例使用完全相同的二进制和配置

对比两个 sshd 进程的文件描述符:

1
2
ls -la /proc/PORT22_PID/fd/
ls -la /proc/PORT2222_PID/fd/

发现差异:

1
2
3
4
5
6
7
# systemd 管理的 sshd(22 端口)
fd 1 -> socket:[XXXXXX] ← systemd journal 套接字
fd 2 -> socket:[XXXXXX] ← systemd journal 套接字

# 手动启动的 sshd(2222 端口)
fd 1 -> /dev/null
fd 2 -> /dev/null

systemd 将 stdout/stderr 重定向到 journal 套接字,而新版 OpenSSH 10.2p1 编译时未包含 systemd 支持(不链接 libsystemd),sshd-session 继承这些 journal 套接字后处理 PTY 时出现异常。

创建 systemd override 修复:

1
2
3
4
5
6
7
8
mkdir -p /etc/systemd/system/sshd.service.d/
cat > /etc/systemd/system/sshd.service.d/override.conf << 'EOF'
[Service]
StandardOutput=null
StandardError=null
Type=simple
EOF
systemctl daemon-reload && systemctl restart sshd

验证 fd 1/fd 2 已变为 /dev/null——问题仍未解决。


第七步:停用 systemd 管理,手动在 22 端口启动

为彻底排除 systemd 影响,停止 systemd sshd,直接手动启动:

1
2
3
systemctl stop sshd
/usr/sbin/sshd -p 22 -f /etc/ssh/sshd_config \
-o "PidFile=/tmp/sshd22_manual.pid"

连接手动启动的 22 端口:htop 鼠标仍然乱跳。

此时两个端口(22 和 2222)的进程在各方面已无任何差异:相同二进制、相同配置、相同 cgroup、相同 fd、SELinux 已关闭、环境变量相同、stty size 输出相同。


第八步:对比两个会话的 env 输出

分别在 22 端口和 2222 端口的 Windows Terminal 会话中执行 env,对比输出。

两个会话的环境变量完全一致,唯一区别是 SSH_CONNECTION 中的端口号和 SSH_TTY 的设备编号。

此时注意到关键线索:连接 2222 端口时是在测试期间手动输入命令,而连接 22 端口使用的是 Windows Terminal 中保存的 Profile


第九步:定位根因——Windows Terminal Profile 配置

在 Windows Terminal 新建标签页(默认 Profile),手动输入:

1
ssh root@SERVER_A_IP -p 22

结果:htop 鼠标完全正常,非常稳定!

问题锁定在 Windows Terminal 的保存 Profile 上。

查看 settings.json,找到问题 Profile:

1
2
3
4
5
{
"commandline": "sshpass -p \"PASSWORD\" ssh -o StrictHostKeyChecking=no root@SERVER_A_IP",
"hidden": false,
"name": "RK3528"
}

根本原因

sshpass 是本次问题的唯一根因。

sshpass 为了自动注入密码,在 Windows Terminal 与 SSH 之间插入了一个中间层来拦截 stdin/stdout。这导致:

  • PTY 终端模式参数(terminal modes)无法完整传递到 SSH 服务端
  • 鼠标跟踪转义序列(SGR mouse mode \E[<...M)在传输中被破坏
  • vim 的光标控制和屏幕刷新序列出现异常

Xshell 8 自身实现了密码管理,不依赖 sshpass,因此不受影响。正常机器的测试也因使用的是直接 SSH 命令(无 sshpass)而正常。


解决方案

改用 SSH 密钥免密登录,彻底去除 sshpass:

在 Windows 端生成并部署密钥:

1
2
3
4
5
# 生成 Ed25519 密钥对(如已有可跳过)
ssh-keygen -t ed25519

# 将公钥上传到服务器
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh root@SERVER_A_IP "cat >> ~/.ssh/authorized_keys"

更新 Windows Terminal Profile:

1
2
3
4
5
{
"commandline": "ssh root@SERVER_A_IP",
"hidden": false,
"name": "RK3528"
}

去掉 sshpass 后,htop 鼠标和 vim 显示均完全恢复正常。


系统恢复

由于问题与 OpenSSH 版本无关,将问题机器恢复至原版 OpenSSH 8.8p1:

1
2
3
4
5
6
7
8
9
10
11
12
13
# yum reinstall 是最干净的恢复方式,直接从官方源还原所有文件
yum reinstall -y openssh openssh-clients openssh-server

# 删除升级过程中引入的新文件
rm -f /usr/libexec/openssh/sshd-session
rm -f /usr/libexec/openssh/sshd-auth

# 移除 systemd override
rm -rf /etc/systemd/system/sshd.service.d/

# 恢复 systemd 管理
systemctl daemon-reload
systemctl start sshd

恢复后 rpm 校验:

1
2
rpm -V openssh openssh-server openssh-clients
# 仅 sshd_config 显示差异(修改过的配置文件,正常)

额外优化

顺手关闭不必要的 X11 转发(openEuler 官方不建议禁用 PAM):

1
2
# /etc/ssh/sshd_config
X11Forwarding no

总结

本次排查历经以下方向,逐一排除:

  • TERM 环境变量差异
  • htop / ncurses / terminfo 版本差异
  • PROMPT_COMMAND 输出 OSC 转义序列
  • OpenSSH 服务端版本差异(8.8p1 vs 10.2p1)
  • systemd journal 套接字污染 fd 1/fd 2
  • cgroup、capabilities、SELinux 等系统安全设置
  • 终端尺寸(stty size / COLUMNS / LINES)

最终根因在客户端,与服务端完全无关。

核心教训:sshpass 通过拦截 stdin/stdout 注入密码的机制,会破坏终端 PTY 的完整性,导致依赖鼠标跟踪和精确光标控制的全屏应用(htop、vim 等)出现异常。 生产环境应使用 SSH 密钥认证替代 sshpass。