第一个开源库PR:openssl!
- 技术
- 2025-12-12
- 59热度
- 0评论
PR的标准开局,崩溃的debugging
背景:
在开发基于 OpenSSL 3.0 的高性能抗量子密码(PQC)网关的时候,用了 OpenSSL 的 ASYNC_JOB(异步任务)机制配合自定义 Provider。
现象:
在单线程下一切正常,但一旦开启多线程并发 + Docker 环境,ASYNC_start_job 就会随机返回错误。
最让人抓狂的是错误的表现形式:
* 返回值:ASYNC_ERR (0) —— 表示出错。
* errno:0 —— 操作系统说“我没报错”。
* ERR_print_errors_fp:空 —— OpenSSL 错误堆栈里什么都没有。
。。。我直接懵逼,这是要干啥,啥错误也没有,开始慢慢排查
依旧DEBUGGING
排查过程:
在排除了代码逻辑、Docker 静态链接库冲突、TLS(线程本地存储)问题后,问题依然存在。
下载了 OpenSSL 源码,在 crypto/async/async.c 中加入了大量的 fprintf 调试日志,重新编译并替换了库文件。
真相大白:
日志最终定格在了一行内存分配代码上:
[DEBUG] OpenSSL_malloc for funcargs failed
原来是 OPENSSL_malloc 返回了 NULL。但是,为什么内存会分配失败?系统内存明明很充足。
进一步追踪发现,调用时的参数大小 size 竟然是 0。
// 伪代码还原现场
ASYNC_start_job(..., args=ptr, size=0);
根本原因:
malloc(0) 的行为在 C 标准中是“实现定义(Implementation Defined)”的:
* 在大多数 glibc 环境下,它返回一个唯一的指针。
* 但在特定的 Docker 环境或 hardened libc 实现下,malloc(0) 可能会返回 NULL。
OpenSSL 的原逻辑没有考虑到这一点,它简单粗暴地认为:只要 malloc 返回 NULL,就是内存不足(OOM),于是报错退出。 但因为它认为这是 OOM,却没有压入错误堆栈,导致了上层的“静默失败”。
看看咋修罗
修复思路:
既然 size 是 0,意味着没有数据需要拷贝。那么,根本就不应该去调用 malloc,更不应该因为 malloc(0) 返回 NULL 而报错。
补丁:
在逻辑中加入了一个简单的判断:
/* Modified check: avoid malloc(0) */
if (args != NULL && size > 0) { // 增加了 size > 0 的判断
ctx->currjob->funcargs = OPENSSL_malloc(size);
// ... 错误检查 ...
memcpy(ctx->currjob->funcargs, args, size);
} else {
ctx->currjob->funcargs = NULL; // size 为 0 时直接置空,这是安全的
}
这不仅修复了 Bug,还省去了一次无意义的内存分配,逻辑更加健壮。
提交
不得不感慨他,大型项目提交PR的流程非常严谨:
- 格式规范:代码逻辑要对,连缩进(4空格)、大括号位置都有严格要求。用 OpenSSL 自带的
check-format.pl脚本进行检查。 - Commit 规范:提交信息必须清晰描述“Why”和“What”。
- CLA(许可协议):第一次提交,OpenSSL 的机器人给我打了个红色的
hold: cla required标签。后面 发现是需要在 git commit message 的末尾显式加上CLA: trivial(表示这是微小修改,无需签署复杂法律文件)。- 解决方法:使用
git commit --amend修改提交记录,然后git push -f。
- 解决方法:使用
APPROVE!!!
不得不说,第一次为这么大的项目提交PR总归是有些小兴奋在滴,看到一步一步
1. CI 自动化测试全部通过。
2. Reviewers(Sashan 和 jogme)分别在20分钟和2个小时后急速的accept,在这过程中还一直期待反复刷新。
3. PR 获得了 approval: done 标签!

我提交的代码就这样会被合并到 OpenSSL 的主分支(master)以及 3.3/3.5/3.6 等版本中。
一点感悟
Debugging是痛苦的,APPROVE是高兴的,所以大家debugging不出来就去提pr吧(不是)
PR Link: Fix silent failure in ASYNC_start_job when size is 0 #29377

