wcscspn()/wcspbrk() deal with each wchar_t >= 0x100 as a match (Android 17, CP3A.260905.009) · Difficulty #2260 · android/ndk · GitHub


Description

On Android 17 builds from September 2026, wcscspn() and wcspbrk() cease on the first character of the string that’s >= U+0100 at any time when the reject set has two or extra characters which can be all < U+0100. The character is reported as a match although it isn’t within the set. wcsspn(), wcschr(), wcsrchr(), wmemchr() and wcsstr() are appropriate.

Any code that scans broad strings for ASCII delimiters, for instance newline or wildcard characters, breaks as quickly because the textual content comprises an emoji, CJK, Cyrillic, Greek or Latin Extended character.

Affected / unaffected

Device Build Result
Pixel 7 google/panther/panther:17/CP3A.260905.009/16091614:person/release-keys damaged
Pixel 8a google/akita/akita:17/CP3A.260905.009/16091614:person/release-keys (patch 2026-09-05) damaged
Nothing Phone 2 Nothing/PongEEA/Pong:16/BQ2A.250721.001-BP2A.250605.031.A3 (patch 2026-08-01) okay

The public android17-release sources (android-17.0.0_r1, June 2026) construct wcscspn/wcspbrk from exterior/llvm-libc, which is a plain nested loop over wchar_t and is appropriate. The code on the September builds is completely different (see “Mechanism”), so the regression was launched between the June and September releases.

Reproducer

Build with the NDK (aarch64-linux-android30-clang), push to /information/native/tmp, run with adb shell:

#embody 
#embody 

int principal(void)
{
    wchar_t s[] = {'a', 0x1F680, 0x100, 'b', 'x', 'n', 0};
    printf("wcscspn(s, L"xn") = %zu (anticipated 4)n", wcscspn(s, L"xn"));
    printf("wcspbrk(s, L"xn") = index %ld (anticipated 4)n", (lengthy)(wcspbrk(s, L"xn") - s));
    printf("wcsspn(s, L"aU0001F680") = %zu (anticipated 2)n", wcsspn(s, L"aU0001F680"));
    return 0;
}

Output on CP3A.260905.009:

wcscspn(s, L"xn") = 1 (anticipated 4)
wcspbrk(s, L"xn") = index 1 (anticipated 4)
wcsspn(s, L"aU0001F680") = 2 (anticipated 2)

A sweep of each code level from U+0001 to U+10FFFF (skipping surrogates and the 2 set members) with wcscspn(L"n", L"xn") returns 0 as a substitute of 1 for 1,111,808 code factors: each one from U+0100 to U+10FFFF. U+0001 to U+00FF are appropriate.

Which set shapes set off it (all measured on the Pixel 8a):

Reject set Result
one character okay
two or extra characters, all < U+0100 (e.g. L"xn", L"xe9n") damaged
two or extra characters, no less than one >= U+0100 okay
haystack restricted to U+0080..U+00FF okay

Mechanism (from the shipped libc.so)

Disassembly of wcscspn in /apex/com.android.runtime/lib64/bionic/libc.so on CP3A.260905.009 reveals three paths chosen by the set:

  1. One character: a plain evaluate loop. Correct.
  2. Two or extra characters: a 256-byte bool desk on the stack is stuffed from the set whereas the set characters are in 1..0xFF. If a set character >= 0x100 is seen, the perform falls again to calling wcschr() for every haystack character. Correct.
  3. Otherwise the haystack is scanned towards the desk:
a2564:  ldrb  w8, [x9, x8]        ; desk[c]
a2568:  tbnz  w8, #0, a2578       ; in set -> cease (appropriate)
a256c:  ldr   w8, [x22, #0x4]!    ; subsequent character
a2570:  cmp   w8, #0x100
a2574:  b.lo  a2564               ; c < 0x100 -> hold scanning
a2578:  ...                       ; c >= 0x100 -> STOP, reported as a match

A personality >= 0x100 can’t be in a set whose members are all < 0x100, so for wcscspn the scan should proceed previous it. The code stops as a substitute. wcsspn has the identical construction, and there stopping is the right reply (the character just isn’t within the hold set), which is why solely the complement capabilities are mistaken. wcspbrk is carried out on high of this scan and inherits the bug.

The current bionic take a look at TEST(wchar, wcspbrk) in assessments/wchar_test.cpp solely makes use of ASCII enter, so it doesn’t catch this. A take a look at comparable to EXPECT_EQ(3u, wcscspn(L"aĀbn", L"xn")) would.

Real-world affect

Visual Pinball on Android (libwinevbs, a Unix construct of Wine’s vbscript engine) stopped loading any desk whose VBScript supply comprises a remark with an emoji after the September replace: the lexer makes use of wcspbrk(ptr, L"nr") to skip feedback, bought the place of the primary emoji as a substitute of the road finish, and reported “Invalid character”. FileSystem.MoveFolder likewise handled each path containing a personality >= U+0100 as a wildcard sample through wcspbrk(src, L"*?><").

Worked round downstream by changing the calls with plain loops: vpinball/libwinevbs#41 (shipped in vpinball/vpinball@e7837322f4).

Full disassembly of the affected capabilities and the probe sources can be found on request.

I’m utilizing a supported NDK

Affected variations

r29



Source link