Skip to the content.

Update brain, commands, contexts, and Update.plist

Home · Documentation · Findings · Status

This page is a build-25G83 research snapshot. Static code paths and declared capabilities do not establish execution or effective runtime policy. Original raw evidence is retained privately; public tables and measurements are linked where available.

Update brain and Update.plist

Immediate input and consumers

The ramrod plugin builds the options path from the update-volume mountpoint plus Update.plist. It opens a CFReadStream and parses a property list. No runtime file with that name was present in the seven retained inventories. This is not proof of deletion: a runtime update-volume file need not remain in the supplied staging package.

A Stage 4D scan hash-verified 1,020 Intel BaseSystem executable/cache files. Exact option strings led to the plugin plus three MobileSoftwareUpdate executables:

Component Confirmed static role
softwareupdated Reads update attributes/build and prepare-snapshot; also retrieves update information
MSUTargetController path getter Appends Update.plist to preparePath
CleanupPreparePathService Obtains that path and calls unlink; success log describes cleanup without a suspended/pending update
CryptegraftService Contains the same path getter

The complete cleanup predicates are not fully traced. A deletion call establishes capability, not an event affecting the examined machine.

Official-reference writer

The 4,393,914-byte update-brain ZIP is byte-identical to the brain archive inside official SharedSupport. It contains 147 entries, including 96 regular files. Both the service and library pass the retained strict codesign checks. The library is universal x86_64/arm64e; the service is x86_64/arm64. The following control-flow analysis is x86_64 only.

Upstream option: DoNotSeal (CFBoolean)
    -> update-context skip field
    -> _store_options_for_patchd
    -> Update.plist key: skip-sealing = true, when the field is set
    -> ramrod plugin's typed skip branch

This describes linked static producer/consumer behavior.
Full call authorization and historical use remain unestablished.
Evidence Observation
Symbol 0x3a0388 → CFString 0x3b6918 → text 0x28d54b _kMSUOptionsKeySkipVolumeSealing is DoNotSeal
0x32b8a–0x32be6 Context creator type-checks CFBoolean and stores the value at +0x2d49
Writer 0x35eb0; insertion 0x362d2–0x362df Nonzero skip field inserts skip-sealing using kCFBooleanTrue
0x36070–0x3608d Context +0x4861 inserts system-volume-verify-done
Getter 0x158ce; writer 0x3668e–0x366ce Constructs Update.plist path and calls _store_dict
_store_dict 0x1d645; _store_dict_with_mode 0x3e155 Requests writeToFile:atomically: with true, then chmod 0644
Apply server 0x4e318 Calls the writer and tests its result

The write helper returns failure for invalid inputs, failed write or failed chmod; success is zero. Its caller propagates failure. Mode 0644 means owner-write and group/other-read in Unix mode bits. Historical ownership, ACLs, parent-directory security, actual resultant permissions and Foundation replacement internals were not acquired. No file-race exploit is established by this sequence.

The verification-state field is set in _verify_postbom before further operations and can also be restored from context. Complete success/failure handling must be traced before interpreting the word “done” as proof of completed verification.

Brain service sandbox

The service’s small entry point transfers to the library’s _update_brain_service_main. The library locates UpdateBrainService.sb and calls sandbox_init. Missing profile returns 72; failed initialization returns 70, before normal startup continues.

The profile allows operations by default, then restricts dynamic code generation, executable mapping and process execution. Explicit execution exceptions include sqlite3, bless, FirmwareUpdateLauncher, efiupdater, mount, umount, mount_apfs and seputil. Mapping exceptions cover extension/plugin needs; AppleConnect-related mappings are explicitly denied in the retained text.

This is policy text plus startup control flow. Imported profiles, runtime kernel enforcement and every executed subprocess remain outside the finding. None of these update tools was launched by the audit.

Command authorization and endpoints

softwareupdated: 27 commands

The table at 0x100156a10 contains 27 records; its caller supplies that count. The dispatcher calls the entitlement helper before invoking the selected handler. All entries have non-null entitlement names.

Entitlement Commands
allow-softwareupdated CreateUpdateBrainConnection, PurgeSuspendedUpdate, CalculatePrepareSize, CalculateApplySize, LoadBrain, LoadMABrain, CancelLoadBrain, AdjustLoadBrainOptions, MAAdjustLoadBrainOptions, RequiredDiskSpace, CheckPreparationSize, CheckInstallationSize, BrainIsLoadable, RetrieveLastUpdateResult, RetrievePreviousUpdateState, IsFirstBootAfterUpdate, RetrievePreviousUpdateDate, GetStashedConnectivityData, RetrievePreviousRestoreDate, PerformReportAndCleanup, GetUpdateInformation, PerformCryptegraftSemiSplat, PerformCryptegraftDownlevel
com.apple.private.mobile.softwareupdate-tools PurgeBrains
com.apple.private.softwareupdated-helpers MarkSelfDirty
com.apple.private.mobile.reboot-nerd RebootToNerd
com.apple.private.mobile.releasevalidation.tests RVTriggerNeRDUpdate

Missing, non-Boolean or false entitlement values are rejected. The helper permits a null entitlement argument, but none of these entries supplies one. Prefix comparison uses the registered command’s length; the selected record’s entitlement gate still applies. No bypass is inferred from matching semantics alone.

UpdateBrainLibrary: seven commands

Command Handler address Required entitlement
PreflightUpdate 0x42a86 allow-softwareupdated
PrepareUpdate 0x43574 allow-softwareupdated
ApplyUpdate 0x43745 allow-softwareupdated
SuspendUpdate 0x4383a allow-softwareupdated
ResumeUpdate 0x438c9 allow-softwareupdated
PingService 0x43941 com.apple.private.softwareupdated-helpers
CommitStash 0x43c34 allow-softwareupdated

The table starts at 0x3a0940, with seven 24-byte records. Gate 0x52b83 precedes handler call 0x52ba6; false goes to error handling. Its helper likewise requires a named XPC Boolean true.

The brain creates an anonymous NSXPC listener. PingService can return its endpoint under MSUBrainEndpoint (0x43e2d–0x43e44). Thus the traced endpoint-distribution command is entitlement-gated. The connection acceptance method configures interfaces, resumes and returns true on its normal path; its dispatch-once block obtains the remote proxy and sets the delegate.

The small NSXPC prepare/apply wrappers forward to a brain object without a separate entitlement check in those wrappers. Endpoint acquisition and the concrete receiver must be considered before calling this missing authorization. No unprivileged endpoint acquisition or full unauthorized update route has been demonstrated.

Brain receiver and prepare/apply guards

Stage 4G: bounded static pass complete. The local receiver and selected state guards are now recorded; complete transitive dataflow and client authorization remain unfinished.

Static NSXPC receiver

Server initialization allocates MSUBrain (0x3b8e4–0x3b8eb) and stores it at object offset +0x18. The brain getter returns that field. Objective-C metadata maps prepare/apply to 0x53fc and 0x5408 respectively.

Both original method bodies are six bytes:

55 48 89 e5 5d c3
push rbp; mov rsp, rbp; pop rbp; ret

The local category section does not name an MSUBrain category. These recorded methods simply return. Runtime replacement, other loaded images and all initialization behavior have not been excluded. Consequently the NSXPC wrappers are not a proven route to the Update.plist writer in this analysis.

Separate concrete command path

The entitlement-gated PrepareUpdate command reads ClientOptions, passes it to _handle_MSUPrepareUpdate_impl, and makes a mutable copy (or an empty dictionary when null). An ordinary branch passes the options toward _MSUPrepareUpdate_server, then _create_update_context_with_error, whose DoNotSeal lookup is traced in Stage 4E.

Key transfer points: 0x43632–0x43719, mutable-copy store 0x3ecdd, server call 0x3fbff, context-creator call 0x4c5cb, and option lookup 0x32b94. The concrete ApplyUpdate implementation likewise has an ordinary call to _MSUApplyUpdate_server at 0x40d8b; alternative branches call Splat-specific functions.

Additional traced conditions narrow that chain:

No direct overwrite or deletion of DoNotSeal was identified in the reviewed local mutation sites. Helpers receive the dictionary, so its preservation through every transitive call remains unproven. Stage 4I below traces prepare/resume handle creation; ownership/expiry, the complete restored-context lifecycle, endpoint forwarding and authorized client origin remain open. This is not an exploit, an unfiltered input route, or evidence that a particular update chose DoNotSeal.

A follow-up census of the retained recovery executable/signing subset parsed 891 entitlement plists among 2,091 saved entitlement files and found 18 x86_64/arm64e hash records declaring allow-softwareupdated=true. They represent nine component families, including installer/update assistants, softwareupdatemaccontrollerd, mobileassetd, secd, cryptegraft and its service. Twelve saved signature checks exited zero; six bundled app/XPC checks reported an obsolete resource envelope. These are potential holders of the named command entitlement, not observed senders of ClientOptions or DoNotSeal. Bundle-level verification, caller identity, endpoint access and historical Update.plist ownership/ACLs remain unresolved.

Verification-state flag and error propagation

Stage 4H: bounded static pass complete. system-volume-verify-done is serialized state, not independent proof of successful verification.

On the manifest/seal-data branch, _verify_postbom sets context byte +0x4861 at 0x74bf before _pm_verify_manifest_with_path runs at 0x75bd. The branch prepares remap.plist and remove_list.plist paths. A separate msu_use_boms NVRAM read and post.manifest availability test can select a post.bom route instead; this audit measured neither a live NVRAM value nor its write authorization.

The manifest wrapper checks open/read/seek and directory-verifier errors and, when selected, remove-list and sealer-data output failures. Its verification-operation result byte at +0x53 belongs to a different object from the serialized update-context byte +0x4861.

Both named direct callers found in the retained disassembly test the return value:

Caller False-result path Limit
_calculate_update_progress 0x35822–0x35824 branches to a zero return Progress-calculation mode is not proof of completed full verification
_prepare_snapshot 0x55b9c–0x55b9e branches to cleanup and a zero return The false branch does not reach the later prepare-snapshot creation call

The early flag is not cleared within the examined helper’s return span, but the traced callers do propagate false. That timing alone does not demonstrate a failed verification being accepted. Failed-context reuse, persistence and every caller still need lifecycle analysis.

Context loading reads system-volume-verify-done with a CFBoolean type check and restores +0x4861 (0x37600–0x37634). The ordinary load/validate wrapper can check target-group UUID and prepare-snapshot reversion before calling the context validator. A separate context-flag test (0x37de4–0x37de8, bit 0x80) branches directly to its success block. Stage 4I below finds no normal population route to that bit in the called loader. The branch must not be presented as a demonstrated bypass or a plist-selectable switch. The log text “context validated” and the validator’s name also do not prove cryptographic re-verification.

These are state-management and verification mechanisms with explicit remaining questions. No bypass or historical use has been established. Fresh symbol-aware listings for Stages 4G/4H contain 3,958 and 3,255 parsed instructions checked against original bytes; those totals are not counts of instructions fully reverse engineered.

Saved-context flags and session lifecycle

Stage 4I: bounded static pass complete. This pass separates reconstructed state, successful serialization and session authorization.

_load_context_from_path allocates and zeroes 0x4868 bytes (0x36fb4–0x36fdb). It reconstructs named fields rather than copying a stored raw flags word. The only direct assignment to the low flag byte +4 is OR 0x20 for a typed, true firmware-only option (0x37110). Three other flag writes affect byte +5, representing word masks 0x0200, 0x0800 and 0x0400. None sets low-byte bit 0x80. Review of the retained loader’s 87 base-register references and explicit field aliases found no ordinary route that populates that bit. This is a bounded code-path conclusion, not a memory-safety proof or a claim about every other context constructor.

The writer independently serializes system-volume-verify-done from byte +0x4861 (0x3697d–0x3699b) and skip-sealing from byte +0x2d49 (0x36c1c–0x36c33). Its body does not re-run the manifest verifier. The suspension server checks the writer’s return: on failure it attempts to unlink the selected output and returns false (0x4e6f8–0x4e6ff, 0x4e75d–0x4e798). Preflight state selects a separate options path. Successful ordinary suspension attempts to remove the stale alternate preflight file; that cleanup failure does not itself change the local success result.

This establishes handling of serialization failure, not a complete chain from failed verification to accepted resumed state. Whether a failed verification leaves a usable context that can reach suspension remains open. No such execution was observed.

Session step Observed static condition Remaining boundary
Initialize service Creates process-global mutable CFSet with CF object callbacks (0x41c55–0x41c63) Does not establish per-client ownership
Prepare succeeds Creates a numeric handle from the context slot, inserts it into the set, delivers it to a callback (0x3fe68–0x3fe99, 0x3ff68–0x3ff6f) Transport exposure and authorized recipient chain unfinished
Apply or suspend Rejects missing/nonmember handles before extracting a context (0x409e5–0x40a19, 0x4155f–0x4159b) Handle expiry, revocation and all aliases unfinished
Resume Requires a non-null loaded context and null error (0x4e833–0x4e84b), then registers a handle only after server success (0x41758–0x41791) Stored-file protection and full validation semantics remain separate

The numeric representation is a CFNumber created with type 0x0a from the context-pointer slot in this x86_64 implementation. Set membership and the previously traced command entitlement gates are real conditions; they do not by themselves establish the entire authorization model. This analysis does not demonstrate an unauthorized accepted handle, stale-pointer exploit or validation bypass.

Findings RAM-010 and RAM-011: expected update-state handling with unresolved lifecycle and client-authorization questions. Confidence is high for the listed local calls/branches, limited for end-to-end policy conclusions. Evidence is the official-reference brain slice, SHA-256 119378e94de84054826a58809a84cbef7c201b318af6ad289525efb83b365b21, and 4,072 byte-matching instructions across 12 selected symbols. This count overlaps earlier passes and is not an additional unique-instruction total. Follow-up: trace failed-context disposal/retry, handle ownership/expiry, client forwarding and runtime file protection. No vulnerability severity is assigned.

Prepare-failure cleanup: current lifecycle boundary

Stage 4J is in progress. The ordinary prepare handler checks the server result before creating and registering a handle. Its false branch invokes a staged-asset purge helper and requests CleanupPreparePath over synchronous XPC. On this route the request sets ShouldPurge, ShouldPurgeStagedAssets and ShouldDisableAssetStaging false, omits ShouldResetAPFSReserve, and includes TargetUUID if supplied. The receiver defaults are traced below; actual file/APFS effects remain unobserved.

Cleanup failure is logged, and the prepare-failure continuation remains false. This is evidence of failure handling; it is not proof that every failed context is destroyed or cannot later reach suspension. The receiver gate and defaults are traced below; connection authority, target provenance and complete context lifetime remain under review. Seven selected functions were byte-checked against the exact-reference brain slice; the 3,702 instruction records overlap prior stages and are not added as new unique coverage. No cleanup operation was executed.

Cleanup receiver, reserve default and failed-context disposal

RAM-012 — bounded receiver and failure-path finding. Stage 4J.1 follows the inventory-matched Intel com.apple.MobileSoftwareUpdate.CleanupPreparePathService XPC service. Its Info.plist declares a system service. All seven decoded command records require com.apple.private.softwareupdated-helpers; the dispatcher requires the copied entitlement value to have XPC Boolean type and a true value before invoking the selected handler. The helper’s null-entitlement-name shortcut does not apply to these seven non-null table entries. Endpoint distribution and the complete caller authorization chain remain separate questions.

Receiver field Absent or wrong-type default Reviewed effect
ShouldPurge false Skips the initial purge branch, not the entire cleanup function
ShouldPurgeStagedAssets false Skips the optional staged-asset purge call
ShouldDisableAssetStaging false Skips the optional staging-disable call
ShouldResetAPFSReserve true Requests resetAPFSFreeBlocksThreshold: after target selection succeeds

Valid CFBoolean values override these defaults. The ordinary brain prepare-failure request sets the first three fields false and omits the fourth. The handler requires setSystemTargetUUID: to succeed before calling the cleanup server. Consequently, the omitted fourth field reaches the reserve-reset request on this accepted-target route. The receiver does not immediately inspect that reset method’s return value; this is a requested operation, not proof that a disk’s reserve changed.

The initial ShouldPurge branch contains upgrade-boot-command cleanup, deletion of the update-volume and target-uuid NVRAM variables, unlink calls for the ordinary/preflight options and brain-locator paths, and Preboot/Data cleanup helpers. The false-purge route skips that initial branch but rejoins subsequent update-volume and prepared-update cleanup. All later deletion/retention predicates and backend effects remain under review. No cleanup command or collected executable was run.

A separate brain failure edge is now resolved: when _validate_update_context returns false, _load_context_from_path_and_validate reaches a verified libSystem/_free call with the loaded outer context and returns null (0x37ef6–0x38050). This narrows the saved-context validation failure path. It does not prove disposal of every nested resource, every prepare context, or every registered handle, nor establish an end-to-end verification bypass.

Integrity and signature qualification: the executable SHA-256 is 471b633762d06ffd180cb57cdb865e10ce8b41c8c9242679ee272dfad4318b0d; all five reacquired bundle files match the earlier inventory. Isolated executable verification fails with an Info.plist-binding error. Restoring the complete bundle changes the diagnostic, but both ordinary and strict full-bundle verification still fail with “resource envelope is obsolete (custom omit rules)”. Signature/entitlement display succeeds, which is not signature verification. This pass reports the verification failure without upgrading it to valid signing or claiming modification. Stage 4K.1 below resolves the matching code-page, special-slot and CMS-integrity layers and identifies the omission rules; full trust interpretation remains unresolved.

Coverage and assessment: seven receiver functions contain 2,349 byte-checked instruction records; seven command records and seven import stubs are independently resolved. Seven brain functions contain 3,702 checked records, with overlap from earlier passes. These are decoding counts, not complete semantic coverage. Two new bounded reviews—the executable and Info.plist—bring the current ledger to 38 inventory paths plus four embedded components. Confidence is high for these local branches and parameter defaults; no vulnerability severity is assigned. Follow-up is full cleanup path selection/retention, target and connection provenance, resource-envelope interpretation and remaining context/handle lifetime.

Prepared-update retention and cleanup results

RAM-013 — bounded cleanup-selection and result-handling finding. Stage 4J.2 traces the outer cleanup server after its purge/reserve branches. A false ShouldPurge still permits this later pass. First, configureUpdateVolumeWithReserve:andErase:withError: receives false for both reserve and erase arguments; failure returns false. The subsequent logic reads the ordinary and preflight options dictionaries.

Decision Observed local behavior
Both options describe the same UpdateUUID Requires dictionary-typed asset attributes and non-null equal UUID objects; ignores the duplicate preflight dictionary
Preflight has a string suspended-update-path with successful canonicalization and stat Selects that path for retention
No preflight path was selected Tries ordinary string update-path, then string suspended-update-path; requires successful canonicalization and stat
Ordinary path exists Presence of the suspended-update-path key selects suspended retention; otherwise a string NVRAM boot-command equal to upgrade selects pending retention
Prepared-directory walk Examines non-root preorder directories whose names begin with the 14 bytes softwareupdate; preserves an exact canonical-path match when retention is selected, otherwise requests recursive removal
A retained state exists after the walk Returns through the retained-state completion, skipping the later global cleanup
No retained state exists Requests upgrade-command/target-UUID cleanup and deletion of both options files, followed by update-volume/log/download and snapshot cleanup

The directory walk uses flags 0x15, corroborated in the local Apple SDK as FTS_COMFOLLOW | FTS_NOCHDIR | FTS_PHYSICAL. FTS struct offsets and constants were checked by compiling x86_64 static assertions without running a program. Each non-root preorder directory is skipped for further traversal after selection, so the reviewed pass does not recursively enumerate arbitrary nested directories before selecting a removal root. removefile itself receives the recursive flag. These facts do not prove race-free path handling, authenticated options, or identical behavior in every libc version. The two string-to-C-buffer calls do not immediately test conversion success; input guarantees remain open.

The later log pass considers top-level regular files with a patchd- prefix and requires the numeric parser’s remaining suffix to equal .log. It checks a relative .patchd-saved- marker. An existing marker, or a zero return from _submitRestoreLogFile, leads to an unlink request. The marker’s effective base directory and submission helper’s destination/transport are not yet traced; the symbol name is not evidence of network transmission or historical log collection.

A success reply does not certify complete cleanup. Prepared-directory removal failures can populate an error and continue. Options-file unlink failures do not force an outer false result. The later UpdateDownloads removal reports non-ENOENT errors but can continue; app-demotion cleanup failure is logged. A non-null root snapshot name triggers a request to prepare the target by reverting to that snapshot, followed by an unmount attempt even if reversion failed. Those failures are logged, but this continuation still sets the outer result true. The receiver chooses its status reply from that Boolean rather than separately rejecting a non-null error. By contrast, the earlier update-volume configuration failure and prepared-root canonicalization/open failures have explicit false exits.

These are cleanup capabilities and error-handling semantics in a privileged update helper. They do not establish unauthorized deletion, log exfiltration, rollback bypass, successful disk changes or compromise. No target operation was invoked and no vulnerability severity is assigned. The existing 2,349 receiver instruction records now support this additional bounded interpretation; 23 import-stub bindings and the SDK ABI check support attribution. Coverage stays at 38 bounded inventory paths and four embedded components. Target/connection provenance, options-file protection, indirect helper behavior and handle lifetime remain unfinished.

Cleanup connection and target selection

RAM-014 — bounded connection and target-selection finding. Stage 4J.3 resolves how the update brain requests this cleanup service and how the receiver selects a disk target. These checks concern retained Intel code; no live service, UUID, mount or log file was accessed.

The brain creates its cleanup queue once and runs connection acquisition on that queue. It first calls dlopen for the MobileSoftwareUpdate framework, then xpc_connection_create with com.apple.MobileSoftwareUpdate.CleanupPreparePathService, installs its event handler and resumes the connection. Subsequent acquisitions retain the cached connection. An event whose XPC type is error releases and clears that shared pointer, allowing later acquisition to create a new connection. The call is the named-service creation API; the reviewed instruction is not the separate Mach-service API with a privileged-lookup flag. Creation of an object is not proof that a peer was reachable or accepted the request. The receiver’s Boolean-true helper entitlement requirement from RAM-012 still applies.

The prepare handler copies its options input and initially takes the cleanup target from TargetUUID. A non-null __CleanupTargetUUID value replaces that target for the later failure-cleanup request (0x3ed73–0x3edee, 0x400bc–0x400c3). This establishes a separate cleanup-target override consumer. It does not identify every producer of that option or prove that an unauthorized client can supply it. Although the handler does not immediately branch on an early target-setter result, _MSUPrepareUpdate_server subsequently checks its own setter result for the ordinary TargetUUID and fails on false (0x4c403–0x4c407). The earlier unchecked call must not be presented as absence of all target checks.

Receiver target condition Observed behavior
Existing target’s mediaUUID matches the supplied value Returns true before the rest of target setup
Non-null UUID needs lookup Builds an NSUUID, fills a zero-initialized 16-byte buffer and rejects a null UUID result before querying media; a mounted snapshot can be replaced by its live media
UUID omitted/null Explicitly resolves /; uses the live-media-for-snapshot route when the root lookup indicates a snapshot or produces no media
Newly resolved media is not an APFS-volume object Returns false
APFS role equals 3 The local diagnostic identifies this as a data volume; uses its paired volume and fails if no pair exists
Update-container selection fails Returns false
Selected target needs further setup Stores target state and can request update-volume configuration, conditional mounting and restore-log opening; some setup failures are logged without making the final result false

The one-argument setter supplies timeout zero. Its UUID lookup helper still makes at least one media query; a miss invokes a one-second sleep before the retry counter expires. This is static control flow, not a measured delay. Internal media ordinarily supplies its own container. An external-media branch conditioned on embedded-root policy searches the primary media’s APFS physical stores for a role-zero container. Exact libpartition2 policy and the hardware conditions behind that branch remain unresolved; a selected update container should not simply be assumed identical to the target’s own container in every case.

The method is therefore a target-resolution and setup operation, not solely a passive UUID-format check. Its true result does not guarantee that an update volume was successfully configured or mounted. Conversely, the explicit root/default and snapshot/pair handling are not evidence that this audit modified the booted system. The relevant services were never run. Stored target-volume and volume-group fields are traced, but per-client ownership, shared-state lifetime, the override’s full provenance and indirect storage operations remain open. No unauthorized target selection or security bypass is established, and no vulnerability severity is assigned.

Evidence quality: seven brain functions contain 2,846 checked instruction records; seven receiver methods contain 818. Original method records agree with all seven receiver implementations. Three block pointers, 56 imported stubs, 244 indirect import references and 386 selector/CFString references are checked against the retained source/fixups; metadata RIP displacements also match original bytes. An older linear listing had one instruction-boundary mismatch at the prepare-handler prologue. This pass uses symbol-aware, byte-checked instructions and retains that discrepancy instead of using the misaligned line. Counts overlap earlier work and do not mean every selected branch or dependency is fully reviewed. Coverage remains 38 bounded inventory paths and four embedded components; the earlier bundle-signature limitation remains unresolved.

Failed preparation, context disposal and the no-op flag

RAM-015 — bounded failure-propagation and internal context-producer finding. Stage 4J.4 follows failure returns across the update-context constructor, prepare server and outer handle-registration branch. It also identifies a concrete producer of the context flag 0x80. These are static observations in the retained, exact-reference Intel update brain; no restore operation was executed.

Question Observed code and implication
What happens when a newly allocated context fails structural validation? The constructor retains the false _validate_update_context result, builds an error and eventually calls free on the outer context allocation before returning null (0x32f46–0x32fd5, with the continuation at 0x333c9). This complements the previously traced loaded-context failure. It does not establish that every nested allocation is released, and context validation must not be equated with cryptographic manifest verification.
Does snapshot-preparation failure produce a new successful update handle? _MSUPrepareUpdate_server checks _prepare_snapshot at 0x4d3a9–0x4d3b0; false reaches the failure block at 0x4db0a, preserves a false result through its common exit and returns it at 0x4d03b. The outer prepare handler then skips the successful CFNumber creation and CFSetAddValue registration (0x3fe68–0x3fe99).
Where does the 0x80 flag come from in one concrete route? _shared_noop_update_context clears a static 0x4868-byte context and writes 0x80 at offset +4 (0x32077–0x32095). The asset-staging method calls it at 0x2093c, installs a dictionary and null progress callback, and supplies the context to _report_progress in its download-wait loop at 0x214b9.
What does the handle-table search establish? Five source-byte-checked RIP-relative MOV/LEA references cover table initialization, successful prepare/resume insertion and apply/suspend membership checks. This limited instruction-encoding search does not establish that aliases, other access patterns or removal mechanisms are absent.

The snapshot-failure result extends the earlier _verify_postbom → _prepare_snapshot failure trace. The early system-volume-verify-done store remains a relevant implementation detail, but this ordinary failure route does not register a new success handle. The traced suspend handler requires handle-table membership before decoding and using a handle. These observations constrain a failure-to-suspension hypothesis; they do not prove that all prior handles, aliases, retries or alternate callers are excluded. The prepare server’s common exit also does not itself demonstrate complete context-memory reclamation.

The no-op context is an explicit internal object used by asset-staging progress code. Its existence provides a concrete explanation for one producer of 0x80; it does not show that a saved plist or unauthorized caller can set that flag. The earlier result that the ordinary saved-context loader does not populate this raw flag remains intact. No connection from this asset-staging object to an accepted verification bypass has been established. No vulnerability severity is assigned.

Evidence and limits: 13 selected functions contain 7,076 original-byte-checked instruction records. The pass checks 84 import stubs, 358 indirect import references and 669 selector/CFString references. Counts include earlier overlap and branches outside the conclusions above; they are not a count of fully understood instructions. Semantic coverage remains 38 bounded inventory paths and four embedded components. Full handle removal/expiry, client ownership, transitive resource disposal, target-override authority and ARM behavior remain open. The next bounded pass examines the cleanup bundle’s unresolved signature/resource-envelope rejection.