跳到正文
Hacker News· hasheddan·· 1 天前AI 评分30

musl libc 的 crypt 函数用 "*" 作哨兵值,导致 Alpine 系统 sshd 可被未认证用户获取 root shell

*sshole, or when are we going to get rid of C?

AI 导读

musl libc 的 crypt 函数自 2012 年起对过长密码返回字符串 "*",而 Unix 上 "*" 一直表示账户被锁定,两者叠加使无 PAM 的 musl 系统(如官方 Alpine AWS 镜像)上未认证用户可拿到(可能是 root 的)shell。

正文 · AI 翻译
The meme of a happy person and a sad person on a bus, the sad person captioned with '4' and the happy person with a string of 257 As.
Genildo Ronchi 原创插画。

又一天,又一个漏洞出现在核心 C 代码中。这次的起因是两个不同的条件使用了同一个哨兵值:

  • 在 Unix 上,密码哈希为 "*" 自早期以来就一直表示账户已被锁定。
  • 自 2012 年起,musl libc 的 crypt 函数对于过长而无法哈希的密码会返回字符串 "*"。

将这两点结合起来,再加上 sshd 会对锁定账户的登录尝试进行哈希以避免时序泄漏,这意味着在没有 PAM 的 musl 系统上(比如,哦,官方 Alpine AWS 镜像),未认证用户现在可以在你的计算机上获得一个(可能是 root!)shell。详情请参阅这份 Alpine 安全公告。

这不是内存安全漏洞;相反,它是那种人们常说拥有更好类型系统的语言也救不了你的问题之一。这是真的!你完全可以在 Rust 中写一个函数,在密码过长时返回 "*"。但在这方面,C 和 Rust 之间的巨大区别在于,Rust 社区的规范不允许这样做。

POSIX 规范试图考虑错误情况。它规定:

成功完成后,crypt() 应返回指向哈希后密码的指针;[…] 否则,它应返回空指针并设置 errno 以指示错误。

但 C 的类型系统无法强制开发者检查返回值是否为 NULL。musl libc 的开发者发现有些二进制程序没有检查它,因此偏离了规范,改为返回哨兵值 "*"。

正如我之前所写,类型系统对工程文化有强烈影响。由于 C 没有代数数据类型或结果类型,围绕 C 社区形成的工程规范导致了这种带内信号传递。与此同时,在 Rust 中,过长的密码会作为错误变体返回,调用者将被迫处理它。如果今天还没有错误变体呢?那么一个破坏性变更会添加一个错误变体,并且很可能还会发布一份RustSec 安全公告。

顺便说一句,当 Linux 发行版维护者抱怨项目引入 Rust 依赖时,我仍然感到惊讶。这些维护者不是去适应工程规范可能更好地服务用户的新世界,而是坚持让世界适应他们。是的,编译时间和到小众架构的可移植性是真实成本,但你是来服务自己的利益还是大多数用户的利益?这里存在一个真实的委托-代理问题。

Agent 救不了你#

说到 agent!最近有一种论点大致是这样的:

  • Rust 诚然对今天的人类编写正确软件很有用。
  • LLM 现在或不久的将来能够直接推理内存安全和其他错误情况。
  • 所以在 agent 密集的开发生命周期中,Rust 和 C(甚至汇编)在软件严谨性上没有区别。
  • 但 C 在构建速度、可移植性等方面比 Rust 有某些优势。
  • 所以我们不妨用 C(甚至汇编)来写软件。

这个论点有很多错误之处,包括在我们已经证明一种更加确定性的方法行之有效的情况下,仍然做出信任模糊流程的可疑决定。但最大的缺失部分是各自社区的规范!当你(或你的智能体)编写 Rust 软件时,你很可能要使用 Rust 库,而你的智能体很可能在其训练集中对现有 Rust 代码进行模式匹配。因此,即使你还不是熟练的 Rust 开发者,类型系统中也很可能编码了更多的不变量1。

C 开发者的工程规范允许 "*" 作为表示错误的可接受哨兵值;Rust 开发者的工程规范则不然。这些规范是一个社区累积贡献的结果,而软件专业人士的部分责任就是思考自己的工作属于什么样的社区。但有些人已经认定,在这个崭新的世界里这些都不重要,其他人的贡献大多可以忽略。他们的傲慢将导致他们的失败。

在 Hacker News 上讨论。


  1. 如果你是熟练的 Rust 开发者,你很可能会发现智能体编码的第一轮不变量严重不足,并且你可能会经历一个漫长的审查周期来修复这类问题。 ↩︎

来源:Hacker News · sunshowers.io