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 < 0. bsd_stubs.c:236
这是分析程序如何跟踪变量的可能值的一个例子,在函数开始时,将索引变量与零进行比较。如果它小于零,则将在内部块中为变量赋值一个不小于零的值。因此,下一个外部if会再次检查index变量的值是否小于零。但是,这是不可能的。
这不会改变程序的逻辑,不过,也有可能暗示了其他条件。也就是说,在任何情况下,额外的检查并不能使代码更具可读性和可理解性。
Fragment N4
PVS-Studio警告如下:
V547表达式'bp->nb_dirtyoff >= bp->nb_dirtyend'始终为false. nfs_bio.c 3858;
V560条件表达式的一部分总是true: (bp->nb_dirtyoff < end). nfs_bio.c 3862;
请注意,这不是代码的完整形式。
现在让我们从第一个警告开始,分析程序确定nb_dirtyoff不能大于或等于nb_dirtyend。在可疑检查之前,还有两个if,带有 (bp->nb_dirtyend > 0) && (bp->nb_dirtyoff < end) 和bp->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 < end检查。因此,由于上述情况的错误,内部检查是无意义的。
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) < = 0为假,则将使用新的n值。由于我尚未使用过新的源代码,因此很难确定哪种行为是正确的。
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还是发现了错误和漏洞。
本文翻译自:https://www.viva64.com/en/b/0818/如若转载,请注明原文地址: