Er is een regressie ontdekt in de Android 17-builds van september 2026 (met name versie CP3A.260905.009) binnen de bionic-bibliotheek. De functies wcscspn() en wcspbrk() rapporteren onterecht een match zodra ze een teken tegenkomen dat groter is dan of gelijk is aan U+0100, mits de afwijzingsset twee of meer tekens bevat die allemaal kleiner zijn dan U+0100.
Technische details:
- Oorzaak: Disassemblage van
libc.so laat zien dat er een optimalisatie wordt gebruikt met een 256-byte bool-tabel. Wanneer een teken $\ge 0x100$ wordt gevonden, stopt de functie onmiddellijk en rapporteert dit als een match, in plaats van de scan voort te zetten.
- Impact: Software die wide strings scant op ASCII-scheidingstekens raakt defect zodra de tekst emoji's, CJK-tekens of andere niet-ASCII tekens bevat.
- Voorbeelden: De VBScript-engine van Wine (gebruikt in Visual Pinball on Android) en
FileSystem.MoveFolder vertoonden foutief gedrag.
- Status: Google heeft bevestigd dat het probleem aanwezig is, ook in de Tip of Tree (ToT). De bug wordt niet gedetecteerd door bestaande tests omdat deze enkel ASCII-input gebruiken.
[BUG] bionic: wcscspn() en wcspbrk() behandelen elke wchar_t >= 0x100 als een match (Android 17, CP3A.260905.009)
Beschrijving
Elke code die wide strings scant op ASCII-scheidingstekens (zoals nieuwe regels of wildcard-tekens) raakt defect zodra de tekst een emoji, CJK-tekens, Cyrillisch, Grieks of tekens uit de Latin Extended-set bevat.
Beïnvloede en niet-beïnvloede apparaten
| Apparaat | Build | Resultaat |
| Pixel 7 | google/panther/panther:17/CP3A.260905.009/16091614:user/release-keys | Defect |
| Pixel 8a | google/akita/akita:17/CP3A.260905.009/16091614:user/release-keys (patch 2026-09-05) | Defect |
| Nothing Phone 2 | Nothing/PongEEA/Pong:16/BQ2A.250721.001-BP2A.250605.031.A3 (patch 2026-08-01) | OK |
De publieke android17-release bronnen (android-17.0.0r1, juni 2026) bouwen wcscspn/wcspbrk vanuit external/llvm-libc, wat een eenvoudige geneste loop over wchart is en correct werkt. De code in de september-builds is anders, waardoor de regressie tussen de juni- en september-releases is geïntroduceerd.
Reproductie
De volgende code kan worden gecompileerd met de NDK (aarch64-linux-android30-clang), naar /data/local/tmp worden gepusht en worden uitgevoerd via 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;
}
Output op 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)
Een scan van elk codepunt van U+0001 tot U+10FFFF (met uitzondering van surrogaten en de twee set-leden) met wcscspn(L"<cp>\n", L"x\n") geeft voor 1.111.808 codepunten een 0 terug in plaats van 1: elk punt van U+0100 tot U+10FFFF. De waarden U+0001 tot U+00FF zijn correct.
Triggercondities (gemeten op Pixel 8a)
| Afwijzingsset (Reject set) | Resultaat |
| Eén teken | OK |
Twee of meer tekens, allemaal $<$ U+0100 (bijv. L"x\n", L"\xe9\n") | Defect |
| Twee of meer tekens, minstens één $\ge$ U+0100 | OK |
| Haystack beperkt tot U+0080..U+00FF | OK |
Mechanisme (gebaseerd op de geleverde libc.so)
Disassemblage van wcscspn in /apex/com.android.runtime/lib64/bionic/libc.so op CP3A.260905.009 laat drie paden zien die worden geselecteerd door de set:
- Eén teken: Een eenvoudige vergelijking-loop. Correct.
- Twee of meer tekens: Een 256-byte bool-tabel op de stack wordt gevuld vanuit de set, zolang de set-tekens in het bereik $1..0\text{xFF}$ liggen. Als een set-teken $\ge 0\text{x}100$ wordt gevonden, valt de functie terug op het aanroepen van
wcschr() voor elk teken in de haystack. Correct.
- Overig: De haystack wordt gescand tegen de tabel:
a2564: ldrb w8, [x9, x8] → table[c]
a2568: tbnz w8, #0, a2578 → in set → stop (correct)
a256c: ldr w8, [x22, #0x4]! → volgend teken
a2570: cmp w8, #0x100
a2574: b.lo a2564 → $c < 0\text{x}100 \rightarrow$ scan voortzetten
a2578: ... → $c \ge 0\text{x}100 \rightarrow$ STOP, gerapporteerd als een match
Een teken $\ge 0\text{x}100$ kan niet in een set zitten waarvan alle leden $<$ U+0100 zijn. Voor wcscspn moet de scan daarom doorgaan. De code stopt echter. wcsspn heeft dezelfde structuur, maar daar is stoppen juist het correcte antwoord (omdat het teken niet in de keep-set zit), wat verklaart waarom alleen de complementaire functies foutief zijn. wcspbrk is geïmplementeerd bovenop deze scan en erft de bug over.
De bestaande bionic-test TEST(wchar, wcspbrk) in tests/wchartest.cpp gebruikt alleen ASCII-input en detecteert dit daarom niet. Een test zoals EXPECTEQ(3u, wcscspn(L"aĀb\n", L"x\n")) zou dit wel oppikken.
Impact in de praktijk
- Visual Pinball on Android (libwinevbs): Sinds de september-update weigert deze Unix-build van de VBScript-engine van Wine tabellen te laden waarvan de broncode een commentaar met een emoji bevat. De lexer gebruikt
wcspbrk(ptr, L"\n\r") om commentaren over te slaan, maar krijgt de positie van de eerste emoji in plaats van het einde van de regel, wat resulteert in een "Invalid character" melding.
- FileSystem.MoveFolder: Behandelde elk pad met een teken $\ge$ U+0100 als een wildcard-patroon via
wcspbrk(src, L"*?><").
Deze problemen zijn downstream opgelost door de aanroepen te vervangen door eenvoudige loops (zie vpinball/libwinevbs#41).
Analyse door ontwikkelaars
Google-collaborators hebben bevestigd dat dit gedrag correct is beschreven en dat het probleem nog steeds aanwezig is in de Tip of Tree (ToT).
Het is bevestigd dat wcspbrk() direct beïnvloed wordt omdat deze functie als volgt is geïmplementeerd:
__attribute__((__flatten__))
wchar_t* wcspbrk(const wchar_t* s, const wchar_t* delims) {
size_t i = wcscspn(s, delims);
if (s[i] != L'\0') return const_cast<wchar_t*>(s) + i;
return nullptr;
}
Er is geconstateerd dat er een bestaande test is voor wcscspn(), maar dat deze onjuist is: wcscspn(L"he\ufffelo", L"lo") verwachtte 2u, maar gaf 3 terug.
[BUG] bionic: wcscspn() en wcspbrk() behandelen elke wchar_t >= 0x100 als een match (Android 17, CP3A.260905.009)
Beschrijving
Elke code die wide strings scant op ASCII-scheidingstekens (zoals nieuwe regels of wildcard-tekens) raakt defect zodra de tekst een emoji, CJK-tekens, Cyrillisch, Grieks of tekens uit de Latin Extended-set bevat.
Beïnvloede en niet-beïnvloede apparaten
| Apparaat | Build | Resultaat |
| Pixel 7 | google/panther/panther:17/CP3A.260905.009/16091614:user/release-keys | Defect |
| Pixel 8a | google/akita/akita:17/CP3A.260905.009/16091614:user/release-keys (patch 2026-09-05) | Defect |
| Nothing Phone 2 | Nothing/PongEEA/Pong:16/BQ2A.250721.001-BP2A.250605.031.A3 (patch 2026-08-01) | OK |
De publieke android17-release bronnen (android-17.0.0r1, juni 2026) bouwen wcscspn/wcspbrk vanuit external/llvm-libc, wat een eenvoudige geneste loop over wchart is en correct werkt. De code in de september-builds is anders, waardoor de regressie tussen de juni- en september-releases is geïntroduceerd.
Reproductie
De volgende code kan worden gecompileerd met de NDK (aarch64-linux-android30-clang), naar /data/local/tmp worden gepusht en worden uitgevoerd via 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;
}
Output op 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)
Een scan van elk codepunt van U+0001 tot U+10FFFF (met uitzondering van surrogaten en de twee set-leden) met wcscspn(L"<cp>\n", L"x\n") geeft voor 1.111.808 codepunten een 0 terug in plaats van 1: elk punt van U+0100 tot U+10FFFF. De waarden U+0001 tot U+00FF zijn correct.
Triggercondities (gemeten op Pixel 8a)
| Afwijzingsset (Reject set) | Resultaat |
| Eén teken | OK |
Twee of meer tekens, allemaal $<$ U+0100 (bijv. L"x\n", L"\xe9\n") | Defect |
| Twee of meer tekens, minstens één $\ge$ U+0100 | OK |
| Haystack beperkt tot U+0080..U+00FF | OK |
Mechanisme (gebaseerd op de geleverde libc.so)
Disassemblage van wcscspn in /apex/com.android.runtime/lib64/bionic/libc.so op CP3A.260905.009 laat drie paden zien die worden geselecteerd door de set:
- Eén teken: Een eenvoudige vergelijking-loop. Correct.
- Twee of meer tekens: Een 256-byte bool-tabel op de stack wordt gevuld vanuit de set, zolang de set-tekens in het bereik $1..0\text{xFF}$ liggen. Als een set-teken $\ge 0\text{x}100$ wordt gevonden, valt de functie terug op het aanroepen van
wcschr() voor elk teken in de haystack. Correct.
- Overig: De haystack wordt gescand tegen de tabel:
a2564: ldrb w8, [x9, x8] → table[c]
a2568: tbnz w8, #0, a2578 → in set → stop (correct)
a256c: ldr w8, [x22, #0x4]! → volgend teken
a2570: cmp w8, #0x100
a2574: b.lo a2564 → $c < 0\text{x}100 \rightarrow$ scan voortzetten
a2578: ... → $c \ge 0\text{x}100 \rightarrow$ STOP, gerapporteerd als een match
Een teken $\ge 0\text{x}100$ kan niet in een set zitten waarvan alle leden $<$ U+0100 zijn. Voor wcscspn moet de scan daarom doorgaan. De code stopt echter. wcsspn heeft dezelfde structuur, maar daar is stoppen juist het correcte antwoord (omdat het teken niet in de keep-set zit), wat verklaart waarom alleen de complementaire functies foutief zijn. wcspbrk is geïmplementeerd bovenop deze scan en erft de bug over.
De bestaande bionic-test TEST(wchar, wcspbrk) in tests/wchartest.cpp gebruikt alleen ASCII-input en detecteert dit daarom niet. Een test zoals EXPECTEQ(3u, wcscspn(L"aĀb\n", L"x\n")) zou dit wel oppikken.
Impact in de praktijk
- Visual Pinball on Android (libwinevbs): Sinds de september-update weigert deze Unix-build van de VBScript-engine van Wine tabellen te laden waarvan de broncode een commentaar met een emoji bevat. De lexer gebruikt
wcspbrk(ptr, L"\n\r") om commentaren over te slaan, maar krijgt de positie van de eerste emoji in plaats van het einde van de regel, wat resulteert in een "Invalid character" melding.
- FileSystem.MoveFolder: Behandelde elk pad met een teken $\ge$ U+0100 als een wildcard-patroon via
wcspbrk(src, L"*?><").
Deze problemen zijn downstream opgelost door de aanroepen te vervangen door eenvoudige loops (zie vpinball/libwinevbs#41).
Analyse door ontwikkelaars
Google-collaborators hebben bevestigd dat dit gedrag correct is beschreven en dat het probleem nog steeds aanwezig is in de Tip of Tree (ToT).
Het is bevestigd dat wcspbrk() direct beïnvloed wordt omdat deze functie als volgt is geïmplementeerd:
__attribute__((__flatten__))
wchar_t* wcspbrk(const wchar_t* s, const wchar_t* delims) {
size_t i = wcscspn(s, delims);
if (s[i] != L'\0') return const_cast<wchar_t*>(s) + i;
return nullptr;
}
Er is geconstateerd dat er een bestaande test is voor wcscspn(), maar dat deze onjuist is: wcscspn(L"he\ufffelo", L"lo") verwachtte 2u, maar gaf 3 terug.