From ac7eb66879d6aaa1e57302d5883cba53623774bf Mon Sep 17 00:00:00 2001 From: Kazuki Nakashima <65545348+lynnswap@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:02:40 +0900 Subject: [PATCH 1/6] chore: track issue 80 remediation --- Docs/issue-remediation-progress.md | 47 ++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 Docs/issue-remediation-progress.md diff --git a/Docs/issue-remediation-progress.md b/Docs/issue-remediation-progress.md new file mode 100644 index 0000000..3fa1921 --- /dev/null +++ b/Docs/issue-remediation-progress.md @@ -0,0 +1,47 @@ +# Issue remediation progress + +Base: `main` at `0d109cda22193020914a70069c3e0b5c07c31e8b` + +## Delivery order + +1. #80 bounded MachOKit chained-fixup reads +2. #81 actionable bounded raw-helper crash diagnostics +3. #83 bounded Objective-C table and loaded-image reads + +Each issue is delivered as an independent Ready PR targeting `main`. A later +issue starts only after the earlier PR is review-clean and merged. + +## Current issue: #80 + +Branch: `codex/issue-80-bounded-chained-fixups` + +Dependency base: + +- MachOKit: `fec9503cdf3d595ef8cf4abac1602e299a8c3be4` (`0.52.101`) + +Verified evidence: + +- PosterBoardUI, PrivateSearchProtocols, and UserManagementUI terminate with + `SIGSEGV` while building or walking file-backed chained-fixup tables. +- The common owner is `MachOFile.DyldChainedFixups.pages(of:)` and + `pointers(of:in:)`, before the Objective-C/Swift metadata-specific caller. +- Current code forms metadata-sized unsafe buffers and contains an + unconditional file-slice acquisition. +- Per-target process isolation contains the crash, but all three targets have + zero artifacts and are absent from the committed generation. + +Design gate pending: + +- Map every external offset/count boundary in starts-in-image, + starts-in-segment, page-start, multi-start, and chain walking. +- Define one checked table/range owner and typed failure semantics without + constructing an invalid pointer or indexing before validation. +- Preserve compatibility for existing public query APIs while making raw + helper traversal fail normally instead of signaling. + +Required runtime gate: + +- Re-run PosterBoardUI, PrivateSearchProtocols, and UserManagementUI on iOS + 27.0 build `24A5390f` with zero helper signals. +- Preserve readable metadata/artifacts, or produce a normal bounded target + failure when a target cannot be decoded. From 5f5156582518c4bd755e455fe096b371de400555 Mon Sep 17 00:00:00 2001 From: Kazuki Nakashima <65545348+lynnswap@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:20:12 +0900 Subject: [PATCH 2/6] docs: approve issue 80 design gate --- Docs/issue-remediation-progress.md | 63 ++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/Docs/issue-remediation-progress.md b/Docs/issue-remediation-progress.md index 3fa1921..f9a5ad2 100644 --- a/Docs/issue-remediation-progress.md +++ b/Docs/issue-remediation-progress.md @@ -30,14 +30,61 @@ Verified evidence: - Per-target process isolation contains the crash, but all three targets have zero artifacts and are absent from the committed generation. -Design gate pending: - -- Map every external offset/count boundary in starts-in-image, - starts-in-segment, page-start, multi-start, and chain walking. -- Define one checked table/range owner and typed failure semantics without - constructing an invalid pointer or indexing before validation. -- Preserve compatibility for existing public query APIs while making raw - helper traversal fail normally instead of signaling. +Confirmed root cause: + +- All three binaries contain a valid sparse `starts_in_image` table with + `seg_info_offset = [0, 0x10, 0]`. Apple dyld treats a zero entry as the + normal absence of fixups for that segment. +- MachOKit instead reads each zero entry as a `starts_in_segment` record at + the start of `starts_in_image`. That aliases the real record's page size as + a bogus `page_count` of `0x4000`, and `pages(of:)` walks past the 64 KiB + mapping before any page-size guard runs. +- `fixupPointersCache` exposed the older parser defect by eagerly traversing + every returned segment. The metadata readers are downstream observers, not + the owner of this failure. + +Design gate approved: + +- A shared internal bounded byte reader owns all fixup-blob ranges for both + file-backed and loaded-image table parsing. It performs exact integer + conversion, checked arithmetic, unaligned scalar loads, bounded arrays, and + bounded NUL-terminated strings before forming a pointer or collection. +- The starts-table parser treats a zero segment offset as normal absence, + preserves the original Mach-O segment index for nonzero records, and + validates each record's declared size, page prefix, and complete flexible + start-entry storage. +- The file chain walker maps a parsed record through its Mach-O segment index; + segment file offset/size owns disk reads. Page starts, multi-start indices + and termination, pointer width, and every `next * stride` advance must stay + inside the current page and file-backed segment. +- Existing public nonthrowing APIs retain their signatures and project checked + results as `nil` or empty collections. Internal typed failures retain the + distinction between absence and invalid input; an additive support SPI may + expose preflight validation without adding a protocol requirement. +- A malformed segment/page/chain cannot discard validated siblings in the + compatibility projection. Imports remain all-or-nothing because a partial + table would shift ordinal identity. +- `pointers(of:in:)` and `pointer(for:in:)` share one checked walker. The + unconditional file-slice/read traps in that path and optional rebase + resolution are removed. + +Deterministic validation gate: + +- Exact sparse-table regression `[0, 0x10, 0]`, retaining segment index 1 and + its valid pointer chain. +- Truncated header/segment-offset/page tables and out-of-range declared sizes. +- Valid and invalid multi-start tables, including bad indices and missing + `START_LAST`. +- Segment slice overflow, chain starts outside a page, pointer-width crossing, + and `next` crossing a page. +- File/image parity for checked table parsing and compatibility projections. +- Existing public API clients compile without source changes. + +Dependency delivery gate pending: + +- Publish the MachOKit fix from the exact pinned base to the `lynnswap` fork. +- Prove one coherent SwiftPM graph uses that revision for all direct and + transitive MachOKit requirements before updating PrivateHeaderKit pins. Required runtime gate: From 72c067cbf0c34905fabbb83628f99ced3cdfda43 Mon Sep 17 00:00:00 2001 From: Kazuki Nakashima <65545348+lynnswap@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:33:34 +0900 Subject: [PATCH 3/6] docs: record issue 80 dependency contract --- Docs/issue-remediation-progress.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Docs/issue-remediation-progress.md b/Docs/issue-remediation-progress.md index f9a5ad2..93d5750 100644 --- a/Docs/issue-remediation-progress.md +++ b/Docs/issue-remediation-progress.md @@ -80,11 +80,20 @@ Deterministic validation gate: - File/image parity for checked table parsing and compatibility projections. - Existing public API clients compile without source changes. -Dependency delivery gate pending: +Dependency delivery gate approved: - Publish the MachOKit fix from the exact pinned base to the `lynnswap` fork. -- Prove one coherent SwiftPM graph uses that revision for all direct and - transitive MachOKit requirements before updating PrivateHeaderKit pins. +- Keep the direct dependency's original MxIris URL but change its requirement + to the fork commit's exact revision. A tracked repo-local SwiftPM mirror maps + both MxIris URL spellings (with and without `.git`) to the `lynnswap` fork. +- SwiftPM 6.3.3 probes confirmed that the revision requirement unifies the + existing MachOKitExtensions, MachOObjCSection, MachOSwiftSection, and + swift-demangling ranges to one checkout without an identity-conflict warning. +- `Package.resolved` retains the original URL and an unversioned exact revision; + the existing #79 Objective-C/Swift reader cohort remains unchanged. +- The mirror is intentionally a root-package build contract. PrivateHeaderKit + is an executable package; supporting it as a transitive library dependency + is outside this issue's distribution scope. Required runtime gate: From 0d97378bdba0803d9f860a4c8ba4d43e57708eb6 Mon Sep 17 00:00:00 2001 From: Kazuki Nakashima <65545348+lynnswap@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:08:27 +0900 Subject: [PATCH 4/6] fix: preflight chained fixup metadata --- .gitignore | 4 +- .swiftpm/configuration/mirrors.json | 13 +++++++ Package.resolved | 5 +-- Package.swift | 2 +- .../PrivateHeaderKitRawDumpMain.swift | 8 +++- .../PrivateHeaderKitHelperProtocolTests.swift | 37 ++++++++++++++++++- 6 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 .swiftpm/configuration/mirrors.json diff --git a/.gitignore b/.gitignore index 6f23421..dab57e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .DS_Store /.vscode/ -/.swiftpm/ +/.swiftpm/* +!/.swiftpm/configuration/ +!/.swiftpm/configuration/mirrors.json /.swiftpm/xcode/xcuserdata/ /.swiftpm/xcode/package.xcworkspace/xcuserdata/ /.build diff --git a/.swiftpm/configuration/mirrors.json b/.swiftpm/configuration/mirrors.json new file mode 100644 index 0000000..6024e8b --- /dev/null +++ b/.swiftpm/configuration/mirrors.json @@ -0,0 +1,13 @@ +{ + "object" : [ + { + "mirror" : "https://github.com/lynnswap/MachOKit.git", + "original" : "https://github.com/MxIris-Reverse-Engineering/MachOKit" + }, + { + "mirror" : "https://github.com/lynnswap/MachOKit.git", + "original" : "https://github.com/MxIris-Reverse-Engineering/MachOKit.git" + } + ], + "version" : 1 +} diff --git a/Package.resolved b/Package.resolved index 224af7d..cc80741 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "8ae34573614918b10b079595077189c359f4bf848247a6103a86e9de2b453a7c", + "originHash" : "afe6393989064d60f345d3b63e7668e4541bfdcee8911add15f52d9c8bb39673", "pins" : [ { "identity" : "associatedobject", @@ -42,8 +42,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/MxIris-Reverse-Engineering/MachOKit.git", "state" : { - "revision" : "fec9503cdf3d595ef8cf4abac1602e299a8c3be4", - "version" : "0.52.101" + "revision" : "e0e0b30187ae74f2088d932845c8ddac2c79f36c" } }, { diff --git a/Package.swift b/Package.swift index 4359553..ffcd26d 100644 --- a/Package.swift +++ b/Package.swift @@ -33,7 +33,7 @@ let package = Package( ), .package( url: "https://github.com/MxIris-Reverse-Engineering/MachOKit.git", - from: "0.51.101" + revision: "e0e0b30187ae74f2088d932845c8ddac2c79f36c" ), .package( url: "https://github.com/lynnswap/MachOObjCSection.git", diff --git a/Sources/PrivateHeaderKitRawDumpCore/PrivateHeaderKitRawDumpMain.swift b/Sources/PrivateHeaderKitRawDumpCore/PrivateHeaderKitRawDumpMain.swift index 42852cf..62bc999 100644 --- a/Sources/PrivateHeaderKitRawDumpCore/PrivateHeaderKitRawDumpMain.swift +++ b/Sources/PrivateHeaderKitRawDumpCore/PrivateHeaderKitRawDumpMain.swift @@ -1,6 +1,6 @@ import Foundation import Dispatch -import MachOKit +@_spi(Support) import MachOKit @_spi(Diagnostics) import MachOObjCSection import MachOSwiftSection import ObjCDump @@ -442,6 +442,7 @@ private func dumpImage( return } profileLogDuration(enabled: options.profile, imagePath: imagePath, name: "loadMachO", since: loadStart) + try machO.validateFileBackedChainedFixups() if options.verbose { print("Dumping: \(placement.identity.url.path)") @@ -486,6 +487,11 @@ private func defaultSwiftInterfaceBuilderFactory( enum RawMachO { case file(MachOFile) case loaded(MachOImage) + + func validateFileBackedChainedFixups() throws { + guard case .file(let file) = self else { return } + try file.validateChainedFixups() + } } enum RawMachOLoadError: Error, Equatable, CustomStringConvertible, Sendable { diff --git a/Tests/PrivateHeaderKitHelperProtocolTests/PrivateHeaderKitHelperProtocolTests.swift b/Tests/PrivateHeaderKitHelperProtocolTests/PrivateHeaderKitHelperProtocolTests.swift index 1a40f73..46fb84c 100644 --- a/Tests/PrivateHeaderKitHelperProtocolTests/PrivateHeaderKitHelperProtocolTests.swift +++ b/Tests/PrivateHeaderKitHelperProtocolTests/PrivateHeaderKitHelperProtocolTests.swift @@ -142,7 +142,7 @@ struct PrivateHeaderKitHelperProtocolTests { #expect(report.omittedDiagnosticCount == UInt.max) } - @Test func resolvedGraphPinsObjectiveCReaderForkExactly() throws { + @Test func resolvedGraphPinsReaderForksExactly() throws { let packageRoot = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() .deletingLastPathComponent() @@ -176,6 +176,41 @@ struct PrivateHeaderKitHelperProtocolTests { == "a7e5982ed7de5dab5dec76036682ea55825b77a8" ) #expect(swiftSectionState["version"] == nil) + + let machOKitPin = try #require( + pins.first { $0["identity"] as? String == "machokit" } + ) + let machOKitState = try #require( + machOKitPin["state"] as? [String: Any] + ) + #expect( + machOKitPin["location"] as? String + == "https://github.com/MxIris-Reverse-Engineering/MachOKit.git" + ) + #expect( + machOKitState["revision"] as? String + == "e0e0b30187ae74f2088d932845c8ddac2c79f36c" + ) + #expect(machOKitState["version"] == nil) + + let mirrorData = try Data( + contentsOf: packageRoot + .appendingPathComponent(".swiftpm/configuration/mirrors.json") + ) + let mirrorDocument = try #require( + JSONSerialization.jsonObject(with: mirrorData) as? [String: Any] + ) + let mirrors = try #require( + mirrorDocument["object"] as? [[String: String]] + ) + let expectedMirror = "https://github.com/lynnswap/MachOKit.git" + #expect(Set(mirrors.compactMap { $0["mirror"] }) == [expectedMirror]) + #expect( + Set(mirrors.compactMap { $0["original"] }) == [ + "https://github.com/MxIris-Reverse-Engineering/MachOKit", + "https://github.com/MxIris-Reverse-Engineering/MachOKit.git", + ] + ) } @Test func inventoryNormalizesImagePathMembershipAndRoundTrips() throws { From bbcd69e05982bbf6eee2b652b08a5eb75b7cee3c Mon Sep 17 00:00:00 2001 From: Kazuki Nakashima <65545348+lynnswap@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:13:32 +0900 Subject: [PATCH 5/6] docs: record issue 80 validation --- Docs/issue-remediation-progress.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Docs/issue-remediation-progress.md b/Docs/issue-remediation-progress.md index 93d5750..9166366 100644 --- a/Docs/issue-remediation-progress.md +++ b/Docs/issue-remediation-progress.md @@ -101,3 +101,33 @@ Required runtime gate: 27.0 build `24A5390f` with zero helper signals. - Preserve readable metadata/artifacts, or produce a normal bounded target failure when a target cannot be decoded. + +Implemented dependency: + +- MachOKit revision: `e0e0b30187ae74f2088d932845c8ddac2c79f36c` +- Dependency branch: `codex/issue-80-bounded-chained-fixups` +- Synthetic safety suite: 28 tests pass. +- Exact binary oracle: 108 / 57 / 34 fixup pointers for PosterBoardUI, + PrivateSearchProtocols, and UserManagementUI, matching Apple `dyld_info`. +- Branch-wide codex-review against `fec9503` completed with no findings after + fixing graph-level header, segment, multi-start, duplicate, bind-ordinal, + and pointer-format invariants. + +PrivateHeaderKit integration: + +- The raw helper preflights file-backed chained fixups before Objective-C or + Swift metadata readers can consume resolver projections. +- The direct revision pin and tracked two-spelling SwiftPM mirror resolve to + one MachOKit checkout without an identity-conflict warning. +- HelperProtocol tests: 13 passed. +- RawDump tests: 89 passed. + +Runtime gate completed: + +- Run: `run-d777ff0f-8964-4129-bc2c-accdd472fad6` +- Generation: `generation-6f89aee6-9a2d-4a3c-8ed1-7d2c2004da91` +- All three targets completed with 6 / 1 / 3 artifacts and no failure summary. +- SQLite integrity: `ok`; run warnings: 0; new helper incident reports: 0. +- Runtime match override restored to default and the run-owned Simulator was + deleted. The isolated output was moved to Trash at + `/Users/kn/.Trash/privateheaderkit-issue80-smoke-yyKic6`. From 7601855fc915803c9b321576081ff3766a477d07 Mon Sep 17 00:00:00 2001 From: Kazuki Nakashima <65545348+lynnswap@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:15:18 +0900 Subject: [PATCH 6/6] chore: remove issue 80 progress ledger --- Docs/issue-remediation-progress.md | 133 ----------------------------- 1 file changed, 133 deletions(-) delete mode 100644 Docs/issue-remediation-progress.md diff --git a/Docs/issue-remediation-progress.md b/Docs/issue-remediation-progress.md deleted file mode 100644 index 9166366..0000000 --- a/Docs/issue-remediation-progress.md +++ /dev/null @@ -1,133 +0,0 @@ -# Issue remediation progress - -Base: `main` at `0d109cda22193020914a70069c3e0b5c07c31e8b` - -## Delivery order - -1. #80 bounded MachOKit chained-fixup reads -2. #81 actionable bounded raw-helper crash diagnostics -3. #83 bounded Objective-C table and loaded-image reads - -Each issue is delivered as an independent Ready PR targeting `main`. A later -issue starts only after the earlier PR is review-clean and merged. - -## Current issue: #80 - -Branch: `codex/issue-80-bounded-chained-fixups` - -Dependency base: - -- MachOKit: `fec9503cdf3d595ef8cf4abac1602e299a8c3be4` (`0.52.101`) - -Verified evidence: - -- PosterBoardUI, PrivateSearchProtocols, and UserManagementUI terminate with - `SIGSEGV` while building or walking file-backed chained-fixup tables. -- The common owner is `MachOFile.DyldChainedFixups.pages(of:)` and - `pointers(of:in:)`, before the Objective-C/Swift metadata-specific caller. -- Current code forms metadata-sized unsafe buffers and contains an - unconditional file-slice acquisition. -- Per-target process isolation contains the crash, but all three targets have - zero artifacts and are absent from the committed generation. - -Confirmed root cause: - -- All three binaries contain a valid sparse `starts_in_image` table with - `seg_info_offset = [0, 0x10, 0]`. Apple dyld treats a zero entry as the - normal absence of fixups for that segment. -- MachOKit instead reads each zero entry as a `starts_in_segment` record at - the start of `starts_in_image`. That aliases the real record's page size as - a bogus `page_count` of `0x4000`, and `pages(of:)` walks past the 64 KiB - mapping before any page-size guard runs. -- `fixupPointersCache` exposed the older parser defect by eagerly traversing - every returned segment. The metadata readers are downstream observers, not - the owner of this failure. - -Design gate approved: - -- A shared internal bounded byte reader owns all fixup-blob ranges for both - file-backed and loaded-image table parsing. It performs exact integer - conversion, checked arithmetic, unaligned scalar loads, bounded arrays, and - bounded NUL-terminated strings before forming a pointer or collection. -- The starts-table parser treats a zero segment offset as normal absence, - preserves the original Mach-O segment index for nonzero records, and - validates each record's declared size, page prefix, and complete flexible - start-entry storage. -- The file chain walker maps a parsed record through its Mach-O segment index; - segment file offset/size owns disk reads. Page starts, multi-start indices - and termination, pointer width, and every `next * stride` advance must stay - inside the current page and file-backed segment. -- Existing public nonthrowing APIs retain their signatures and project checked - results as `nil` or empty collections. Internal typed failures retain the - distinction between absence and invalid input; an additive support SPI may - expose preflight validation without adding a protocol requirement. -- A malformed segment/page/chain cannot discard validated siblings in the - compatibility projection. Imports remain all-or-nothing because a partial - table would shift ordinal identity. -- `pointers(of:in:)` and `pointer(for:in:)` share one checked walker. The - unconditional file-slice/read traps in that path and optional rebase - resolution are removed. - -Deterministic validation gate: - -- Exact sparse-table regression `[0, 0x10, 0]`, retaining segment index 1 and - its valid pointer chain. -- Truncated header/segment-offset/page tables and out-of-range declared sizes. -- Valid and invalid multi-start tables, including bad indices and missing - `START_LAST`. -- Segment slice overflow, chain starts outside a page, pointer-width crossing, - and `next` crossing a page. -- File/image parity for checked table parsing and compatibility projections. -- Existing public API clients compile without source changes. - -Dependency delivery gate approved: - -- Publish the MachOKit fix from the exact pinned base to the `lynnswap` fork. -- Keep the direct dependency's original MxIris URL but change its requirement - to the fork commit's exact revision. A tracked repo-local SwiftPM mirror maps - both MxIris URL spellings (with and without `.git`) to the `lynnswap` fork. -- SwiftPM 6.3.3 probes confirmed that the revision requirement unifies the - existing MachOKitExtensions, MachOObjCSection, MachOSwiftSection, and - swift-demangling ranges to one checkout without an identity-conflict warning. -- `Package.resolved` retains the original URL and an unversioned exact revision; - the existing #79 Objective-C/Swift reader cohort remains unchanged. -- The mirror is intentionally a root-package build contract. PrivateHeaderKit - is an executable package; supporting it as a transitive library dependency - is outside this issue's distribution scope. - -Required runtime gate: - -- Re-run PosterBoardUI, PrivateSearchProtocols, and UserManagementUI on iOS - 27.0 build `24A5390f` with zero helper signals. -- Preserve readable metadata/artifacts, or produce a normal bounded target - failure when a target cannot be decoded. - -Implemented dependency: - -- MachOKit revision: `e0e0b30187ae74f2088d932845c8ddac2c79f36c` -- Dependency branch: `codex/issue-80-bounded-chained-fixups` -- Synthetic safety suite: 28 tests pass. -- Exact binary oracle: 108 / 57 / 34 fixup pointers for PosterBoardUI, - PrivateSearchProtocols, and UserManagementUI, matching Apple `dyld_info`. -- Branch-wide codex-review against `fec9503` completed with no findings after - fixing graph-level header, segment, multi-start, duplicate, bind-ordinal, - and pointer-format invariants. - -PrivateHeaderKit integration: - -- The raw helper preflights file-backed chained fixups before Objective-C or - Swift metadata readers can consume resolver projections. -- The direct revision pin and tracked two-spelling SwiftPM mirror resolve to - one MachOKit checkout without an identity-conflict warning. -- HelperProtocol tests: 13 passed. -- RawDump tests: 89 passed. - -Runtime gate completed: - -- Run: `run-d777ff0f-8964-4129-bc2c-accdd472fad6` -- Generation: `generation-6f89aee6-9a2d-4a3c-8ed1-7d2c2004da91` -- All three targets completed with 6 / 1 / 3 artifacts and no failure summary. -- SQLite integrity: `ok`; run warnings: 0; new helper incident reports: 0. -- Runtime match override restored to default and the run-owned Simulator was - deleted. The isolated output was moved to Trash at - `/Users/kn/.Trash/privateheaderkit-issue80-smoke-yyKic6`.