delphij's Chaos

选择chaos这个词是因为~~实在很难找到一个更合适的词来形容这儿了……

06 Mar 2021

记一件糗事:术后清理要做好

今天和 Henry Hu 聊起 iichid 已经进了 FreeBSD 的主线, 想起自己之前鼠标啥的还是都在用 sysmouse,觉得既然来都来了就索性都改成 evdev 的输入设备算了? 改了 /boot/loader.conf 并增加了下列设置:

iichid_load="YES"
usbhid_load="YES"
hms_load="YES"
hw.usb.usbhid.enable="1"    # 防止uhid(4)接管

重启。等等,一闪而过的那是什么?

ntpd is not allowed to log in on /dev/console

为什么 ntpd (这是 FreeBSD 用于启动 ntpd 的角色用户) 不能在 /dev/console 登录?

看了一眼日志,发现有:

pam_acct_mgmt: Authentication error

等等,这个跟 PAM 有什么关系?

检查了一下 /etc/pam.d/su 和系统默认的一致,这文件好几年没人动过了。唯一不一样的文件是 /etc/pam.d/system, 这个策略是许多其他 PAM 策略引用的,系统默认的是:

account         required        pam_login_access.so

而我的是:

account         required        pam_login_access.so debug

想起来了……去年年底的时候 secteam@ 收到一封报告说 FreeBSD 12.2 里的 pam_login_access(8) 不起作用了。 这个模块的主要功能是检查用户配置的 login.access(5) 并根据策略决定是否允许登录。

我之前并没用过这个模块,收到报告之后,第一件事是测试是否能重现问题。长话短说,我在 /etc/login.access 中增加了这些内容:

+:wheel:ALL
-:ALL:ALL

其含义是,如果用户在 wheel 组,则允许登录,否则拒绝登录。

接下来我创建了一个新的非 wheel 组的用户,发现能够重现问题。查看历史,发现是在 这里 引入的问题, 具体来说是代码重构时,逻辑被搞反了(此处原作者故意用了 ; 并且加了注释,但重构时没有被重构者和复核的人注意到),由于其他功能正常,于是也就合并了。 修起来并不复杂:

diff --git a/lib/libpam/modules/pam_login_access/login_access.c b/lib/libpam/modules/pam_login_access/login_access.c
index 9496081d362e..719808858dac 100644
--- a/lib/libpam/modules/pam_login_access/login_access.c
+++ b/lib/libpam/modules/pam_login_access/login_access.c
@@ -137,10 +137,10 @@ list_match(char *list, const char *item,
     if (match != NO) {
 	while ((tok = strtok((char *) 0, listsep)) && strcmp(tok, "EXCEPT")) {
 	     /* VOID */ ;
-	    if (tok == NULL || list_match((char *) 0, item, match_fn,
-		login_access_opts) == NO) {
+	}
+	if (tok == NULL ||
+	    list_match((char *) 0, item, match_fn, login_access_opts) == NO) {
 		return (match);
-	    }
 	}
     }
     return (NO);

修好之后我手工进行了功能测试,一切正常。我把补丁放到了 secteam@ 的票上,同时请报告的人也去测试一下。因为是安全问题不能立即公开,考虑到我的笔记本并不使用这个功能, 于是我就在本地回退了相关的改动。

这之后一切平静,直到 给笔记本喝了水,在等待新键盘的这段时间, 我的补丁随安全公告 FreeBSD-SA-21:03.pam_login_access 进入了主线。既然修好了就更新一下系统好了,然后重启的时候就没特别注意看日志,直到今天换成 iichid

时间线总结:

  1. 2020-12-28 (故障引入) 修改 /etc/login.access,增加测试项目禁止非 wheel 组用户登录。
  2. 2020-12-28 修正了 pam_login_access(8) 的安全漏洞。此时故障应显现,但因为进行的是单项测试,并未注意到问题。
  3. 2020-12-28 将补丁交给 FreeBSD Security Team,并回退了本地的 pam_login_access(8) 的改动。问题因此被掩盖。
  4. 2021-02-23 上游将修正合并回主线。
  5. 2021-03-02 重新编译系统,未重启,因此仍然未能注意到问题。
  6. 2021-03-06 重启系统并仔细观察输出,发现问题。
  7. 2021-03-06 (修正问题) 修正 /etc/login.access 的测试配置(回退为未修改前的空白版本)并测试确认恢复。

主要的损失是花费了一些时间去进行 ktrace 和阅读改过的代码乃至怀疑人生。

问题溯源:

  1. ntpd 无法正确启动的原因: su 失败。
  2. su 失败的原因是 pam_login_access(8) 拒绝了 ntpd 用户的登录操作。
  3. pam_login_access(8) 拒绝 ntpd 的登录操作,是因为 /etc/login.access 中配置了禁止非 wheel 组用户登录,而 ntpd 角色用户并不属于 wheel 用户组。
  4. 配置 /etc/login.access 禁止非 wheel 组用户登录,是为了测试 pam_login_access(8) 是否能正确地按照配置拒绝用户登录。
  5. 存在此配置的原因在于 a) 直接在笔记本的主要系统中进行了测试,而没有创建独立的测试环境,以及 b) 在测试后未完全回退相关的测试变动。

总结经验,为了避免此类问题再次发生,在进行此类修改时,应采取下列改进:

  1. 将当前系统制作一 ZFS 快照,并采用该快照克隆产生一测试环境。
  2. 在测试环境(jail/chroot,或是以该测试环境作为BE重启),而非主用环境中进行配置及代码修改。
  3. 在修改了主用环境之后,应仔细检查修改过的全部配置(例如通过 zfs diff),而不是仅限于依赖 git 回退代码部分的改动。