CVE-2026-28622 AI-generated analysis
platform/packages/providers/MediaProvider, MediaProvider query builder (geolocation metadata filtering)
MediaProvider now returns an empty result whenever latitude/longitude columns appear in the SQL selection, GROUP BY or HAVING clause (regardless of whether a sort order is also present), and neutralizes those columns in the sort order with a case-insensitive, word-bounded replacement instead of a case-sensitive substring replace.
Root cause
In src/com/android/providers/media/MediaProvider.java, the query-builder method (the one that ends with `return qb;` after the owner-package filtering) computed `filterClauses = getClausesForFilteringGeolocationData(extras, type)` and then used an if/else. If the list contained QUERY_ARG_SQL_SORT_ORDER, the code only rewrote the sort string. Otherwise it appended `FALSE` to the WHERE clause. Because the sort-order branch took priority, a query that put latitude/longitude in the selection, GROUP BY or HAVING clause and also in the sort order was never blocked with `FALSE`, so the restricted columns could still be used to filter rows. The sort-order rewrite also used case-sensitive `String.contains`/`String.replace` with the LATITUDE/LONGITUDE constants, so other letter-case spellings (for example "LATITUDE", which the new tests exercise) would not be replaced with NULL.
How the fix works
- src/com/android/providers/media/MediaProvider.java: the inline block is moved into a new private method, filterGeolocationClauses(SQLiteQueryBuilder qb, Bundle extras, int type), which is called just before `return qb;` in the query-builder method.
- filterGeolocationClauses: returns early when `!indexMediaLatitudeLongitude() || isCallingPackageSelf()` or when getClausesForFilteringGeolocationData(extras, type) returns an empty list. This is the same gating as before.
- filterGeolocationClauses: the priority is inverted. If filterClauses contains QUERY_ARG_SQL_SELECTION, QUERY_ARG_SQL_HAVING or QUERY_ARG_SQL_GROUP_BY, `appendWhereStandalone(qb, "FALSE")` is applied, so the query returns no rows even when a sort order is also present. The sort string is rewritten only in the `else if` branch (sort order alone).
- filterGeolocationClauses: the sort-order rewrite now uses `sortArgs.replaceAll("(?i)\\b" + LATITUDE + "\\b", "NULL")` and the same for LONGITUDE. This makes the replacement case-insensitive and limits it to whole words. The result is then stored back into extras under QUERY_ARG_SQL_SORT_ORDER.
- tests/src/com/android/providers/media/MediaProviderTest.java: testQueryingMediaGeolocationDataInSelectionShouldReturnEmptyCursor, testQueryingMediaGeolocationDataInOrderByShouldReturnNonEmptyCursor and testQueryingMediaGeolocationDataInGroupByAndHavingShouldReturnEmptyCursor are extended to loop over lowercase (constant) and uppercase column names, including combined LATITUDE OR LONGITUDE selections.
Completeness
- Covers the combination case: any geolocation reference detected in selection, HAVING or GROUP BY now forces an empty result regardless of the sort order, which closes the branch-ordering gap.
- Covers letter-case variants in the sort-order rewrite through the (?i) regex. The word boundary also prevents replacing part of a longer token.
- Detecting which clauses reference the columns still depends entirely on getClausesForFilteringGeolocationData(extras, type), whose body is not in the diff. The material does not show whether its matching is case-insensitive or handles qualified/quoted column names.
- The projection path (geolocation columns returned as NULL) is covered by the existing testQueryingMediaGeolocationDataInProjectionShouldReturnNull but is not changed by this commit.
- The whole filter is still gated on the indexMediaLatitudeLongitude() flag and skipped when isCallingPackageSelf() is true.
Behavior changes
- Non-self queries that reference latitude/longitude in selection, GROUP BY or HAVING now return an empty cursor even when they also supply a sort order. Previously the sort-order branch took priority and FALSE was not appended.
- Sort orders containing latitude/longitude in any letter case, as whole words, are rewritten to NULL, so results are not sorted by those columns. Previously only exact-case substrings matching the constants were replaced.
- No new exceptions or error codes. The logic now lives in a new private method, filterGeolocationClauses.
Per major
- Android 14: Not affected per the bulletin. At android-security-14.0.0_r29 only 1/8 lines of the patched region are present. The material does not show what code exists there instead.
- Android 15: Not affected per the bulletin. At android-security-15.0.0_r17 only 1/8 lines of the patched region are present. The material does not show what code exists there instead.
- Android 16: Not affected per the bulletin. At android-security-16.0.0_r8, 8/8 lines of the patched region are present, which suggests the fixed form of the code is already in that tag. The material does not show how it got there.
- Android 17: Resolved by this commit (43a7df8794a8, RESTRICT AUTOMERGE, cherry-picked from two internal changes) in android-security-17.0.0_r1. The code context at that tag shows filterGeolocationClauses in place, although the resolver reports only 3/8 patched lines matching.
Unknowns
- The implementation of getClausesForFilteringGeolocationData (how it detects column references and whether it is case-insensitive) is not in the material, although the commit message claims case-insensitive matching.
- Whether uppercase sort-order spellings were detected before the fix, and therefore whether results were actually sorted by geolocation values.
- Why android-security-16.0.0_r8 already contains the full patched region while 16 is not listed by the bulletin.
- What code exists in the patched region on 14 and 15 (only 1/8 lines match).
- Why the resolver reports 3/8 matching lines at android-security-17.0.0_r1 even though the context shows the fixed method present.
- The name of the enclosing query-builder method, which the diff hunk header does not show.