Summary
On a Samsung Android 16 phone the Gallery media store, cmh.db, can keep a view in its schema after the table that the view reads has been removed. A join on that view then fails, and the ALEAPP artifact for the database returned no rows although the database held image rows. This change tests the view first and, when it cannot be queried, keeps the image rows and leaves the Address column blank.
The result on four research images is 5,523, 215, 4,582 and 1,450 rows where the artifact reported 0, 0, 0 and 3. The notes say what a blank Address means and what was observed in the data, so an examiner is not left to guess whether blank columns are a defect.
- 5,523rows on the largest image, 0 before
- 4images checked
- 216lines added, 6 removed
- 2synthetic test cases
The issue
Imagine an examiner who opens the report for a Samsung phone and finds the Gallery section empty. A phone with no photos and a parser that could not read the photos look the same: a table with no rows. The log holds one line about a missing table, and a log line is easy to miss.
That is what happened with the CMH media store on a One UI 8 database. The artifact selects image rows from the files table and joins location_view to add an address. On the database tested the view was still defined, but the location table it selects from was gone, so the query failed with “no such table: main.location” and the artifact reported nothing, for a database holding 213 image rows. The same failure was found on four images: the artifact returned 0, 0, 0 and 3 rows where the databases held 5,523, 215, 4,582 and 1,450 image rows.
cmh.py (diagram; the 213 rows are an aggregate count).The loss is large and silent. It removes every image row, with the date taken, the URI and the path, and it does so because of an optional piece of information, the address. The Lab’s study of silent parser failures calls this class “empty is not absent”: the artifact matched a file, ran, and returned a table that looked like a normal answer. An examiner working from the report alone would conclude there was nothing to examine.
The essence
The artifact now tests the view with a one-row query before it uses it. If the query works, nothing changes: the view is joined on _id and Address is the location text. If the query fails, the artifact writes a log line, leaves out the join and reports the image rows with Address blank. The notes state that a blank Address does not show the photos had no address.
The same notes record what was observed in the data so that blank columns are explained. On the 213-row database, Latitude, Longitude and Is Private held no value on any row. On the 5,523-row database, Date Added, Date Modified, Title and Bucket Name were also empty, while Date Taken, URI and Path were filled on every row. Those values are NULL in the files table; the artifact does not drop them.
| Column | Read from | When the view cannot be queried |
|---|---|---|
| Date Taken, Date Added, Date Modified, Title, Bucket Name | files | Unchanged |
| Latitude, Longitude, Is Private | files | Unchanged (empty on the tested databases) |
| Address | location_view.address_text | Blank, no join |
| URI, Path | files.uri, files._data | Unchanged |
Our methodology
How the defect was found. The Lab ran the complete artifact sets of two open-source parser suites on research images and classified every outcome, then traced each zero-row outcome back to the raw store. This artifact matched a file and returned no rows; on the Android image it was the one defect among 143 artifacts in that state, most of which are legitimately empty. The raw store told the real story: a count of image rows in the files table gave 213 on that database. The count, made with SQLite alone and no ALEAPP code, is what separated a defect from an unused feature.
The fix and its form. The change is small on purpose: 25 added and 5 removed lines in the artifact, and the rest is notes and test material. The view is tested first, the failure is logged by name, and the query that follows is built with or without the join. Nothing else in the artifact moves, so databases that keep the location table behave as before.
scripts/artifacts/cmh.py (diagram).Synthetic fixtures. Two cases with invented values were added: one with a files table, a location table and the view, and one in which the view is present and the table is dropped, as in the Samsung database. The repository’s test runner passes both. The pull request records 134 passing cases against 132 before, no failures, and 165 passes in the storage-view mode with five known failures that carry reasons.
Independent recount and second users. On each of the four images the artifact’s row count was compared with a separate count of image rows per user database (Figure 3). A Secure Folder user keeps its own cmh.db, and the artifact reads it beside the primary user’s: the two extra terms in the counts are 2, 21 and 3 rows on three images. The repository’s output checker was run on the four full reports; its first run flagged four columns as empty on all 5,523 rows, and the check against the table showed that they are NULL in the stored data, so the notes now say so.
Show this figure as a table
| Image | Before | After | Independent count |
|---|---|---|---|
| Image 1 | 0 | 5,523 | 5,523 |
| Image 2 | 0 | 215 | 213 + 2 |
| Image 3 | 0 | 4,582 | 4,561 + 21 |
| Image 4 | 3 | 1,450 | 1,447 + 3 |
Known limits, recorded. The storage-view comparison reads a byte-identical copy of a database once, so the two new cases carry a recorded known failure for their second-user leg; it is not a dropped user, because a real second user’s database differs, as the counts show. The automated workflows of the repository needed a maintainer’s approval for a first-time contributor, so the test record in the pull request is the local equivalent of each of them. Older Android versions were not re-run.
The value
For an examiner the change restores a whole class of evidence in the report: the images that the phone’s Gallery indexed, with the date taken, the URI and the path, for each Android user. The question “how many images does this device index, and where do they point?” now has an answer where it had none. The counts matter for scope: 5,523 rows on one device and 215 on another are very different review tasks.
The change also protects against a second mistake, the opposite one. A report with many empty columns can look like a broken parser. Here the empty columns are in the data, and the notes say so. That lets an examiner report that the store holds no title or bucket name for these rows without implying that the tool lost them.
Show this figure as a table
| Column | Rows with a value | Reason |
|---|---|---|
| Date Taken | 5,523 of 5,523 | filled |
| URI | 5,523 of 5,523 | filled |
| Path | 5,523 of 5,523 | filled |
| Date Added | 0 of 5,523 | NULL in the files table |
| Date Modified | 0 of 5,523 | NULL in the files table |
| Title | 0 of 5,523 | NULL in the files table |
| Bucket Name | 0 of 5,523 | NULL in the files table |
| Latitude | 0 of 5,523 | NULL in the files table |
| Longitude | 0 of 5,523 | NULL in the files table |
| Is Private | 0 of 5,523 | NULL in the files table |
| Address | 0 of 5,523 | location table is gone |
cmh.py in the diff and pull request body, second checklist item. Aggregate counts only, no synthetic values.Limits stay in view. Address is blank because the table is gone, which says nothing about whether the photos carried locations; Latitude and Longitude were empty on every row of the databases tested, which also is not evidence about the photographs. Whether other builds keep addresses in another table was not examined. Dates in the report are the values the database stores. They were not checked against the photographs themselves, and they are indicators to be read with other sources.
Where this sits in the Lab's research
This defect is a case study in the Lab’s report on silent parser failures: Addai, D. (2026). Empty Is Not Absent: Silent Failure Modes in Open-Source Mobile Forensic Parsers on iOS 26 and Android 16. Lab Technical Report LAB-TR-0006, version 1.0, Zenodo, doi:10.5281/zenodo.23717929 (all versions: 10.5281/zenodo.23717928). Read the report on this site. Its Table 6 lists the media database defect with the counts used here. The report finds that no single check catches every silent failure and that a raw count taken from the store is one that does catch this one.
Lab Technical Report LAB-TR-0037 (available on this site as a preprint; Zenodo deposit pending) injects this class of failure, an empty or dropped read, into synthetic phones and shows how often a simple count check exposes it. No Lab report covers the Address behaviour on other builds; for that the method above is from the pull request.
Sources
Every number and claim above, with where it comes from.
| Number or claim | Source |
|---|---|
| Pull request merged 25 October 2026; 216 additions, 6 deletions, 7 files, 2 synthetic cases | Pull request metadata (gh pr view) |
| 213 image rows; error text "no such table: main.location" | Pull request body, "What this changes" |
| Rows after: 5,523, 215, 4,582, 1,450; before: 0, 0, 0, 3 | Pull request body, first checklist item |
| Independent counts 5,523; 213 + 2; 4,561 + 21; 1,447 + 3 | Same item |
| Four columns empty on all 5,523 rows; Date Taken, URI and Path filled; Latitude, Longitude, Is Private empty | Notes of cmh.py in the diff; pull request body |
| 25 lines added and 5 removed in the artifact | Diff statistics of scripts/artifacts/cmh.py |
| 134 passes, 165 storage-view passes, 5 known failures; lint 10.00 of 10 | Pull request body, "Test record" |
| Workflows awaiting approval; local equivalent run | Pull request body, last row of the test record |
| Media database defect: 0, 0, 0 and 3 rows; 0 of 213 rows on the sweep image; one defect among 143 matched-no-rows artifacts on Android; the raw count separated it from empty cases | LAB-TR-0006, Table 6 and Sections 5.3 and 5.4 |
| Empty-read class measured as 1 to 5,523 stored rows reported as zero on 3 of 4 images | LAB-TR-0037, Table 7 |
| Figures 1 and 2 are diagrams; Figures 3 and 4 use the counts above | Author |