标签归档:debug

Manjaro Linux 抢救记

2024 年中秋适逢台风贝碧嘉来袭(熟悉的开头:D),强降水给小区带来的树倒、停电等一系列灾害,我的装了洗手.jpgManjaro 系统的 N100 小主机在 226 天的 uptime 后终于趁着停电休息了一把。供电恢复后通过 openVPN 、智能家居 app 发现路由器、 IoT 设备都已重新上线,困惑的是唯独这台小主机仍然处于失联状态。等到节后回来便研究了一番。以下全程感谢 @wzyboy 再一次的不吝指导。

接上显示器和外设开机发现,系统已经处于 emergency mode 状态。此时遇到的第一个坑是,emergency mode 的第一行提示 “loadkmap short read”,加上按一些字母键都没有反应(此时实为输入密码状态,但无回显),由于对 emergency mode 不熟悉使我和 gpt 误认为键盘映射出现问题。实际上此时只要输入 root 的密码就可以登录系统。

以 root 登录后输入 dmesg 查看内核输出的启动信息,发现在正常启动过程中有以下看起来很关键的报错:

intel_ish_ipc: ishtp-ish timed out waiting for fw-initiated reset
intel_ish_ipc: ISH: hw start failed.

于是一阵搜索,越搜越觉得困惑和茫然,是不是内核 bug,是不是硬件损坏…gpt 也小心翼翼地建议在 grub 启动项中屏蔽 ish 有关硬件,满怀希望地保存后重启,问题依旧。

emergency mode 中,系统很明确地提示要用 journalctl -xb 命令去查看启动日志,于是很仔细地查看这一千七百八行日志,这里我边看边在想,和 dmesg 不同的是阻塞正常启动的关键错误信息可能不会按时间顺序出现在日志的最后面,但仍会以不同颜色的字体标出。

仔细观察日志后发现与 /etc/fstab 有关的挂载错误,却没被我放在心上。我知道挂载一个不存在的分区会引起启动问题,因此在 /etc/fstab 中设置了 nofail 来长期把一移动硬盘挂载在系统中。虽然排错时移动硬盘未插入,不至于影响正常启动吧?所以又错过了奇点,从别的角度甚至是 USB Live CD 的 chroot 等方式试图修复系统。等到回过头继续从 journalctl -xb 日志侦错时,还是觉得大致记下报错的分区 UUID 去 /etc/fstab 看一眼吧。这一看才发现该 UUID 所对应的是一个未设置 nofail 、已经被合并了进 / 了的、不存在的分区,因为操作时间久远且一直未重启才忽略了该问题,注释该行,问题解决。

在排错的过程中,因为系统设置了中文导致 emergency mode 的字体全变成方块,这个问题在我首次接触 debian 时也遇到过,不过此次学到了通过设置变量 export LANG=C 的方法强制 fallback 到比 en_US.UTF-8 还基本的 locale 来避免中文显示问题。甚至还了解到了有趣的 Linux tty 勉强型中文字体——戴着有限字符空间的镣铐跳起支持中文的舞。又让我想起了一些汉化版 FC 游戏因 ROM 空间不足而被迫实施的一些奇技淫巧。

Linux 启动排错可能使人更有机会了解 “Linux 系统是怎么编排硬件的” 。而我一直想象的一个场景是:把桌面环境的 Linux 自行配置成家用路由器——通过安装 pppoe 、 upnp 等软件包来实现家用路由器功能;配置多网卡、网桥、路由表等来实现 WAN-LAN 口转发等,这样的场景想必更有挑战,也会使人更加了解 “Linux 系统是怎么编排网络的” 。

由 Python 偶现 SSL 错误引发的 TLS cipher 问题探究

近期在用 Python (v3.12.0) 对某 API 接口持续性地调用测试过程中,发现会偶现 SSL 错误:

raise SslHandShakeException("ssl error", reason=e)
somesdk.exception.exceptions.SslHandShakeException: SslHandShakeException - ssl error
reason: SSLError(SSLError(1, '[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1002)'))

由于拨测是在 Windows 云主机上进行,把脚本拉到本地后在本地重新安装 SDK 等相关 py 环境,发现问题依旧。

此前从同事那里得知 ipv6 网络环境曾出现过不稳定的情况,初步怀疑是是 ipv6 引起此问题,是否在走与不走 ipv6 链路的区别中产生了 “不稳定” 现象。把 Windows 系统的 ipv4/ipv6 分别禁用,排除了这种可能性。祭出 Wireshark 在本地抓包,同时不停地运行 Python 调用 API 脚本发送请求,终于在一段时间测试后发现了问题:API 所在主机有 A 、 B 两个 ipv4 解析结果,在解析结果为 B 时稳定复现此问题,解析结果为 A 时则稳定不出现问题。

由于 Python 的底层运行库提示为 SSL 错误,因此这两个 server 的 TLS 支持情况成为关键。此前从一些网络技术文章可知,对于客户端 TLS 指纹(包括但不限于 client cipher suite 的组合特征)、甚至 HTTP2 指纹的识别早已经应用到脚本、爬虫等 “非人” 客户端的识别封禁等。

调查 Python 客户端与 A 主机 TLS 握手失败的包,发现以下现象:在服务端返回握手失败包之前客户端所发送的 Client Hello 包中,客户端(Python3.12.0 及相关网络库)向服务端提供了以下 18 个加密套件:

Pyhton 3.12.0 提供的 18 个 cipher suite(点击展开)
1TLS_AES_256_GCM_SHA384(0x1302)
2TLS_CHACHA20_POLY1305_SHA256(0x1303)
3TLS_AES_128_GCM_SHA256(0x1301)
4TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xc02c)
5TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xc030)
6TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xc02b)
7TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xc02f)
8TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(0xcca9)
9TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(0xcca8)
10TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(0xc024)
11TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(0xc028)
12TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(0xc023)
13TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(0xc027)
14TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(0x009f)
15TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(0x009e)
16TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(0x006b)
17TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(0x0067)
18TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0x00ff)
前三个属于 TLS1.3 的加密套件在本次实际交互中不被支持,最后一个 SCSV 不是真正的加密套件,只有剩下的 14 个可用,而这 14 个套件皆为 DHE 或 ECDHE 。
使用 sslscan 工具分别扫描 A 、 B 两台服务端所支持的 cipher 情况(点击展开)
A 支持的 cipher
TLSv1.2128 bits0xC013ECDHE-RSA-AES128-SHA
TLSv1.2256 bits0xC028ECDHE-RSA-AES256-SHA384
TLSv1.2256 bits0x0035AES256-SHA
TLSv1.2128 bits0x002FAES128-SHA
B 支持的 cipher
TLSv1.2256 bits0x0035AES256-SHA
TLSv1.2128 bits0x002FAES128-SHA

可见 Python 3.12.0 这样的 Client 在 TLS 握手时向服务端提供的加密套件中,和 B 没有交集,和 A 有交集 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,HEX 代码为 (0xc028),因此只能和 A TLS 握手成功。

在和其他部门同事复现、调试的过程中,据称同事的 Python3.6 没有这个问题,因此使用了自己另一台电脑的 Python3.9.2 环境进行复测,Python3.9.2 的相关 SSL 库在 TLS 握手 Client Hello 时提供了 43 个 cipher,最终服务端 B 在 Server Hello 中回复商定了 TLS_RSA_WITH_AES_256_CBC_SHA (0x0035) 作为加密套件。

Pyhton 3.9.2 提供的 43 个 cipher suite(点击展开)
1TLS_AES_256_GCM_SHA384(0x1302)
2TLS_CHACHA20_POLY1305_SHA256(0x1303)
3TLS_AES_128_GCM_SHA256(0x1301)
4TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xc02c)
5TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xc030)
6TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xc02b)
7TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xc02f)
8TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(0xcca9)
9TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(0xcca8)
10TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(0x009f)
11TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(0x009e)
12TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256(0xccaa)
13TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8(0xc0af)
14TLS_ECDHE_ECDSA_WITH_AES_256_CCM(0xc0ad)
15TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8(0xc0ae)
16TLS_ECDHE_ECDSA_WITH_AES_128_CCM(0xc0ac)
17TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(0xc024)
18TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(0xc028)
19TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(0xc023)
20TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(0xc027)
21TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(0xc00a)
22TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xc014)
23TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(0xc009)
24TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xc013)
25TLS_DHE_RSA_WITH_AES_256_CCM_8(0xc0a3)
26TLS_DHE_RSA_WITH_AES_256_CCM(0xc09f)
27TLS_DHE_RSA_WITH_AES_128_CCM_8(0xc0a2)
28TLS_DHE_RSA_WITH_AES_128_CCM(0xc09e)
29TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(0x006b)
30TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(0x0067)
31TLS_DHE_RSA_WITH_AES_256_CBC_SHA(0x0039)
32TLS_DHE_RSA_WITH_AES_128_CBC_SHA(0x0033)
33TLS_RSA_WITH_AES_256_GCM_SHA384(0x009d)
34TLS_RSA_WITH_AES_128_GCM_SHA256(0x009c)
35TLS_RSA_WITH_AES_256_CCM_8(0xc0a1)
36TLS_RSA_WITH_AES_256_CCM(0xc09d)
37TLS_RSA_WITH_AES_128_CCM_8(0xc0a0)
38TLS_RSA_WITH_AES_128_CCM(0xc09c)
39TLS_RSA_WITH_AES_256_CBC_SHA256(0x003d)
40TLS_RSA_WITH_AES_128_CBC_SHA256(0x003c)
41TLS_RSA_WITH_AES_256_CBC_SHA(0x0035)
42TLS_RSA_WITH_AES_128_CBC_SHA(0x002f)
43TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0x00ff)

那么 Python3.12.0 和 Python3.9.2 的 Clinet cipher 到底有什么区别?略一对比可以看出 Python3.12.0 抛弃了相当多的 RSA 相关加密套件,而保留了和 DHE 、 ECDHE 相关的。

TLS1.3 一项重要的改进就是抛弃了 RSA 相关算法而保留了 Diffie–Hellman(DH)作为唯一的密钥交换算法。因为前者不具有前向安全性(Forward Secrecy,简称 FS),简单说,把 https 密文保存下来后,即使在未来私钥泄露的情况下,也无法解开所保存的密文。

Python3.10 的更新日志 中有一条:
The ssl module now has more secure default settings. Ciphers without forward secrecy or SHA-1 MAC are disabled by default. Security level 2 prohibits weak RSA, DH, and ECC keys with less than 112 bits of security. SSLContext defaults to minimum protocol version TLS 1.2. Settings are based on Hynek Schlawack’s research. (Contributed by Christian Heimes in bpo-43998.)
其思路和 TLS1.3 抛弃 RSA 比较一致,把不支持前向安全的加密套件废除以提高安全性。

最终服务端 B 包括域名的 v6 地址所接受的 cipher 与 A 拉齐,问题得到解决。一个看似简单又有点奇怪的 “SSL 偶现报错” 问题原来与 DNS 解析负载均衡、新版 Python 严格的 TLS 加密套件策略等一系列原因有关。