MacOS内核安全性检测

macOS Big Sur 从亮相至今,接近一年的时间,还没有稳定下来。有人惊叹道,macOS 系统沦落到需要重启电脑来解决问题。的确,macOS Big Sur 让不少用户用上了重启的功能。特别是在使用这个系统第一个正式版的时候,重启电脑的次数比过去几年的总量都要多。

相对 Windows 系统来说,macOS 升级的频繁高。其实,更新频繁高不是问题,关键是系统不稳定,影响到日常使用,这个问题在 macOS Big Sur 上最为突出。特别是电池问题,无论是 MacBook Pro 无法充电,还是耗电快,都困扰到不少用户。

在今年年初,苹果发布了 macOS Big Sur 的源代码。它包括 XNU, macOS 操作系统的内核。几年前,PVS-Studio 已经检查了内核源代码,它与在 macOS 上发布的分析程序相一致。PVS-Studio 是由俄罗斯公司开发的一款简单易用的静态代码分析工具,主要目的是在最初阶段就能识别和修复错误,防止后期浪费大量时间来寻找错误,提升效率的同时保障质量,可用于检测程序源代码中的错误,使用该软件,帮助用户快速检测 C / C / C 0x 应用程序源代码中的错误。

XNU – X 不是 Unix,它是苹果公司为 Mac OS X 操作系统开发的。这个内核的源代码是 20 年前根据 APSL ( 苹果公共源代码许可证 ) 和 OC Darwin 一起发布的。以前,你甚至可以将 Darwin 安装为一个成熟的操作系统。然而,现在这已经不可能了。源代码很大程度上是基于其他开源项目的。这就是为什么它的源代码被发布了。

你可以点击这里 https://opensource.apple.com/ 找到组件的源代码,我使用的是 GitHub 上的镜像来检查。

如上所述,几年前,PVS-Studio 已经检查了内核源代码,相关的发现请点此 https://www.viva64.com/en/b/0566/。

最新的内核安全性检测

说实话,这个项目有复杂的代码,我也没有使用这样的代码库的经验。然而,PVS-Studio 的警告非常详细。有一个指向文档的链接,其中包含正确和错误的代码示例。在这次检查中,cloc 统计了 1346 个 *. C 文件,1822 个 C/ c 标头文件,225 个 *.cpp 文件在项目中。

Fragment N1

PVS-Studio 警告:V1064 整数除法的 “gPEClockFrequencyInfo.bus_clock_rate_hz” 操作数小于 “gPEClockFrequencyInfo.dec_clock_rate_hz” 之一。结果将始终为 zero. pe_identify_machine.c 72。

此处使用的所有字段均为整数类型:

通过中间赋值,将分隔字段 gPEClockFrequencyInfo.bus_clock_rate_hz 赋值为值 100000000,将除数字段 gPEClockFrequencyInfo.dec_clock_rate_hz 赋值为值 1000000000。在这种情况下,除数比被除数大十倍。由于此处的所有字段都是整数,因此 gPEClockFrequencyInfo.bus_to_dec_rate_den 字段为 0。

根据生成的 bus_to_dec_rate_den 字段的名称判断,除数和被除数混淆了。该代码的开发者可能认为初始值会发生变化,因此结果将不再等于 0。但是,此代码对我而言仍然非常可疑。

Fragment N2

PVS-Studio 警告:V614 使用未初始化的变量 ‘best’ used. sdt.c 572

使用这个方法的目的就是要寻找某个函数的名称,该算法使用最佳变量。它可能是结果的最佳候选人。但是,最初仅在不初始化的情况下声明此变量。下次使用将检查具有最佳变量的某个元素的值,该变量此时将未初始化,甚至仅在使用其自身值的条件内对其进行了初始化。

未初始化的变量可能导致不可预知的结果,尽管这个错误看起来微不足道,但在使用 PVS-Studio 检查不同的项目时仍然很常见。

Fragment N3

PVS-Studio 警告:V560 条件表达式的一部分始终为 false: index

这是分析程序如何跟踪变量的可能值的一个例子,在函数开始时,将索引变量与零进行比较。如果它小于零,则将在内部块中为变量赋值一个不小于零的值。因此,下一个外部 if 会再次检查 index 变量的值是否小于零。但是,这是不可能的。

这不会改变程序的逻辑,不过,也有可能暗示了其他条件。也就是说,在任何情况下,额外的检查并不能使代码更具可读性和可理解性。

Fragment N4

PVS-Studio 警告如下 :

V547 表达式 ‘bp->nb_dirtyoff >= bp->nb_dirtyend’ 始终为 false. nfs_bio.c 3858;

V560 条件表达式的一部分总是 true: ( bp->nb_dirtyoff

请注意,这不是代码的完整形式。

现在让我们从第一个警告开始,分析程序确定 nb_dirtyoff 不能大于或等于 nb_dirtyend。在可疑检查之前,还有两个 if,带有 ( bp->nb_dirtyend > 0 ) && ( bp->nb_dirtyoff nb_dirtyend > end 检查。同样,bp->nb_dirtyend = end 执行被赋值。

为什么第三个 bp->nb_dirtyoff >= bp->nb_dirtyend 检查总是假的 ?

它是如此简单,从条件来看,nb_dirtyoff 小于 end, nb_dirtyend 等于 end。因此,nb_dirtyend 肯定大于 nb_dirtyoff。bp->nb_dirtyoff = bp->nb_dirtyend = 0 赋值将永远不会执行。

最终,我们有了以下代码部分:

可以把它简化成以下这样 :

但前提是这个算法在此时才能显示出正确的答案。

如果嵌套在第一个警告中,第二个警告指示第四个警告。

此时,分析程序将基于永远不会执行零赋值这一事实发出警告。因此,外部条件已经有了 bp->nb_dirtyoff

Fragment N5

PVS-Studio 警告:V793 奇怪的是,”len optlen” 语句的结果是条件的一部分。

这是一个相当简单的漏洞。在这种情况下,将两个变量简单地添加在一起,而不是布尔表达式。最终,仅当总和等于零时,表达式才为假。如果这是隐含的,那么可能值得显然与 0 进行了比较。那么,条件正确性的问题就不会困扰我们了。

也许,这是故意的。然而,在代码中还存在有这样的检查 :

这表明,比较也应该在两个 if 中发生。

而且,此功能减少到 16 行,在原有形式中占 2268 行 !

以下是同一部分的第二个警告:

V793 奇怪的是,’len optlen’ 语句的结果是条件的一部分。

Fragment N6

PVS-Studio 警告:V793’tp-> t_rawq.c_cc tp-> t_canq.c_cc’ 语句的结果是条件的一部分,这很奇怪。

还有一个检查吗,它使用总和,并将结果与另一个变量进行比较:

在简化的代码中,分析程序指出的条件是显而易见的。然而,在初始代码中,它嵌套在多个 if 中。因此,在代码审查时很容易忽略它。不过,分析程序不会忽略它。

Fragment N7

pvp – studio 警告 :V1028 可能溢出。考虑将 ‘amount used’ 运算符的操作数强制转换为 ‘size_t’ 类型,而不是 result. kpi_mbuf.c。

同样如果条件发生错误,则结果完全不同。加法结果被转换为 size_t。此时,加法操作数必须转换为 size_t,以便结果完全符合数字类型。如果由于加法而发生了溢出,那么将把这个无意义的值缩减为 size_t,并与 mbuf_maxlen ( m ) 的结果进行比较。因为程序员想要防止溢出,所以必须正确执行 :

有几种此类警告,如下所示:

V1028 可能溢出。考虑转换操作数,而不是转换结果。vm_compressor_pager.c 1165

V1028 可能溢出。考虑转换操作数,而不是转换结果。vm_compressor_pager.c 1131

V1028 可能溢出。考虑转换操作数,而不是转换结果。audit_worker.c 241

V1028 可能溢出。考虑将 ‘ ( ( u_int32_t ) slp * hz ) 999999’ 操作符的操作数转换为 ‘long’ 类型,而不是结果类型。tty.c 2199

Fragment N8

V1019 复合赋值表达式 ‘n -= i’ 被用于 condition. kern_descrip.c_99 3916 内部

这段代码很难被读懂,也许应该简化一下:

这段代码似乎不太有效,但是肯定更容易理解。要快速检查代码等效性,请转到 Godbolt(编译器资源管理器)。顺便说一句,在那里你可以测试 PVS-Studio 检查程序的工作。在此服务的工具中很容易找到分析程序。

如果不启用优化,则程序集代码将只有几行。但是,要注意 if 的主体。不使用新的 n 值。也就是说,很有可能在这里不需要赋值。

此外,当进一步使用 n 变量时,源代码可能会导致错误。如果表达式 ( n -= i )

Fragment N9

PVS-Studio 警告:V764 传递给 ‘vsock_pcb_safe_reset_address’ 函数 :’dst’ and ‘src’. vsock_domain.c 549 可能的错误参数顺序

也许这不是一个错误,但是,此阶段中调用的函数的签名看起来非常可疑:

在此片段中使用此函数时,名称相似的最后两个参数将以不同的顺序传递。

以下是同一个警告的不同内容:

V764 传递给 ‘vsock_pcb_safe_reset_address’ 函数的可能错误参数顺序:’dst’ 和 ‘src’. vsock_domain.c 587;

V764 传递给 ‘vsock_pcb_safe_reset_address’ 函数的可能错误参数顺序: ‘dst’ 和 ‘src’. vsock_domain.c 590;

Fragment N10

PVS-Studio 警告:V1051 考虑检查打印错误,有可能在 here. classq_subr.c 685 检查 ‘tbr->tbr_last’

在项目中,此检查无法以最佳方式运行。发生这种情况是因为外部变量在条件或循环的主体中的代码中不断进行初始化。这些变量的名称与条件中使用的名称相似。因此,这次检查程序发出了几个明显的错误警告。条件主体中未使用选中的 tbr_rate 字段。初始化时比该检查多 35 行。这就是为什么上述警告对我来说仍然可疑的原因。但是,在此检查之前初始化的 tbr_last 字段未在其他任何地方使用。我们可以假设必须检查它而不是 tbr_rate 字段。

Fragment N11

PVS-Studio 警告:V571 重复检查。 ‘if ( ar->k_ar.ar_arg_mac_string == NULL ) ‘ 已在 245. audit_mac.c 246 行检查过了;

PVS-Studio 警告:V547 表达式 ‘ar-> k_ar.ar_arg_mac_string == NULL’ 始终为 true. audit_mac.c 246;

分析程序对这段代码同时发出了两个警告。

你可能会注意到第一个 if 中的检查与第二个 if 中的检查是相同的。此外,对于第二次检查还有一种解释 :

因此,在第二次检查中不应该有任何内部验证。我们只需要退出这个方法。所以,最有可能的是,内部检查是偶然重复的,没有任何意义。

尽管也许应该在内部检查中检查其他字段。但是,这里出现了复制粘贴错误。开发人员忘记更正字段名称。

Fragment N12

PVS-Studio 警告:V567 未定义行为

宏是非常难以理解的,但是,事实证明这种情况要容易一些。从 OSSwapInt16 ( *ucsp ) 表达式开始分析。

然后,我意识到有一个更简单的方法。我刚打开了项目检查后留下的 .i 文件。

最重要的是,表达式的这一部分引起了我们的注意:

表达式中的操作符都不是序列点,因为我们不知道 | 操作符的哪个参数将首先被赋值,所以 *uscp 的值没有定义。

对于 V567 检查,PVS-Studio 提供了非常详细的文档。如果你想知道为什么这样的代码会导致未定义的行为,可以详细阅读此文档。

此时,程序员只打算将增加 *ucsp 的值增加一倍,事实上,这个值会增加两倍。二这个过程是看不见的,也是不清楚的。这就是宏非常危险的原因。在很多情况下,最好是写一个普通的函数。编译器最有可能自动执行替换,因此,不会发生性能下降。

Fragment N13

PVS-Studio 警告:V567 未定义行为。让我们看一下 htobe64 调用的行。经过预处理,分析人员发现该行可疑:

这个问题实际上和前面的例子一样。在带有 | 和 & 操作数的内链中没有序列点。因此,每个操作期间 pf_status.stateid 取什么值是不知道的,结果也不确定。

同样,该变量在一行中也会递增几次。

总结

这一次分析程序发现的错误比以前的检查少,这证明 XNU 开发过程中很可能使用了静态分析和其他代码质量控制工具。几乎可以肯定的是,该项目使用了 Clang 静态分析程序。然而,如上所述 PVS-Studio 还是发现了错误和漏洞。