CVE-2023-40121 AI-generated analysis
platform/frameworks/base, android.database (DatabaseUtils SQL string escaping)
Rewrites DatabaseUtils.appendEscapedSQLString to walk every UTF-16 code unit, keep only well-formed surrogate pairs, drop unpaired (lone) high or low surrogates, and escape single quotes. Before the fix it appended the raw string verbatim when the string had no single quote.
Root cause
In core/java/android/database/DatabaseUtils.java, appendEscapedSQLString only handled the single-quote character. If the input had no '\'' it appended the whole string unchanged (fast path); otherwise it only doubled quotes. It never checked surrogate code units, so strings with malformed UTF-16 (unpaired high or low surrogates) went into the quoted SQL literal as-is. The commit message states the goal is to "detect malformed UTF-16 strings". The bulletin classifies the issue as information disclosure (ID). The material does not show the downstream mechanism by which the lone surrogates led to disclosure.
How the fix works
- core/java/android/database/DatabaseUtils.java:appendEscapedSQLString: removes the indexOf('\'') fast path, so every input string is now processed character by character.
- For each char c: if Character.isHighSurrogate(c) and it is the last char, it is skipped. If the next char is a low surrogate, both are appended together and the loop continues; the next iteration re-reads the low surrogate and skips it through the isLowSurrogate branch. Otherwise the lone high surrogate is skipped.
- If Character.isLowSurrogate(c) (either a lone low surrogate or the second half of a pair already appended), the char is skipped.
- Remaining chars: a '\'' is doubled, then the char is appended. Opening and closing quotes are still added around the result.
- Net effect: the output literal contains only well-formed UTF-16 plus correctly escaped quotes. Malformed surrogates are silently removed, not rejected.
Completeness
- Only appendEscapedSQLString is changed. Callers that go through it are covered transitively: SQLiteQueryBuilder.appendWhereEscapeString in frameworks/base, plus ContactsProvider LegacyApiSupport.appendRawContactsAccount / appendGroupAccount and ContactsProvider2.appendSnippetFunction (per index search).
- Lone surrogates are dropped, not rejected. No exception or error is raised, so callers cannot tell a sanitized string from the original.
- A trailing high surrogate is dropped and a surrogate pair is kept intact. Non-surrogate code units pass through unchanged apart from quote doubling; no other characters are validated.
- SQL that callers build by direct string concatenation, without appendEscapedSQLString, is not affected by this change. The material shows no other escaping routine in DatabaseUtils being changed.
Behavior changes
- Strings containing unpaired surrogate code units are silently altered: the lone surrogates are left out of the generated SQL literal.
- The fast path that appended the string without iterating when it had no single quote is gone. Every call now iterates over all characters, with no functional difference for well-formed input.
- Well-formed strings (with or without single quotes) produce the same output as before.
Per major
- Android 14: same fix: commit c700cdc015a8 in android-security-14.0.0_r1, with matching commit 23d54f494415 in android-security-14.0.0_r2 (same Change-Id Ide65205b83063801971c5778af3154bcf3f0e530). The patched loop is present at android-security-14.0.0_r29 (7/8 lines of the patched region); the code shown there is the fixed version.
- Android 15: not affected per bulletin. The fixed appendEscapedSQLString is present at android-security-15.0.0_r17 (7/8 lines of the patched region).
- Android 16: not affected per bulletin. Fixed code present at android-security-16.0.0_r8 (7/8 lines of the patched region).
- Android 17: not affected per bulletin. Fixed code present at android-security-17.0.0_r1 (7/8 lines of the patched region).
Unknowns
- The material does not show how unpaired surrogates in the SQL literal led to information disclosure (for example, what the downstream consumer did with the malformed UTF-16).
- The POC referenced in bug 224771621 and the atest test are not included.
- The material does not show whether the Android 11, 12, 12L and 13 branches got the same cherry-pick, or whether their pre-fix code matches. Only the Android 14 commits are provided.
- How the fixed code came to be present in the Android 15 to 17 tags is not shown.