我第一次给 Linux 内核做贡献,不仅被剥夺了,还遭到了维护者的轻视…

编译 | 苏宓

出品 | CSDN(ID:CSDNnews)

开源浪潮之下,无数开发者的角色正在发生巨大变化,从过往的使用者逐渐变成开源项目参与者、贡献者。

然而,近日,一位来自思科且充满热情的软件工程师 Ariel Miculas 在参与开源贡献过程中,备受打击,以至于他发布了一篇主题为《我的第一个内核贡献是如何被剥夺的》的长文,控诉自己耗费了大量的时间、精力首次为 Linux 内核做的贡献,不仅被 Linux 内核维护人员给“打回”了,更关键点在于 Ariel 提出的问题被维护者关注,维护者使用其自己的修复版本,却只给予 Ariel 一个“报告者”而非“贡献者”的头衔。

这导致“我感到被轻视,对我的工作没有得到适当的认可感到愤怒”,Ariel Miculas 说道。

这究竟是怎么一回事,我们将从他的亲身经历中一探究竟。

一个 Bug 引发的“连锁反应”

事情要从一年半前开始说起,Ariel 曾向他以前的公司申请时间来解决一个影响项目调试能力的问题——gdbserver 无法调试在 PowerPC32 架构上运行的多线程应用程序。与 gdbserver 的连接断开了,它无法再控制调试会话。

这里的 gdbserver(GDB Server),是一个用于调试程序的工具,特别是针对嵌入式系统和远程目标的调试。它允许开发人员使用 GNU 调试器(GDB)与运行在另一个计算机或设备上的程序进行交互式调试。

事实上,在此之前,也有很多人遇到了同样的问题,但就是不能确定问题究竟出现在了哪个软件组件上,也许是工具链、gdbserver、Linux 内核或 Ariel 团队在内核树上应用的自定义补丁。

虽然有大致方向,但是仍然没有具体的头绪,这也导致 Ariel 团队一时之间也找不到问题的根本原因和解决方案。

深挖 Bug 源头,寻找修复方式

正如上文所述,行业内也有很多人在遇到同样的问题时展开调查,而 Ariel 结合了他们现有分析并使用 Google 搜索查阅大量资料之后,取得了第一个突破。

Ariel 在网络上找到了一封 2016 年的邮件列表,此列表中不仅描述了与他遇到问题的相同症状,还指出了引入问题的确切提交。

来源:https://lore.kernel.org/linuxppc-dev/dc38afe9-6b78-f3f5-666b-986939e40fc6@keymile.com/

具体而言,引入错误的补丁将 thread_struct thread 的定义从 task_struct 的中间移动到了末尾,这是一个看似无害的改变。

但是当时一位名为 Holger Brunck 开发者在调试问题后发现:

我看到 gdbserver 为每个线程发送一个 SIGSTOP 信号给内核并等待响应。内核确实接收到了所有信号,但只在错误情况下对其中一些信号作出响应。

这与我的“ps”输出相匹配,因为我看到有些线程不处于 pthread_stop 状态,然后 gdbserver 被挂起。

这是一个低级问题,因为在与 gdbserver 交互后,某些线程处于错误的进程状态,gdbserver 无法再控制它们。

为此,Ariel 花了 3-4 天时间去详细地了解阅读与 PowerPC 架构相关的提交描述以及关于 task_struct 的更改,试图弄清楚这个问题是否在后续的内核版本中得到解决(这里剧透下:其实并没有解决)。

于是,Ariel 排查了 thread_struct thread 以确定何时出现问题,并使用了一系列辅助工具,如 pahole 检查 task_struct 的布局;ftrace 来确定被调试进程的线程何时被调度等等。

直到这时,Ariel 突然意识到这可能是一个内存损坏问题,因为不像其他线程那样,被卡住的线程只被调度一次。

起初,Ariel 忽略了这可能是一个内存损坏问题,因为在原始线程中提到:

缓冲区的内容总是零,不会更改。所以至少没有人向缓冲区写入非零值。

在这个过程中,Ariel 犯下了一个错误,他没有验证结构是否被零字节覆盖,这里也警醒很多开发者,要学会不断验证你的假设。

在进一步查找解决方案过程中,凭借多年的软件工程师经验,Ariel 记得 x86 架构有可以用来触发数据写入断点的调试寄存器。果然,PowerPC 也通过 DABR 寄存器实现了类似的功能。

在此基础上,Ariel 调查了如何在 Linux 上使用硬件断点,最终他在参考程序员问答社区 StackOverflow 上的一个答案(https://stackoverflow.com/a/19755213)实现了新的 Linux 内核模块。该模块能够在 __state 字段上放置硬件断点,以找出是谁在写入它。

详细来看,Ariel 通过自定义内核模块显示了写入 task_struct 的 __state 字段的地方的堆栈跟踪。此时,Ariel 注意到一个异常值,揭示了 ptrace_put_fpr(POKEUSER API 使用的函数)中的缓冲区溢出。这导致了 task_struct 的重要字段被覆盖,例如 __state,该字段存储进程的状态,并且内核还用它来跟踪哪些进程被调试器停止了。

溢出的原因是什么?将一个应用于 32 位元素数组的索引用于 64 位元素数组。共有 64 个索引用于 FPR,因此可寻址的内存总共是 64 * 8 = 512 字节。但 fp_state.fpr 数组中只有 32 个条目,这意味着可用内存只有 32 * 8 = 256 字节。这允许用户(也就是 gdbserver)在数组末尾之后写入多达 256 字节。

发送补丁至上游,“挫折”也由此出现

找到了问题的根源也有了解决方案,Ariel 基于安全方面考虑,他认为这个可能导致进程状态内存被覆盖的内存损坏问题可能具有安全影响,于是他很兴奋地向 Linux 内核安全团队(security@kernel.org)发送了一个补丁。

不幸的是,这个邮件列表是私人的,所以他无法链接到自己发送的原始补丁。

后来 Linux 内核安全团队安排了 PowerPC 的维护者 Michael Ellerman 来进行了跟进,并告诉 Ariel 会私下联系他来解决这个问题。

此时,Ariel 已经给 Michael Ellerman 发送了两个修复问题的补丁,根据其描述:

  • 一个是我发送给安全邮件列表的原始补丁;

  • 另一个版本(与第一个版本相当不同),它解决了对原始提交的回复中收到的一些建议。后一个补丁实际上是基于现有的内核代码,模拟了 PowerPC64 上的 PowerPC32 操作(他们正确处理了 FPR 索引)。

然而,Michael Ellerman 没有接受这两个补丁,而是实施了他自己的修复版本。

在这一操作背后,Ariel 曾与 Michael Ellerman 沟通过,“如果能接受我的补丁,我会非常感激,这样我就可以因修复这个问题而得到认可,并成为内核贡献者。我也愿意与他合作,解决他的反馈,并发送后续版本的补丁。”

万万没想到的是,Michael Ellerman 回复称(意译):

对不起,我更喜欢我的版本。如果你想成为 Linux 内核贡献者,这里有一个问题可以让你来解决。

对于这样的答复,Ariel 觉得困惑和侮辱。Ariel 认为,“他并不是想让我为修复问题而受到认可,而是想让我多做更多工作。我和我的公司应该得到适当的荣誉,因为我们花了很多心血来解决这个问题。”

最终,Ariel 只得到一个“Reported-by”的标签,这也让他感觉真的很不公平。

Reported-by 标签是给予那些发现错误并报告的人以荣誉,并希望激励他们将来再次帮助我们。

Ariel 表示,「我绝对没有感到受到鼓舞,再次参与内核社区。相反,我感到被轻视,对我的工作没有得到适当的认可感到愤怒」。

Ariel :我的第一个内核贡献经历非常令人沮丧

在 Ariel 看来,自己花了大量的时间和精力进行根本原因分析,然后去修复错误、测试和调试 Bug,与此同时,也从公司的其他工程师那里获得反馈,将最新的修复去适应最新的内核版本,并向 PowerPC 维护者 Michael Ellerman 发送了两个不同的补丁。

遗憾的是,Michael Ellerman 没有接受 Ariel 的补丁,也没有引导他找到更好的解决方案,而是自行实施了他自己的修复版本,只给 Ariel 报告问题的荣誉(而这个问题其实在六年前就已经被其他人披露出来了)。

「我的第一个内核贡献经历非常令人沮丧和令人泄气,因为要与那些认为得不到适当认可并不重要的人打交道」,Ariel 在自己的长文最后写道。

写在最后

现实来看,「名」与「利」也是驱动开发者不断反哺开源社区的一层重要驱动力。不过,很多人也认为维护者这样做也有自己的考量,从而在 HN 上引发了两种主要观点的碰撞。

其中,一部分人认为:

即使不接受 Ariel 的完整补丁,也应该把功劳归于他。

显然,如果不是他发现并提交了修复补丁,那么也不会有人会去修复这个安全问题。

尤其是如果 Michael 看了他的补丁,改了一些风格上的东西,然后自己提交了补丁,那么不给予肯定是不道德的。我很惊讶大家都认为维护者是对的。

如果有人剽窃了你的工作,并将其全部归功于你,尽管这是一次合作(即使 Ariel 的代码是垃圾代码,但这仍然是一次合作),你也不会觉得这样做对,也不会想再与他们合作。

也有开发者在深挖了维护者的代码之后,认可维护者的做法:

维护者的补丁更好。

1. 它不会丢失 32 位情况下的 fpidx < (PT_FPSCR – PT_FPR0) 测试。

2. 在 put 函数中,它不会丢失 *data = child->thread.fp_state.fpscr; 回退赋值,该赋值在 fpidx 越界时使用。

3. 原始补丁的提交信息说 “在 PPC32 上,假定 TS_FPRWIDTH 为 1 也是可以的”,但随后还是包含了这样的内容:

  #define FPRNUMBER(i) (((i) – PT_FPR0) >> 1)

  #define FPRHALF(i) (((i) – PT_FPR0) & 1)

  #define FPRINDEX(i) TS_FPRWIDTH * FPRNUMBER(i) * 2 + FPRHALF(i)

很抱歉,你(Ariel)发现了这种微不足道的数组错配问题,但这并不意味着你的错误补丁就必须被接受。

不过,维护者应该承认,Ariel 并没有简单地报告这个问题,而是通过调查找出了问题的根本原因,并用他自己的版本进行了几乎同样简单的修复。

只给一个 Reported-by 的头衔缺乏准确性,无法表达问题是由该人彻底调查并找出根本原因的。

最后,有用户评论道,需要给足够的认可,才能吸引更多的贡献者参与到开源项目中,否则会让他们不断「后退」:

我同意维护者的工作更好。但他本可以只对补丁进行代码审查,然后要求更新。

这种情况经常发生在项目的新贡献者身上,这才是正确的做法–它可以教导新贡献者,让他们尽快适应。如果你只是重做他们的工作,就会打击他们贡献的积极性,同时也不给他们可学习的机会。

对此,你怎么看?

本文文字及图片出自 CSDN

你也许感兴趣的:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注