AOSP source releases are so late developers disassemble libraries to report bugs
Android 17's bionic libc mis-matches every wide char ≥ U+0100; AOSP sources lag a month behind Pixel builds, so the reporter disassembled libc.so.

bionic, the C library at the core of Android, picked up a reversed comparison in Android 17’s September builds: when the reject set passed to wcscspn() or wcspbrk() holds two or more characters that are all below U+0100, the functions report a false match on the first character at or above U+0100. CJK text, emoji, Cyrillic and Greek all qualify. Developer francisdb filed android/ndk issue #2260 on October 12 after reproducing the failure on a Pixel 7 and a Pixel 8a running build CP3A.260905.009, while a Nothing Phone 2 on Android 16 behaved correctly. With no public AOSP branch matching the shipped image, he pinned the bug down by disassembling the libc.so on the device.
Key points
- Once the reject set has two or more members, all below U+0100, every code point from U+0100 to U+10FFFF — 1,111,808 of them — misreports as a match.
wcsspn,wcschr,wcsstrand their siblings are unaffected; the reporter swept the entire range. - The evidence spans devices: Pixel 7 and Pixel 8a September builds (patch level 2026-09-05) fail, Nothing Phone 2 on Android 16 passes, NDK r29 is marked affected. Visual Pinball tables fail to load when a VBScript comment contains an emoji.
- Google collaborator and bionic maintainer enh-google confirmed the same day that the bug is still present at ToT, and found that the existing
wcscspnunit test asserts the wrong value. The issue remained open with no merged Gerrit fix as of October 14.
Background
The release cadence is the through-line. enh-google admitted he was about to write “you know you can just look at the source?”, then found the android-latest-release manifest branch too old to contain any of the April changes. pirama-arumuga-nainar pointed out that the android17-release git branch does carry bionic sources; zenczykowski asked the harder question: where is the source for Android 17 QPR1, the build actually running on Pixels and stable for over a month? The report itself supplies proof of the drift: in public android17-release (android-17.0.0_r1, June 2026) the two functions come from external/llvm-libc as a plain nested loop and are correct, while the code on the September builds is different.
Patience with Google’s open-source posture was already wearing thin. Earlier this month we covered Google freezing its OSS bug bounty after AI-generated invalid reports flooded triage. Two weeks later, another core-library problem reaches Google only because a volunteer took a shipped image apart.
The bug
wcscspn(s, set) returns the length of the initial segment of s holding no character from set, and wcspbrk returns a pointer to the first match; both are staples of wide-string scanning. The reporter narrowed the trigger to a table:
| Reject set | wcscspn/wcspbrk |
|---|---|
| One character | correct |
Two or more, all < U+0100 (e.g. L"x\n") | broken |
| Two or more, any ≥ U+0100 | correct |
A sweep over every code point with wcscspn(L"<cp>\n", L"x\n") came back clean for U+0001 through U+00FF and wrong for all 1,111,808 code points from U+0100 to U+10FFFF, each returning 0 instead of 1. The minimal reproducer is one array, wchar_t s[] = {'a', 0x1F680, 0x100, 'b', 'x', '\n', 0}, on which wcscspn(s, L"x\n") returns 1 on affected builds. The correct answer is 4.
| Device | Build | Result |
|---|---|---|
| Pixel 7 | CP3A.260905.009 | broken |
| Pixel 8a | CP3A.260905.009 (patch 2026-09-05) | broken |
| Nothing Phone 2 | Android 16, BQ2A.250721.001-BP2A.250605.031.A3 | correct |
Inside the shipped libc
Since the public sources do not match the image, the location work happened in a disassembler. wcscspn in /apex/com.android.runtime/lib64/bionic/libc.so picks one of three paths based on the set: a plain compare loop for a single member, a per-character wcschr() fallback when the set contains a member ≥ 0x100, and otherwise a 256-byte boolean table on the stack whose scan loop checks the table, then decides via cmp w8, #0x100 and b.lo whether to keep going. That last branch is the bug: a character ≥ 0x100 falls through to the stop-and-report-a-match exit, even though a set built entirely from values below 0x100 cannot contain it. wcsspn shares the structure, and there stopping is the correct semantics, so only the two complement functions die. wcspbrk is, per enh-google’s comment, a one-line flatten wrapper over wcscspn and inherits the failure.
Real-world fallout
Visual Pinball hit it first. libwinevbs, a Unix port of Wine’s VBScript engine, stopped loading any table whose script comments contain an emoji after the September update: the lexer locates end-of-line comments with wcspbrk(ptr, L"\n\r"), received the emoji’s position instead, and reported “Invalid character”. FileSystem.MoveFolder broke in parallel — it probes wildcards with wcspbrk(src, L"*?><"), so a path holding a single character ≥ U+0100 was treated as a wildcard pattern.
The downstream fix predates the issue by a day. On October 11 a different developer, jsm174, replaced both call sites with plain loops; the PR merged into libwinevbs as commit 7c5c2ce, and the main vpinball repo followed by bumping the dependency in commit e7837322f4. Two authors in two repos hitting the same wall independently is solid evidence the bug is real.
What others say
Google’s response was quick. enh-google confirmed “still an issue at ToT” within hours, owned the broken test (the suite asserts wcscspn(L"he\ufffelo", L"lo") equals 2 when the correct value is 3, which quietly codified the bug), and committed to adding the missing non-ASCII tests, running the suite against at least one other libc, and exploring a backport.
The thread then drifted into process. joaquinvacas argued that issues “have been flourishing” since Google tightened AOSP access, with little transparency in return; pirama-arumuga-nainar and zenczykowski disputed which branch even counts as current. On Hacker News (submitted October 14; 480 points and 234 comments as of October 15), one camp speculated the delay dodges EU Cyber Resilience Act obligations (Regulation (EU) 2024/2847); hobofan countered that Samsung’s Android 17-based One UI 9 shipped a month earlier, and bmicraft called the updated-device-withheld-source logic incoherent. refulgentis, describing himself as a former Google Android engineer, blamed “MBA-style management” and a slow drain of resources. userbinator and StilesCrisis suspected the report itself was written by Claude. All of that is forum opinion; the reproducer, the device matrix and the disassembly in the issue are checkable. No independent press coverage had surfaced as of publication.
Fix status
Nothing has landed. The issue stayed open through October 14, a Gerrit search turns up no merged wcscspn fix, and the test-plus-backport plan carries no dates. Downstream is not waiting: libwinevbs’s plain-loop substitution is a few lines, and any project can copy it.
Our take
For teams shipping native Android code, the checklist is short. Grep for wcscspn and wcspbrk, and treat every call that scans for ASCII delimiters (newlines, wildcards, path separators) as breakable by a single emoji or CJK character. The plain-loop workaround is cheap, and francisdb’s assertion EXPECT_EQ(3u, wcscspn(L"aĀb\n", L"x\n")) belongs in your CI today.
The larger lesson is procedural. The same company that pays $250k for a Chrome full-chain exploit chain (we covered XBOW’s six-day GPT-5.6 chain) left a regression in a base libc discoverable only by a volunteer disassembling shipped firmware. When public sources and shipped images drift apart, the floor for reporting a bug moves from reading source to reading machine code, and the maintainer’s own half-sentence, “you know you can just look at the source?”, is the footnote. Two dates to watch: when the fix reaches monthly images, and when QPR1 sources go public.