Open Source / iLEAPP #2518

Permission cache and location state dumps

Processes write snapshots of their own state into the unified log. This contribution reads two of them, the permission cache and the location manager state, and explains why they show a state at a moment, not an event and not who asked.

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

View the pull request on GitHub

Summary

The pull request adds four artifacts to iLEAPP's logarchive module. Two read statedumps titled “TCC Authorization Cache”, which show what a process had cached about privacy permissions at the moment it wrote the dump. Two read statedumps titled “CLLocationManagerStateTracker”, which show the flags and settings of a location manager. Each has a row-per-dump view and a collapsed summary.

Values are copied as logged and no meaning is assigned. The dumps do not name the application they describe. The only attribution is the process that wrote them, and a row is a lead, not proof of consent or of use.

  • 4artifacts added
  • 409lines of parser code
  • 542 and 278TCC rows on two research images
  • 2,174 and 514location state rows on the same two

Methodology note

Drafted with AI assistance and reviewed by the author before publication.

The issue

An examiner is asked which apps may use the address book or location, and whether anything held a live location manager in the background. Two places can help. The permission database TCC.db names the requesting app and gives a last modified time. The unified log holds statedumps: records in which a process writes out a block of its own state. Until this contribution iLEAPP did not read the statedumps of the permission cache or of the location manager.

They are awkward to read for two reasons. First, a statedump record has no subsystem and no category, and the table iLEAPP builds does not keep the record's event type. Every marker that selects by subsystem finds nothing, so a statedump has to be found by the first line of its message (Figure 1). Second, they repeat. Some processes wrote up to 252 TCC dumps and up to 1,764 location dumps on one image, most of them identical to an earlier one. Printed one row per dump, the report holds thousands of rows that describe a few states.

A statedump has no subsystem or categoryA synthetic statedump record with an empty subsystem and category and a first message line that reads title: TCC Authorization Cache. A filter on subsystem finds nothing; a filter on the first line of the message collects the dump.One statedump record (synthetic example)Timestamp2026-03-14 10:00:07Process/usr/libexec/faketccdSubsystem(empty)Category(empty)Message, line 1title: TCC Authorization CacheMessage, line 2Object Type:Filter on subsystem or categoryThese fields are empty on a statedumpResult: nothing is collectedFilter on the first line of the messagetitle: TCC Authorization Cache%Result: the dump is collectedInvented process, time and layout. The real dumps have the same empty fields.
Show this figure as a table
Fields of the synthetic record
FieldValue
Timestamp2026-03-14 10:00:07
Process/usr/libexec/faketccd
Subsystem(empty)
Category(empty)
Message, line 1title: TCC Authorization Cache
Message, line 2Object Type:
Figure 1Why these records need their own marker: the usual handles, subsystem and category, are empty. Source: notes and collection query in the pull request diff; record values are a synthetic example.

The essence

Each dump kind has two artifacts. One gives a row for each cached entry of each dump, or for each dump. The other collapses identical content and gives the first and last dump that held it and the number of dumps. Both state that they show content, not change: two rows for one process with different content mean that dumps held different content, not that a permission changed.

The four artifacts and their row counts on two research images
ArtifactOne row isRows, iOS 26.5.2Rows, iOS 26.6
logarchive_tcc_cacheOne cached entry in one dump542278
logarchive_tcc_cache_summaryOne distinct entry per process, with first and last dump10399
logarchive_location_stateOne dump, with 16 logged keys as columns2,174514
logarchive_location_state_summaryOne distinct content per process and PID119156

The TCC rows give the service, two object addresses, the auth right, reason and version as logged, and the writing process. The location rows give keys such as the updating-location flag, the background-updates flag and the desired accuracy, with the remaining keys in an Other Fields column.

Our methodology

Format discovery. Statedump messages are a title line, two object-type lines and a body. The TCC body is a JSON object whose descriptions hold object addresses and no bundle identifier or process name. The location body is not valid JSON, because the last value ends in a comma, so it is read line by line. The two title lines are the only handle, and the patterns are anchored at the start of the message so that a title quoted inside another message does not match.

Collection and filter. Both markers are added to the collection step as well as to the artifacts' filters, since a dependent artifact only sees what the collection step kept. A test fails if a marker an artifact reads is missing from the collection step.

Synthetic fixture and tests. A synthetic log show export with invented process paths, PIDs and addresses covers each fallback, with decoys. Its generator marks every invented shape. Twenty-eight tests in four groups run the real two-stage chain, check the markers, test the parsers, and check that the fixture is reproducible. A deeply nested body is treated as unreadable, not as an error, and a key inside another word is not read.

Behavior on missing or odd data. An empty dump is collected but gives no row. A dump whose body cannot be read gives one row with the body as logged and every other field blank. Keys that a later release adds go to Other Fields.

Validation on real images. On two corpus sysdiagnoses, no body failed to read and every authorization record matched the expected shape. The image with iOS 26.5.2 gave 518 collected TCC dumps, 481 with entries and 542 rows. The image with iOS 26.6 gave 331 dumps, 254 with entries and 278 rows (Figure 3). Auth Right read Allowed, Limited, Denied or Unknown, and Reason read Entitled, User Consent, Service Policy or None. The artifact does not say what those words or a Version of 1 or 2 denote.

How the two dump bodies are readTwo lanes. A TCC cache dump body is a JSON object whose entries are read by regular expressions into one row per entry, with fallbacks for empty and unreadable dumps. A location manager dump body is read line by line into sixteen columns plus an Other Fields column.TCC authorization cache dumpDump bodyJSON object: service,credential, record textRecord textregex reads right, reason,version and any flagsOne row per entryempty dump: no rowunreadable: one row, text keptLocation manager dumpDump bodylines of key and valuenot valid JSONRead line by line16 known keys havea column eachOther Fieldsunknown keys and odd lineskept, in the order loggedValues are copied as logged. Nothing is translated into a meaning.
Show this figure as a table
Readers
DumpReaderFallback
TCC cacheJSON object, regex on record textEmpty: no row. Unreadable: one row with the text
Location stateLine by line, 16 known keysOther keys and odd lines go to Other Fields
Figure 2Two body shapes, two readers, one rule: copy what was logged and keep what you cannot place. Source: functions _tcc_cache_entries and _location_state_fields in the pull request diff. Diagram.
Rows before and after the summaryTwo bar charts. TCC cache: 518 dumps and 542 rows collapse to 103 summary rows on the iOS 26.5.2 image, and 331 dumps and 278 rows to 99 on iOS 26.6. Location state: 2,174 rows collapse to 119 summary rows on 26.5.2, and 514 to 156 on 26.6.TCC authorization cache (scale 0 to 600)010020030040050060026.5.2: dumps collected51826.5.2: rows54226.5.2: summary rows10326.6: dumps collected33126.6: rows27826.6: summary rows99Location manager state (scale 0 to 2,500)05001,0001,5002,0002,50026.5.2: dumps = rows2,17426.5.2: summary rows11926.6: dumps = rows51426.6: summary rows156
Show this figure as a table
Rows per artifact
ImageTCC dumps collectedTCC rowsTCC summary rowsLocation rowsLocation summary rows
iOS 26.5.25185421032,174119
iOS 26.633127899514156
Figure 3Repetition is the bulk of the data. The summary artifacts show each distinct content once, with first and last dump and a count; they are a labelled view, not a change log. Source: sample_data and notes of the four artifacts in the pull request diff and body. Real counts; no synthetic values.

The value

Who benefits. Examiners who need to say which process held a cached permission verdict or a live location manager, and researchers who want to know what the permission cache looks like across builds.

Which question can now be answered. “Which process wrote a TCC cache dump with an entitled address-book entry, and when?” and “Which processes wrote a location dump with updating-location true?” On the two images most TCC rows were entitled address-book entries (381 of 542 and 230 of 278), mostly written by processes outside app bundles (341 and 195). Updating Location read true on 62 and 42 location rows. Twelve of the 62 were written by processes in app bundles on the first image, and none of the 42 on the second.

What it changes in a case. The dumps add the writing process and a dump time to what TCC.db gives, which is the requesting app and a last modified time (Figure 4). An examiner can use a dump as a lead for a process to look at next. A row with updating-location true shows that state at the dump time. It does not show that a position was obtained, stored or used.

Limits. Only processes that wrote a dump inside the log's window appear, so an empty result is not evidence that no permission was cached. A dump says nothing about when a permission was granted. How a dump entry relates to a TCC.db row is not established.

Two sources for the address-book permissionBar chart on one scale from 0 to 50. On iOS 26.5.2 the dumps held entitled address-book entries for 42 distinct process paths while TCC.db held address-book rows for 7 apps. On iOS 26.6 the numbers were 38 and 7.Entitled address-book entries: who is named0102030405026.5.2: process paths in the dumps4226.5.2: apps with a TCC.db row726.6: process paths in the dumps3826.6: apps with a TCC.db row7Dumps name the writing process and the dump time; TCC.db names the requesting app.
Show this figure as a table
Process paths and apps
ImageProcess paths in dumpsApps with a TCC.db row
iOS 26.5.2427
iOS 26.6387
Figure 4The two sources answer different questions. How the entries of one relate to the entries of the other is not established. Source: notes of logarchive_tcc_cache in the pull request diff. Real counts; no synthetic values.

Where this sits in the Lab's research

This contribution supports Lab Technical Report LAB-TR-0012, “What the iOS Unified Log Says About Device Security Controls: Most Leave a Trace, Few Record a Change” (preprint, not peer reviewed; available on this site, Zenodo deposit pending, so there is no DOI yet). The report lists statedumps selected by their title as one of its marker rules (Section 3.4) and gives the results for both dump kinds in Table 6, including the larger counts in its iOS 27.0 log. It explains why the request lines alone do not answer the question: the requester of a permission request is withheld, and the statedumps add the cached verdict and reason per writing process without naming the application. That is the report's central finding in a small case: the control leaves a trace, and the trace shows a last known state, not a change.

The white paper LAB-TR-0017 (preprint on this site; Zenodo deposit pending) maps where settings live on disk and does not cover the unified log, so it does not cover this contribution. The method above is from the pull request and LAB-TR-0012.

Sources

Every number in this post is listed here with where it comes from. Figures use these numbers or invented example values, marked as such.

Sources for every number and claim in this post
Number or claimWhere it comes from
4 artifacts; 409 lines added to logarchive.py; merged 20 October 2026Pull request metadata and diff
518 and 331 TCC dumps collected; 481 and 254 with entries; 542 and 278 rowsnotes of logarchive_tcc_cache in the diff; pull request body
Summary rows: 103 and 99 (TCC), 119 and 156 (location)sample_data of the two summary artifacts in the diff
2,174 and 514 location dumps and rows; 55 and 44 distinct process pathsnotes and sample_data of logarchive_location_state in the diff
Up to 252 TCC dumps and up to 1,764 location dumps from one process, iOS 26.5.2 imagenotes of the two dump artifacts in the diff
381 of 542 and 230 of 278 rows are entitled address-book entries; 341 and 195 by processes outside app bundlesnotes of logarchive_tcc_cache in the diff
Updating Location true on 62 and 42 rows; 12 and 0 by processes in app bundlesnotes of logarchive_location_state in the diff
42 and 38 process paths in the dumps; TCC.db address-book rows for 7 apps on eachnotes of logarchive_tcc_cache in the diff
28 tests in four groupsCount of test functions in test_logarchive_statedumps.py in the diff
Statedumps selected by title; Table 6 results; requester withheld in the request linesLAB-TR-0012, Section 3.4, Table 5 and Table 6