@@ -88,13 +88,14 @@ uint8_t HashSizeFor(uint8_t hash_type) {
8888
8989bool ParseCodeSignature (std::span<const uint8_t > blob, uint64_t slice_size,
9090 ParsedCodeDirectory& out, std::string& err) {
91- // Reset output up front so callers reusing a ParsedCodeDirectory across
92- // parses don't leak stale strings/spans/hashes from a prior successful
93- // call through the conditionally-overwritten fields (identifier,
94- // team_id) on the next parse, and so every early-return path leaves
95- // a clean output .
91+ // All-or-nothing semantics: accumulate every field into `tmp`, commit
92+ // to `out` only when every check has passed. `out` is reset on entry
93+ // so failure paths leave the caller's struct in a clean default state
94+ // — never a partially-populated one — regardless of where validation
95+ // bailed .
9696 out = ParsedCodeDirectory{};
9797 err.clear ();
98+ ParsedCodeDirectory tmp;
9899
99100 if (blob.size () < sizeof (CS_SuperBlob)) {
100101 err = " code signature blob too small for SuperBlob" ;
@@ -228,13 +229,14 @@ bool ParseCodeSignature(std::span<const uint8_t> blob, uint64_t slice_size,
228229 // picked CD's bytes.
229230
230231 const CS_CodeDirectory* cd = picked->cd ;
231- out.hash_type = cd->hashType ;
232- out.hash_size = HashSizeFor (cd->hashType );
233- if (out.hash_size == 0 ) {
232+ tmp.hash_type = cd->hashType ;
233+ tmp.cd_bytes = std::span<const uint8_t >(picked->blob_base , picked->blob_len );
234+ tmp.hash_size = HashSizeFor (cd->hashType );
235+ if (tmp.hash_size == 0 ) {
234236 err = " CodeDirectory unsupported hashType slipped through" ;
235237 return false ;
236238 }
237- if (cd->hashSize != out .hash_size ) {
239+ if (cd->hashSize != tmp .hash_size ) {
238240 err = " CodeDirectory hashSize mismatch" ;
239241 return false ;
240242 }
@@ -259,7 +261,7 @@ bool ParseCodeSignature(std::span<const uint8_t> blob, uint64_t slice_size,
259261 err = " CodeDirectory pageSize unsupported (must be 12..18)" ;
260262 return false ;
261263 }
262- out .page_size = 1u << cd->pageSize ;
264+ tmp .page_size = 1u << cd->pageSize ;
263265
264266 const uint32_t version = OSSwapBigToHostInt32 (cd->version );
265267
@@ -282,11 +284,11 @@ bool ParseCodeSignature(std::span<const uint8_t> blob, uint64_t slice_size,
282284
283285 const uint32_t cl32 = OSSwapBigToHostInt32 (cd->codeLimit );
284286 if (version >= CS_SUPPORTSCODELIMIT64 && cd->codeLimit64 != 0 ) {
285- out .code_limit = OSSwapBigToHostInt64 (cd->codeLimit64 );
287+ tmp .code_limit = OSSwapBigToHostInt64 (cd->codeLimit64 );
286288 } else {
287- out .code_limit = cl32;
289+ tmp .code_limit = cl32;
288290 }
289- if (out .code_limit > slice_size) {
291+ if (tmp .code_limit > slice_size) {
290292 err = " CodeDirectory codeLimit exceeds slice size" ;
291293 return false ;
292294 }
@@ -309,11 +311,11 @@ bool ParseCodeSignature(std::span<const uint8_t> blob, uint64_t slice_size,
309311 // (large codeLimit, small page_size) lets nCodeSlots match a truncated
310312 // value and PageVerifier walks past the slot table at runtime.
311313 uint64_t numerator;
312- if (os_add_overflow (out .code_limit , static_cast <uint64_t >(out .page_size ) - 1 , &numerator)) {
314+ if (os_add_overflow (tmp .code_limit , static_cast <uint64_t >(tmp .page_size ) - 1 , &numerator)) {
313315 err = " CodeDirectory codeLimit + page_size overflows" ;
314316 return false ;
315317 }
316- const uint64_t expected_pages_u64 = numerator / out .page_size ;
318+ const uint64_t expected_pages_u64 = numerator / tmp .page_size ;
317319 if (expected_pages_u64 > UINT32_MAX ) {
318320 err = " CodeDirectory page count exceeds UINT32_MAX" ;
319321 return false ;
@@ -325,7 +327,7 @@ bool ParseCodeSignature(std::span<const uint8_t> blob, uint64_t slice_size,
325327 // the xnu invariant on the full claim so a CD that overstates nCodeSlots
326328 // beyond what fits gets rejected with the same verdict xnu would give.
327329 if (hash_offset > picked->blob_len ||
328- (picked->blob_len - hash_offset) / out .hash_size < n_code_slots) {
330+ (picked->blob_len - hash_offset) / tmp .hash_size < n_code_slots) {
329331 err = " CodeDirectory nCodeSlots does not fit in blob" ;
330332 return false ;
331333 }
@@ -353,9 +355,9 @@ bool ParseCodeSignature(std::span<const uint8_t> blob, uint64_t slice_size,
353355 err = " CodeDirectory has fewer slot hashes than codeLimit/pageSize requires" ;
354356 return false ;
355357 }
356- out .page_count = expected_pages;
358+ tmp .page_count = expected_pages;
357359
358- const size_t slots_bytes = static_cast <size_t >(out .page_count ) * out .hash_size ;
360+ const size_t slots_bytes = static_cast <size_t >(tmp .page_count ) * tmp .hash_size ;
359361 // Note: hashOffset < sizeof(CS_CodeDirectory) is intentionally accepted.
360362 // xnu's cs_validate_codedirectory only checks `length < hashOffset`,
361363 // not against the CD header size:
@@ -370,13 +372,13 @@ bool ParseCodeSignature(std::span<const uint8_t> blob, uint64_t slice_size,
370372 err = " CodeDirectory slot hashes out of bounds" ;
371373 return false ;
372374 }
373- out .slot_hashes = std::span<const uint8_t >(picked->blob_base + hash_offset, slots_bytes);
375+ tmp .slot_hashes = std::span<const uint8_t >(picked->blob_base + hash_offset, slots_bytes);
374376
375377 // Compute the cdhash of the picked CD: H_picked(cd_blob[0, blob_len)),
376378 // truncated to CS_CDHASH_LEN. Matches xnu's cs_cd_hash.
377379 {
378380 uint8_t full[CC_SHA384_DIGEST_LENGTH ]; // largest supported
379- switch (out .hash_type ) {
381+ switch (tmp .hash_type ) {
380382 case CS_HASHTYPE_SHA1 : {
381383 Sha1Traits::Ctx c;
382384 Sha1Traits::Init (&c);
@@ -408,7 +410,7 @@ bool ParseCodeSignature(std::span<const uint8_t> blob, uint64_t slice_size,
408410 }
409411 static_assert (CS_CDHASH_LEN <= CC_SHA1_DIGEST_LENGTH ,
410412 " all supported hash digests must be at least CS_CDHASH_LEN" );
411- std::memcpy (out .cdhash , full, CS_CDHASH_LEN );
413+ std::memcpy (tmp .cdhash , full, CS_CDHASH_LEN );
412414 }
413415
414416 // Read the null-terminated identifier string at cd_blob_base + identOffset.
@@ -419,7 +421,7 @@ bool ParseCodeSignature(std::span<const uint8_t> blob, uint64_t slice_size,
419421 const uint8_t * p = picked->blob_base + ident_off;
420422 size_t max_len = picked->blob_len - ident_off;
421423 size_t actual_len = strnlen (reinterpret_cast <const char *>(p), max_len);
422- out .identifier .assign (reinterpret_cast <const char *>(p), actual_len);
424+ tmp .identifier .assign (reinterpret_cast <const char *>(p), actual_len);
423425 }
424426 }
425427
@@ -431,10 +433,12 @@ bool ParseCodeSignature(std::span<const uint8_t> blob, uint64_t slice_size,
431433 const uint8_t * p = picked->blob_base + team_off;
432434 size_t max_len = picked->blob_len - team_off;
433435 size_t actual_len = strnlen (reinterpret_cast <const char *>(p), max_len);
434- out .team_id .assign (reinterpret_cast <const char *>(p), actual_len);
436+ tmp .team_id .assign (reinterpret_cast <const char *>(p), actual_len);
435437 }
436438 }
437439
440+ // All checks passed. Commit the fully-populated tmp to out in one move.
441+ out = std::move (tmp);
438442 return true ;
439443}
440444
0 commit comments