嵌入式 ARM Linux 下 OpenSSH 9.9 崩溃 (Signal 11) 排查与修复


在嵌入式 ARM + musl libc 平台上升级或使用 OpenSSH 9.9 时,客户端连接可能出现 Software caused connection abort 错误。本文记录从日志抓取、动态库跟踪、汇编/ELF分析到最终关闭 PIE 解决问题的全过程。


一、 问题现象与初查日志

客户端连接时被中断,查看服务器端的日志 /var/log/auth.log/var/log/secure

Dec 31 16:21:21 hx_610 auth.info sshd[1090]: Received signal 15; terminating.
Dec 31 16:21:21 hx_610 auth.info sshd[1101]: Server listening on 0.0.0.0 port 22.
Dec 31 16:21:31 hx_610 auth.err sshd[1101]: error: ssh_msg_send: write: Broken pipe
Dec 31 16:21:31 hx_610 auth.err sshd[1101]: error: send_rexec_state: ssh_msg_send failed
Dec 31 16:21:31 hx_610 auth.err sshd[1101]: error: session process 1104 for connection from 192.168.55.254 to 192.168.55.100 killed by signal 11 (early)

日志分析:

  1. sshd 主进程(PID 1101)接收连接请求并派生(fork)子进程(PID 1104)来处理会话。
  2. 子进程在极早期阶段抛出 Signal 11 (SIGSEGV,段错误) 崩溃死亡。
  3. 主进程试图通过管道向子进程发送数据时触发 Broken pipe

二、 深入排查与定位

1. 开启前台 Debug 模式抓取日志

在目标板上启动临时前台 Debug 进程:

/usr/sbin/sshd -ddd -p 2222

客户端发起带详细日志的连接:

ssh -vvv -p 2222 [email protected]

服务器端输出的关键崩溃点:

debug3: using /sbin/sshd-session for re-exec
debug1: rexec start in 8 out 8 newsock 8 pipe -1 sock 11/12
Segmentation fault

注意: OpenSSH 9.9 采用了进程隔离架构,主进程 /sbin/sshd 仅负责监听端口,连接建立后通过 rexec 机制调用独立二进制文件 /sbin/sshd-session 处理会话密钥交换与认证。崩溃发生在拉起 sshd-session 的瞬间。

2. 检查依赖库与模拟加载

在嵌入式无 ldd 工具的环境下,通过动态链接器模拟打印库依赖:

/lib/ld-musl-arm.so.1 --list ./sshd-session

输出如下:

libssl.so.3 => /lib/libssl.so.3 (0xb6d13000)
libcrypto.so.3 => /lib/libcrypto.so.3 (0xb69ce000)
libz.so.1 => /lib/libz.so.1 (0xb69b9000)
libc.so => /lib/ld-musl-arm.so.1 (0xb6ebe000)
libatomic.so.1 => /lib/libatomic.so.1 (0xb69b3000)

依赖库均能正常寻址,未提示 not found。但在终端直接裸跑 ./sshd-session 仍瞬间发生 Segmentation fault

3. 使用 strace 追踪系统调用

使用 strace 抓取二进制文件加载瞬间:

strace ./sshd-session

输出日志末端:

open("/lib/libssl.so.3", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
...
mprotect(0xb6eae000, 24576, PROT_READ) = 0
mprotect(0xb6db9000, 204800, PROT_READ) = 0
mprotect(0xb6aa8000, 4096, PROT_READ) = 0
mprotect(0xb6a92000, 4096, PROT_READ) = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x59823c} ---
+++ killed by SIGSEGV +++ Segmentation fault

诊断分析:

4. 解析 ELF 头结构 (readelf)

在交叉编译机上检查 sshd-session 的 ELF 文件属性:

arm-v01c02-linux-musleabi-readelf -l usr/libexec/sshd-session

输出片段:

Elf file type is DYN (Position-Independent Executable file)
Entry point 0xa119
...

三、 根因分析

  1. PIE (Position-Independent Executable) 机制: 较新的 GCC 交叉工具链默认开启了 PIE 编译选项,生成 DYN 类型的可执行文件,用于配合 ASLR(地址空间布局随机化)提升安全性。
  2. 兼容性冲突: 老旧的 ARM 嵌入式 Linux 内核或低版本 musl libc 动态链接器(ld-musl-arm.so.1)对 PIE 二进制文件的入口点重定位处理存在 Bug。
  3. 结果:sshd 尝试通过 rexec 启动 sshd-session 时,动态链接器未能正确计算重定位地址,导致指令直接跳转至非法的内存区域(0x59823c),触发硬件 MMU 拦截并抛出 SEGV_ACCERR

四、 解决方案

重新编译 OpenSSH 并强制关闭 PIE 功能,生成标准的静态地址可执行文件(EXEC)。

方法 1:直接修改 Makefile(推荐,100% 生效)

在执行完常规 ./configure 后,不要直接 make,打开生成的 Makefile

# 搜索并修改 CFLAGS 与 LDFLAGS
CFLAGS = -O2 -Wall -Wpointer-arith ... -fno-pie -fno-PIE
LDFLAGS = -L. -Lopenbsd-compat/ ... -no-pie

修改后保存,运行 make 进行编译。

方法 2:配置环境变量并 Configure

在编译机上彻底清理并传入编译参数:

make distclean

CC="arm-v01c02-linux-musleabi-gcc" \
CFLAGS="-O2 -fno-pie -fno-PIE" \
LDFLAGS="-no-pie" \
./configure --host=arm-linux-musleabi --without-pie \
  --prefix=/usr \
  --sysconfdir=/etc/ssh

make

方法 3:全静态编译(备选方案)

若环境对动态库重定位极度脆弱,可直接打包为全静态二进制文件:

LDFLAGS="-static" ./configure --host=arm-linux-musleabi --without-pie
make

五、 验证与结果

  1. 在编译机上使用 readelf 验证新生成的二进制文件:
arm-v01c02-linux-musleabi-readelf -l usr/libexec/sshd-session | grep "Elf file type"

输出由 DYN 变更为 EXEC

Elf file type is EXEC (Executable file)
Entry point 0x19779

注:入口点变更为确切的绝对内存物理地址 0x19779

  1. 将编译好的 sshd/usr/sbin/sshd)与 sshd-session/usr/libexec/sshd-session)替换到目标板硬件(hx_610)中,确保赋予执行权限(chmod +x)。

  2. 重启 SSH 服务:
    killall sshd
    /usr/sbin/sshd
    
  3. 重新连接 SSH,Software caused connection abort 错误消除,SSH 恢复正常登录。
· Written by Link