AOSP Source Releases Are So Late Developers Disassemble Libraries to Report Bugs

发布时间 2026-10-14 1 天前
来源 Hacker News 热门
字数 2,682 字
查看原文

智能总结

开发者反馈 AOSP 源码发布严重滞后,以至于他们只能反编译预编译库来定位和报告 bug。由于官方源码与实际编译产物之间存在差距,相关 issue(如 android/ndk#2260)在 Hacker News 上引发 234 条讨论和 480 个关注点,凸显 Android NDK 维护者在源码可获得性上的不足。

[BUG] bionic:wcscspn()/wcspbrk() 将所有 wchar_t >= 0x100 当作匹配项(Android 17,CP3A.260905.009)

描述

在 2026 年 9 月构建的 Android 17 版本上,当拒绝集合包含两个或更多全部小于 U+0100 的字符时,wcscspn() 与 wcspbrk() 会停在字符串中第一个大于等于 U+0100 的字符处,并将它当作匹配项,即使它并不在该集合中。wcsspn()、wcschr()、wcsrchr()、wmemchr() 与 wcsstr() 的行为是正确的。

任何用宽字符串扫描 ASCII 分隔符(例如换行符或通配符)的代码,只要文本中包含表情符号、CJK(中日韩统一表意文字)、西里尔字母、希腊字母或拉丁扩展字符,就会被破坏。

受影响 / 未受影响

设备 构建版本 结果
Pixel 7 google/panther/panther:17/CP3A.260905.009/16091614:user/release-keys 异常
Pixel 8a google/akita/akita:17/CP3A.260905.009/16091614:user/release-keys(2026-09-05 补丁) 异常
Nothing Phone 2 Nothing/PongEEA/Pong:16/BQ2A.250721.001-BP2A.250605.031.A3(2026-08-01 补丁) 正常

公开的 android17-release 源码(android-17.0.0_r1,2026 年 6 月)从 external/llvm-libc 构建 wcscspn/wcspbrk,这是对 wchar_t 的一个普通嵌套循环,行为正确。9 月构建中的代码不同(参见"机制"部分),因此该回归问题是在 6 月与 9 月两次发布之间引入的。

复现

使用 NDK(aarch64-linux-android30-clang)构建,推送到 /data/local/tmp,通过 adb shell 运行:

#include <stdio.h>
#include <wchar.h>

int main(void)
{
    wchar_t s[] = {'a', 0x1F680, 0x100, 'b', 'x', '\n', 0};
    printf("wcscspn(s, L\"x\\n\") = %zu (expected 4)\n", wcscspn(s, L"x\n"));
    printf("wcspbrk(s, L\"x\\n\") = index %ld (expected 4)\n", (long)(wcspbrk(s, L"x\n") - s));
    printf("wcsspn(s, L\"a\\U0001F680\") = %zu (expected 2)\n", wcsspn(s, L"a\U0001F680"));
    return 0;
}

CP3A.260905.009 上的输出:

wcscspn(s, L"x\n") = 1 (expected 4)
wcspbrk(s, L"x\n") = index 1 (expected 4)
wcsspn(s, L"a\U0001F680") = 2 (expected 2)

使用 wcscspn(L"\n", L"x\n") 遍历从 U+0001 到 U+10FFFF 的每一个码点(跳过代理区码点和那两个集合成员),结果对 1,111,808 个码点都返回 0 而不是 1:所有从 U+0100 到 U+10FFFF 的码点。U+0001 到 U+00FF 的结果是正确的。

哪些集合形式会触发该问题(全部在 Pixel 8a 上测得):

拒绝集合 结果
一个字符 正常
两个或以上字符,全部 < U+0100(例如 L"x\n"、L"\xe9\n") 异常
两个或以上字符,至少有一个 >= U+0100 正常
haystack(待扫描字符串)限制在 U+0080..U+00FF 范围内 正常

机制(取自已发布的 libc.so)

对 CP3A.260905.009 中 /apex/com.android.runtime/lib64/bionic/libc.so 里 wcscspn 的反汇编显示,代码根据集合选中三条路径之一:

  1. 一个字符:普通的逐位比较循环。结果正确。
  2. 两个或以上字符:在栈上分配一个 256 字节的 bool 表,由集合填充(前提是集合中的字符都在 1..0xFF 范围内)。如果看到某个集合字符 >= 0x100,函数会回退到对每个 haystack 字符调用 wcschr()。结果正确。
  3. 其他情况下,haystack 针对该表进行扫描:
a2564:  ldrb  w8, [x9, x8]        ; table[c]
a2568:  tbnz  w8, #0, a2578       ; 在集合中 -> 停止(正确)
a256c:  ldr   w8, [x22, #0x4]!    ; 下一个字符
a2570:  cmp   w8, #0x100
a2574:  b.lo  a2564               ; c < 0x100 -> 继续扫描
a2578:  ...                       ; c >= 0x100 -> 停止,并将其当作匹配项

对于所有成员都 < 0x100 的集合而言,>= 0x100 的字符不可能属于该集合,因此对 wcscspn 来说扫描必须越过它继续进行。但代码却直接停了下来。wcsspn 拥有相同的结构,在那种情况下停下来是正确的答案(该字符不属于保留集合),这也解释了为什么只有补集函数(即 wcscspn/wcspbrk)出错。wcspbrk 是基于该扫描实现的,因此继承了这个缺陷。

现有的 bionic 测试 TEST(wchar, wcspbrk)(位于 tests/wchar_test.cpp)只使用 ASCII 输入,所以它捕获不到这个缺陷。一条像 EXPECT_EQ(3u, wcscspn(L"aĀb\n", L"x\n")) 这样的测试就可以。

实际影响

Android 上的 Visual Pinball(libwinevbs,是 Wine 的 vbscript 引擎的 Unix 构建)在 9 月更新后无法加载任何 VBScript 源码里注释中包含表情符号的桌台:词法分析器使用 wcspbrk(ptr, L"\n\r") 来跳过注释,结果得到的不是行尾位置,因此报告"无效字符"。FileSystem.MoveFolder 同样通过 wcspbrk(src, L"*?><") 把任何路径中包含 >= U+0100 字符的路径当作通配符模式处理。

下游已经通过将这些调用替换为普通循环来绕过该问题:https://github.com/vpinball/libwinevbs/pull/41 (随 https://github.com/vpinball/vpinball/commit/e7837322f4 一起发布)。

受影响函数的完整反汇编以及探测源码可按需索取。

我正在使用受支持的 NDK

  • [x] 我已确认我正在使用的 NDK 当前受支持

受影响版本

r29