Open Source / ALEAPP #1834

Samsung recovery history, read in full

A Samsung phone keeps a short text log of every start of its recovery program. This contribution makes ALEAPP read every file that holds the log, show the options as printed, and keep a damaged text out of the Build column.

  • Daniel Addai, SectraTEK Lab
  • Merged
  • CC BY 4.0

View the pull request on GitHub

Summary

Samsung phones keep a short text log of every start of their recovery program, with the options it was started with. This change makes ALEAPP read all the files that hold that log, show the options exactly as printed, and stop carrying a damaged text in the Build column.

It extends the existing recovery history artifact (sRecoveryhist) instead of adding a second one, and it adds one artifact, Bug Report - Recovery History, for the single case the first cannot see: a bug report zip that is itself a file inside an extraction. Times stay as printed. Nothing is judged: a row says recovery was started with the options shown, which is an indicator and not a finding that work was done.

  • 3,055lines added, 100 removed
  • 28files changed
  • 5recovery files now read
  • 8columns appended

The issue

An examiner has a Samsung extraction and a question that comes up often: was this phone reset, and what else did recovery do? The recovery history is the natural first look. It is a plain text file with one entry for each start of the recovery program: a header with a two-letter code, a date and the firmware, then the options recovery was started with, a status line and a reboot reason.

Before this change the artifact read one path, efs/recovery/history. Three things were out of its reach. The companion file extra_history holds step lines linked to an entry by the same two-letter code, and its format_volume lines name the volumes recovery formatted. The cache folder holds three more files, last_history, last_all_history and last_extra_history, and the same names turn up as members of a bug report. And the Build column was wrong for one header shape: when a header ends in a bracketed dash, the closing bracket and the dash were returned as part of the build text. That happened on 5 of 5 rows on one tested image and 0 of 3 on the other. The output looked ordinary, so nothing in it signalled a problem.

Files read before and after the change, and the Build cellTwo columns of five recovery files. Before, only efs/recovery/history was read. After, all five are read. Below, a synthetic header line and the Build text before and after: before it kept the characters "] [-", after it does not.Before: one file readAfter: five files readefs/recovery/historyreadefs/recovery/historyreadefs/recovery/extra_historynot readefs/recovery/extra_historyread, newcache/recovery/last_historynot readcache/recovery/last_historyread, newcache/recovery/last_all_historynot readcache/recovery/last_all_historyread, newcache/recovery/last_extra_historynot readcache/recovery/last_extra_historyread, newOne header line of a history file (synthetic example)+ [AB | 2026/01/05 10:00:00 | FW1234.X1] [-]Build before: FW1234.X1] [-Build after: FW1234.X1
Show this figure as a table
Files read before and after
FileBeforeAfter
efs/recovery/historyreadread
efs/recovery/extra_historynot readread
cache/recovery/last_historynot readread
cache/recovery/last_all_historynot readread
cache/recovery/last_extra_historynot readread
Figure 1The recovery files sRecoveryhist reads, before and after, and the effect on the Build cell of a header that ends in a bracketed dash. Header and values are a synthetic example. Source: diff of scripts/artifacts/sRecoveryhist.py (paths and notes) and pull request body, "What the artifacts read" (synthetic example).

The practical effect is a quiet gap. An examiner who looked only at the main file could not see which volumes a wipe entry named, could not see options that have no column of their own (for example a data-resizing option), and had no way to know that a bug report kept on the phone carried another copy of the log. None of these is an error message. They are missing context, and missing context in a reset question is easy to read as “nothing more to see”.

The essence

The recovery history is now read from every place the tested devices keep it, and each row shows what the file says in the file’s own terms. The twelve earlier columns keep their order and form. Eight columns are appended before Source File, and the Build text no longer carries the trailing characters of a header that ends in a bracketed dash.

A row is one record of a history file. Activity names the options that are printed, for example Wipe requested (--wipe_data); it does not show that a wipe ran or finished. Times are shown as printed: Timestamp is the header date and Request Timestamp is the --requested_time value, with no other time derived and none labelled UTC.

Artifacts and columns in this change
ItemWhat it reads or addsStatus
sRecoveryhist (Wipe & Setup)efs/recovery/history with extra_history; cache/recovery/last_history, last_all_history, last_extra_historyExtended from one file to five
Bug Report - Recovery HistoryThe same names as members of a bug report zip kept inside an extraction (input type fs)New artifact
Activity, Options (as printed)Words taken from the options; every -- line of the recordAppended
Formatted VolumesThe format_volume lines of the extra file that carry the same codeAppended
Status Line, Other Lines, Entry Notes, Entry Line, Extra History FileStatus line of the record; lines in no other form; early header date note; line number of the header; the extra file usedAppended
BuildNo longer carries the trailing ] [-Fixed

Our methodology

Format discovery. The method began with the files, not with the existing parser. The history files are plain text, and the Lab read them as they are: three header shapes occur (a bracketed header, a header that closes its first bracket after the tag, and an older shape without brackets), a record may end with a lone dash or with no separator, and the extra file uses lines of the form [CODE][S] text and [CODE][E] text. The reader keeps a record’s lines under the field they belong to and keeps anything it cannot place in Other Lines, so nothing is silently dropped. Control characters are shown as a space and a line is cut at 400 characters.

One reader for two artifacts. The reader has no dependency on the rest of ALEAPP. It joins each history file to the extra file in the same folder by code, and a code in the extra file with no record in the history file gets a row of its own, so volumes are never lost for want of a match. When a bug report zip is the input of the tool, its members are already loose files and sRecoveryhist reads them; the bug report artifact logs that it found only the report text. When the zip sits inside an extraction, its members are not files of the extraction and the bug report artifact reads them. That split is why no file is listed twice.

From recovery files to report columnsThree framed groups left to right. Files read: efs and cache recovery files and a bug report zip member. Shared reader: four steps. Report columns: times as printed, activity and options, volumes and lines, notes and source.Files readShared readerReport columns addedefs/recovery/history, extra_historycache/recovery/last_history, last_all_historylast_extra_historyMember of a bug report zipkept inside an extraction(input type fs)Bug Report - Recovery History1 Split into recordsheader line, then the lines under itTimes, as printedTimestamp, Request Timestamp2 Name the activitywords taken from the options printedActivityActivity, Options (as printed)3 Note early header datesa header year before 2012Volumes and linesFormatted Volumes, Status Line4 Join the extra file by codeformat_volume lines, S and E namedNotes and linesEntry Notes, Entry Line
Figure 2Data flow of the shared reader. The same four steps serve both artifacts; only the way the file is opened differs. Source: scripts/android_recovery_history.py, sRecoveryhist.py and bugreportRecovery.py in the diff (diagram).

Synthetic fixtures. The pull request adds a fixture builder, a unit test file and ten case zips with baselines, all with invented values. They cover the shapes that real images did not supply: prompt-and-wipe records, the spaced reboot reason: form, header lines without pipes or build, a code used twice in one file, CRLF line ends, an extra file without a history file, a damaged zip, and wipe and region-change entries inside a bug report zip. The pull request lists these as constructed only; the other shapes were also run on real files.

Independent recount. On each real source the number of rows was compared with a separate count of the header lines of the file, made without ALEAPP code (Figure 3). The earlier columns were also compared field by field with the previous reading; they matched except for Build.

Rows against an independent count of header linesHorizontal bars for five sources. For each, the number of rows the tool reported and the number of header lines counted separately are equal: 5 and 5, 3 and 3, 2 and 2, 8 and 8, 38 and 38. Scale 0 to 40.Rows reported by the toolHeader lines counted separately (no ALEAPP code)010203040Samsung S-series, file system55 header linesSamsung A-series, file system33 header linesSamsung A07, bug report21 + 1 header linesSamsung A26, bug report84 + 4 header linesSamsung S-series, bug report3819 + 19 header linesFour more bug reports (Samsung A17, Huawei, Transsion-brand, Motorola): no recovery member, 0 rows.
Show this figure as a table
Rows against independent counts
SourceEvidence classRowsIndependent count
Samsung S-series, file systemfull file system zip55 header lines
Samsung A-series, file systemfull file system zip33 header lines
Samsung A07, bug reportbug report zip21 + 1 header lines
Samsung A26, bug reportbug report zip84 + 4 header lines
Samsung S-series, bug reportbug report zip3819 + 19 header lines
Figure 3Rows reported by the tool and an independent count of header lines, one pair of bars for each source. All five pairs are equal. Source: pull request body, table "Proven on real sources (counts only)". Aggregate counts only, no synthetic values.

Missing data and limits. A zip with no recovery member gives no rows and the log says so; a zip that cannot be opened is logged and skipped; a member larger than 8 MiB is read up to that size. A header date before 2012 is noted on its row, because the tested wipe records carry 1970 header dates and one update-only phone carried 2010. Header dates were not always in ascending order, so row order is the order written and not a time order. The pull request also states what was not examined: vendors other than Samsung, Android before 16 for the added columns, a cache recovery folder with files in a file system extraction (empty on both images), and the recovery logs.

Checks. The pull request’s test record lists a lint score of 10.00 of 10, ten passing case runs, 1,034 unchanged bug report case runs, and the repository’s claim-language check. The artifact notes keep to what is printed and say plainly what is not sourced, for example what the S and E marks of the extra file stand for.

The value

For an examiner the change turns one list of entries into a readable trail. The question “which volumes did this wipe entry name?” now has an answer in the same row, and the question “does this phone hold a second copy of the log?” is answered by the Source File column. Because the cache files and the efs file can hold the same entries, one record can appear up to three times, each with its own source; the notes say so, so a repeated row is not read as three events.

The dates need care, and the change is built to support that care rather than replace it. Of the nine Samsung extractions with a history in the Lab’s report on reset indicators, the header clock was plausible on four; the others showed 1970, a constant wrong year, or another value that a plausibility test can pass. The artifact therefore prints dates as written and flags an early year. If a date is wanted for an entry on an unset clock, it is an estimate anchored to a recorded time, such as the requested time of a Settings wipe, checked with a pair of anchors, and reported in the time zone of that recorded time.

A synthetic record as the report lists it, and what it supportsA card with six fields of a synthetic record, then three boxes: what a reader may say, what a reader may not say, and a note on dates. The synthetic header date reads 1970.One record in the report (synthetic example, wording shortened)Entry Tag (as stored)ABTimestamp1970-01-01 02:15:40Request Timestamp2026-03-14 09:05:12.345ActivityWipe requested (--wipe_data)Formatted Volumes/data, /cache, /metadata (start and end lines)Entry Notesheader date before 2012, shown as printedA reader may sayRecovery was started with thewipe option. This is an indicator,consistent with a reset request.A reader may not sayThat the wipe ran or finished,when it ran, or who asked.A record not listed says nothingabout earlier use.About the datesTimestamp is the header clock;here it reads 1970. A date for theentry is an estimate anchored toa recorded time, checked first.
Figure 4What the columns support. The values are invented for illustration and no device holds them. Source: column names from sRecoveryhist.py; reading rules from the artifact notes and LAB-TR-0004, Section 6.2 (synthetic example).

What the change does not do is equally part of its value. It does not tell an examiner that a wipe happened. An entry with the wipe option is an indicator that recovery was started that way, consistent with a reset request. A device that shows no entry may simply not keep the file: in the Lab’s collection the file was absent on all three units of one flagship generation, which the report treats as unknown, not as evidence of no reset.

Where this sits in the Lab's research

The pull request is the reader side of the Lab’s study of reset indicators and their clocks: Addai, D. (2026). Informative but Individually Unreliable: An Empirical Study of Android Reset Indicators and Their Clocks. Lab Technical Report LAB-TR-0004, version 1.1, Zenodo, doi:10.5281/zenodo.23720065 (all versions: 10.5281/zenodo.23717926). Read the report on this site. The report measured 14 file system extractions and 7 bug reports, sorted the recovery header clock into five behaviours, and tested one candidate rule for dating undated entries that held on one phone and failed by 526 days on another. Its comparison of readers with the earlier artifacts is where the Build column error was found.

The recovery history is also one of the traces that mark wipe events in Lab Technical Report LAB-TR-0028 (available on this site as a preprint; Zenodo deposit pending), which lists it with its own clock as a known source of error. No Lab report covers the cache copies or the bug report artifact specifically; for those the method above is from the pull request.

Sources

Every number and claim above, with where it comes from.

Sources
Number or claimSource
Pull request merged 28 October 2026; 3,055 additions, 100 deletions, 28 filesPull request metadata (gh pr view)
Files read: five recovery files; eight appended columns; twelve earlier columnsPull request body "What a row is"; DATA_HEADERS in the diff of sRecoveryhist.py
Build wrong in 5 of 5 rows on one image, 0 of 3 on the otherPull request body, "Fixed on the way"
Rows 5, 3, 2, 8, 38 with independent counts 5, 3, 1+1, 4+4, 19+19Pull request body, "Proven on real sources"
Four bug reports with no recovery member (Samsung A17, Huawei, Transsion-brand, Motorola)Same table, last row
Ten case zips; 10 passes; 1,034 unchanged bug report case runs; lint 10.00 of 10Pull request body, "Test record"
Header dates 1970 on tested wipe records, 2010 on an update-only phone; early-year note before 2012Pull request body; HEADER_NOTE_YEAR = 2012 in android_recovery_history.py
Text cap 400 characters; member cap 8 MiBMAX_TEXT and MEMBER_CAP in the diff
Same entries up to three times, one record per filesRecoveryhist.py notes in the diff
Header clock plausible on 4 of 9 extractions with a history; 526 days; 14 extractions and 7 bug reportsLAB-TR-0004, Sections 1 and 5.3 and Table 3
File absent on all three units of one flagship generationLAB-TR-0004, Section 5.1
Build text damaged in 5 of 5 rows (measured on one image)LAB-TR-0004, Table 9
Recovery history listed as a wipe-event trace with its own clockLAB-TR-0028, Table 2
Figures 1, 2 and 4 are diagrams with synthetic values; Figure 3 uses the counts aboveAuthor