Skip to content

[Root Cause] Path B rejects its recovery state after closing the DFU handle and overclaims A12+ support #106

Description

@GregHabiskis

Summary

Path B has two separate problems that are currently presented as one bypass flow:

  1. The reported step-2 error is caused by an internal USB handle/state lifecycle bug.
  2. Even after that bug is fixed, the current Path B implementation does not provide a functional A12+ activation path.

The first problem is directly reproducible with the existing code and explains #42:

[INFO] [path_b] Step 1/10: Rebooting device from DFU to recovery mode...
[INFO] [path_b] DFU abort sent, waiting for recovery mode...
[INFO] [path_b] Recovery mode detected (30s)
[INFO] [path_b] Step 2/10: Manipulating in recovery mode...
[ERROR] [path_b_id] No USB handle for DFU mode

This report is focused on the root cause and possible solutions. It complements #42, #88, #91, #82, and #38 rather than replacing those user reports.

Affected Target

  • Device: iPhone XS Max
  • SoC: A12 Bionic
  • CPID: 0x8020
  • Firmware: iOS 18.7.9
  • Path: path_b_a12plus

Reproduction

  1. Put an A12+ device into DFU mode.
  2. Run the normal flow or ./tr4mpass --force-path-b.
  3. Allow Path B to complete the DFU-to-recovery transition.
  4. Observe that recovery is detected, followed immediately by the no-handle error.

The failure does not depend on the USB serial descriptor index. It occurs before the serial descriptor read is attempted.

Expected Behavior

After the DFU handle is closed and recovery mode is detected, Path B should either:

  • use a recovery-mode libirecovery client for all subsequent recovery operations, or
  • explicitly reacquire and store a recovery handle before invoking the identity step.

It must not require a DFU-only libusb_device_handle after the device has intentionally left DFU mode.

Actual Control Flow

1. DFU handle acquisition

src/main.c:130-151 calls usb_dfu_find(), reads CPID/ECID, and stores the opened DFU handle in dev->usb.

src/device/usb_dfu.c:83-122 opens Apple DFU VID 0x05AC / PID 0x1227, detaches the kernel driver, and claims interface 0.

2. DFU-to-recovery transition

src/bypass/path_b.c:88-153 sends DFU_ABORT, closes the handle, and explicitly clears the state:

usb_dfu_close(dev->usb);
dev->usb = NULL;
dev->is_dfu_mode = 0;

The function then creates a temporary libusb context and polls for recovery PID 0x1281.

3. Step 2 rejects the intentional state

src/bypass/path_b.c:161-174 immediately calls path_b_manipulate_identity() after recovery is detected.

src/bypass/path_b_identity.c:331-339 still requires dev->usb to be non-NULL:

if (!dev->usb) {
    log_error("[path_b_id] No USB handle for DFU mode");
    return -1;
}

That guard is incompatible with the previous function, which intentionally closed and nulled the DFU handle.

Why This Is Not Primarily a Descriptor-Index Bug

The current tree already contains the runtime descriptor-index work that an older implementation was missing:

  • src/device/usb_dfu.c:97-102 captures bDeviceDescriptor.iSerialNumber.
  • src/device/usb_dfu.c:204-215 probes the runtime hint followed by legacy indices 3 and 4.
  • src/bypass/path_b_identity.c:118-194 uses the cached index with the same fallbacks.
  • src/exploit/checkm8_spray.c:191-201 passes the runtime index into checkm8 helpers.

A descriptor-index failure would result in a CPID/ECID parsing or serial-read error. The observed error is emitted at path_b_identity.c:337, before path_b_read_serial() is reached.

There are still transition issues worth fixing:

  • src/bypass/path_b.c:104-106 ignores the return value of the raw DFU_ABORT transfer.
  • The recovery poll only accepts PID 0x1281, while path_b_identity.c:267-276 accepts recovery modes corresponding to 0x1280..0x1283.
  • The recovery poll does not match the detected ECID, so another connected recovery device could be mistaken for the target.

Existing Recovery-Side Code

The repository already has code that appears intended to handle the post-transition state:

  • src/bypass/path_b_identity.c:197-227 reads the serial through libirecovery when dev->usb == NULL.
  • src/bypass/path_b_identity.c:231-305 opens recovery mode and uses irecv_setenv() / irecv_saveenv().
  • src/bypass/path_b.c:184-249 later reopens recovery mode for the reboot-to-normal step.

The stale DFU-only precondition makes this recovery branch unreachable from the real Path B orchestration.

Test Coverage Gap

tests/integration/test_path_b_e2e.c:19-21 explicitly avoids executing path_b_module.execute() because the real transition polls can take up to 60 seconds.

The identity test at tests/integration/test_path_b_e2e.c:82-128 injects a fake non-NULL USB handle. It therefore cannot catch the production sequence where step_reboot_to_recovery() sets dev->usb = NULL.

A full mocked state-machine test is needed with these states:

DFU handle present
  -> DFU_ABORT
  -> DFU handle closed
  -> recovery detected
  -> dev->usb == NULL
  -> libirecovery serial read/write
  -> recovery reboot
  -> normal-mode lockdownd reconnect

Compatibility Finding: A12+ Support Is Not Functional

The repository's chip table correctly marks CPID 0x8020 as A12 and non-checkm8-vulnerable:

  • include/device/chip_db_table_rop.h:175-190
  • tests/test_chip_db.c:37-42
  • src/exploit/checkm8.c:28-43

However, Path B accepts any non-checkm8 device:

  • src/bypass/path_b.c:52-77

The wrapper also labels every nonzero CPID as supported:

  • start-helpers.sh:384-393

The README claims functional Path B support for A12 through A17:

  • README.md:6-8
  • README.md:68-78
  • README.md:193-199

The current Path B implementation does not contain an A12 SecureROM exploit. It changes an iBoot environment value and then attempts to use locally fabricated session data. The activation record intentionally contains empty SEP-bound fields:

  • src/activation/record.c:157-162
  • src/activation/record.c:218-231

The offline session code also states that the locally fabricated response requires a patched mobileactivationd:

  • src/activation/session.c:78-83
  • src/activation/session.c:281-297

Path B never performs the Path A jailbreak/mobileactivationd replacement. The integration test documents the expected A12+ activation rejection rather than a successful activation:

  • tests/integration/test_path_b_e2e.c:132-187

Therefore, removing the stale handle guard would fix the immediate error but would not make iPhone XS Max / A12 / iOS 18.7.9 activation functional.

Possible Solutions

Option 1: Minimal state/lifecycle fix

Make the existing recovery implementation reachable and correct:

  • Remove the DFU-only dev->usb precondition from path_b_manipulate_identity().
  • Add an explicit device-mode enum instead of using dev->usb == NULL as an implicit recovery state.
  • Open and validate a recovery irecv_client_t before serial read/write.
  • Check the DFU_ABORT result before continuing.
  • Poll all supported recovery PIDs and match the expected ECID.
  • Add a full mocked Path B transition test with dev->usb == NULL after re-enumeration.

This resolves the reported error only. It must not be described as an A12+ bypass fix.

Option 2: Recommended honest support behavior

Gate the current Path B implementation rather than running destructive operations on unsupported devices:

  • Refuse known A12+ chips with a clear message that no functional activation path is integrated.
  • Do not label every non-checkm8 or unknown CPID as supported.
  • Update the README support matrix to distinguish detection, experimental steps, and verified activation.
  • Preserve the recovery lifecycle fix as a separate correctness change if the recovery code is still useful for research.
  • Make the final success condition require an actual Activated state, never merely a successful local command or marker write.

This is consistent with the design discussion in research/fix-issues-hw-usability.md:230-263 and 751-763.

Option 3: Separate A12/A13 research implementation

A real A12/A13 path would require a separate, hardware-specific SecureROM primitive, a compatible boot/jailbreak chain, and a valid activation strategy. This is not a descriptor-index adjustment or a one-line handle fix.

Any such implementation should be separately reviewed, tested on owned hardware, and kept out of the current generic path_b_a12plus module until it can demonstrate real-device activation.

Proposed Acceptance Criteria

  • No production path invokes a DFU-only operation after the DFU handle is closed.
  • The state machine records DFU, recovery, and normal modes explicitly.
  • Recovery re-enumeration is validated by mode and ECID.
  • The exact reproduction above no longer emits No USB handle for DFU mode.
  • A full mocked Path B test covers DFU close, recovery open, setenv, reboot, and normal reconnect.
  • CPID 0x8020 does not receive a generic “supported” result unless a real activation implementation is present.
  • README claims match tested behavior.
  • The tool reports failure unless the device's activation state is actually Activated.

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions