daniel.haxx.se

Search Primary Menu Skip to content

Search for: cURL and libcurl, Development

Writing C for curl

April 7, 2025 Daniel Stenberg Leave a comment

我经常被问到一个问题:我们如何在 curl 中编写 C 代码,以确保数十亿次安装的安全? 这里有一些我们采取的预防措施和做出的决定。 没有银弹,只有一些指导原则。 我想你可以在下面看到,它们既不奇怪也不令人惊讶。

curl 中的 'c' 并不代表 C 编程语言,而是代表 client (客户端)。

免责声明

这段文字绝不意味着我们偶尔不会合并与安全相关的错误。 我们会。 我们是人。 我们会犯错。 然后我们修复它们。

测试

我们编写尽可能多的测试。 我们经常在代码上运行所有静态代码分析工具。 我们不停地在代码上运行fuzzers。

C 不是内存安全的

我们当然无法避免与内存相关的错误、失误或漏洞。 我们统计到目前为止,我们大约 40% 的安全漏洞是由于我们使用 C 而不是内存安全语言替代方案直接造成的。 然而,这个数字远低于通常重复的 60-70%,这些数字来源于一些大型公司和项目。 我无法判断这是因为计数方式不同,还是我们实际上 C 问题的数量较少。

在过去五年中,我们没有收到任何报告指出存在_严重_漏洞,只有两个漏洞被评为_高_严重性。 其余的(60 多个)漏洞的严重性为_低_或_中_。

我们目前有接近 180,000 行 C89 生产代码(不包括空行)。 我们坚持使用 C89,以获得最广泛的移植性,并且因为我们相信持续不断地迭代和改进,而不是重写。

可读性

代码应该易于阅读。 它应该清晰明了。 不要将代码隐藏在巧妙的结构、花哨的宏或重载之下。 易于阅读的代码易于审查、易于调试和易于扩展。

较小的函数比较长的函数更容易阅读和理解,因此更受欢迎。

代码应该读起来像是同一个人编写的。 应该有一种一致且统一的代码风格,这有助于我们更好地阅读代码。 错误或不一致的代码风格是一个 bug。 我们修复所有发现的 bug。

我们有工具可以验证基本的代码风格合规性。

窄代码和短名称

代码应该写得窄。 阅读长行代码很伤眼睛,因此我们强制执行严格的 80 列最大行长度。 我们使用两个空格的缩进,以便在列限制成为问题之前,仍然允许我们进行一些缩进。 如果缩进级别成为问题,也许应该将其拆分为几个子函数?

同样相关:(特别是局部) 标识符和名称应该简短。 过长的名称使它们难以阅读,尤其是在有多个相似的名称时。 更不用说,在经过一定数量的缩进后,它们可能很难容纳在 80 列中。

现在很多人会开玩笑地说可以使用宽屏等等,但这里的关键是可读性。 更宽的代码更难阅读。 句号。 问题可能在于究竟在哪里划定界限,这是一个每个项目都需要讨论的问题。

没有警告

虽然对于每个人来说都应该是很自然的事情,但我们当然会在我们执行的 220 多个 CI 作业中的任何一个中,完全没有任何编译器警告的情况下构建所有 curl 代码。 我们使用我们使用的一组编译器存在的所有最挑剔的编译器选项来构建 curl,并消除出现的每个警告。 我们将每个编译器警告都视为一个错误。

避免 “坏” 函数

由于缺少边界控制或局部状态,有些 C 函数非常糟糕,我们避免使用它们 (gets, sprintf, strcat, strtok, localtime 等)。

有些 C 函数在其他方面很复杂。 它们具有过于开放的功能,或者做的事情经常会导致问题,或者仅仅是错误的; 它们很容易导致我们犯错误。 由于这些原因,我们避免使用 sscanf 和 strncpy。

我们有工具_禁止_在我们的代码中使用这些函数。 尝试在 pull request 中引入对其中之一的使用会导致 CI 作业变红,并提醒作者他们的错误。

缓冲区函数

多年前,我们发现自己在处理不同动态缓冲区的代码中犯了几个错误。 我们有太多独立的实现来处理动态增长的内存区域。 我们使用一组新的内部帮助函数统一了这种处理,用于增长缓冲区,现在确保我们只使用这些函数。 这大大减少了对 realloc() 的需求,这有助于我们避免与该函数相关的错误。

每个动态缓冲区也有其自己设置的最大大小,这在其简单性方面也有助于捕获错误。 在当前的 libcurl 代码中,我们有 80 多个不同的_动态缓冲区_。

解析函数

我提到过我们不喜欢 sscanf。 这是一个强大的解析函数,但它经常解析超出用户想要的内容(例如,即使只应接受一个空格,它也会解析多个空格),并且它对整数溢出的处理很弱(不存在)。 最后,它引导用户不必要地复制解析结果,导致过度使用局部栈缓冲区或短期的堆分配。

相反,我们引入了另一组用于字符串解析的辅助函数,并且随着时间的推移,我们将 curl 中的所有解析器代码都切换到使用这组函数。 这样可以更轻松地编写严格的解析器,该解析器仅精确匹配我们想要匹配的内容,避免额外的复制/malloc,并且它可以更好地进行严格的整数溢出和边界检查。

监控内存函数的使用

内存问题通常涉及动态内存分配,然后将数据复制到已分配的内存区域中。 或者,也许如果分配和复制都正确完成,则没有问题,但如果其中任何一个出错,事情可能会变得糟糕。 因此,我们的目标是最大限度地减少这种模式。 我们宁愿支持 strdup 和内存复制,它们在同一调用中分配和复制数据,或者使用可能在其 API 后面执行这些操作的辅助函数。 我们在 curl 仪表板中运行每日更新的图表,该图表显示 curl 中的_内存函数调用密度_。 理想情况下,该图应随着时间的推移而不断下降。

随时间推移,curl 生产代码中的内存函数调用密度

也许还可以补充一点,我们避免不必要的内存分配,尤其是在热路径中。 大量下载不需要比小量下载更多的分配。

仔细检查乘法

整数溢出是另一个需要关注的领域。 必须确保完成的每个算术运算都不会溢出。 遗憾的是,这仍然主要是一项体力劳动,留给人工审查来检测。

保证 64 位支持

在 2023 年初,我们放弃了在没有功能性 64 位整数类型的系统上构建 curl 的支持。 这简化了许多代码和逻辑。 整数溢出不太可能触发,并且作者不会意外地认为他们执行的是 64 位算术,而它最终可能在某些罕见的构建中是 32 位,就像过去可能发生的那样。 当然,如果使用错误的类型,仍然可能发生溢出和错误。

最大字符串长度

为了帮助我们避免字符串上的错误,特别是整数溢出,以及其他逻辑,我们对库的所有字符串输入进行常规检查:它们不接受超过设定限制的字符串。 我们认为,任何长度超过此限制的字符串设置要么只是一个明显的错误,要么是一些尝试(攻击?)以触发库内部某些奇怪行为的尝试。 我们在此类调用中返回错误。 目前,此最大限制为 8 兆字节,但我们将来可能会随着世界和 curl 的发展对其进行调整。

保持 master 分支的纯净

在任何时候都不允许破坏 master 分支。 我们只将我们认为是干净、精细且运行完美的代码合并到 master 中。 这有时仍然会失败,但随后我们会尽力尽快解决这种情况。

始终检查错误并采取行动

在 curl 中,我们始终检查错误,并且如果(当!)发生错误,我们会_在不泄漏任何内存_的情况下退出。 这包括所有内存操作、I/O、文件操作等。所有调用。

一些开发人员习惯于现代操作系统基本上无法为其中一些操作返回错误,但是 curl 在许多具有不同行为的环境中运行。 此外,系统库不能在错误时 exitabort,它需要让应用程序做出该决定。

API 和 ABI

任何公开可访问的函数和接口都绝不能以可能破坏 API 或 ABI 的方式进行更改。 出于这个原因,并且为了便于发现需要这些额外预防措施的函数,我们有一条严格的规则:公共函数以 "curl_" 为前缀,并且没有其他函数使用该前缀。

每个人都可以做到

由于人工审查员的流程、大量的自动化工具以及详尽而广泛的测试套件,每个人都可以(尝试)编写 curl 代码。 当然,前提是你了解 C。无论代码的作者是谁,都不会被发现不良代码的风险大致相等。 责任是分担的。

去吧。 你能做到! cURL and libcurlDevelopment

Post navigation

Previous Postcurl 8.13.0

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked * Comment * Name * Email * Website Time limit is exhausted. Please reload CAPTCHA. 41ninefive1 Δ This site uses Akismet to reduce spam. Learn how your comment data is processed.

Recent Posts

Recent Comments

curl, open source and networking

Sponsor me: on GitHubFollow me: @bagderKeep up: RSS-feedEmail: weekly reports April 2025 M | T | W | T | F | S | S
---|---|---|---|---|---|---
1| 2| 3| 4| 5| 6
7| 8| 9| 10| 11| 12| 13
14| 15| 16| 17| 18| 19| 20
21| 22| 23| 24| 25| 26| 27
28| 29| 30
« Mar Privacy Proudly powered by WordPress