How `patch_launchd_jetsam` Prevents the Initproc Crash Loop During iOS Boot

The patch_launchd_jetsam function prevents the initproc crash loop by rewriting a conditional branch in launchd's jetsam checking routine into an unconditional jump, ensuring the function returns normally instead of triggering a kernel panic.

During early iOS boot, the launchd daemon inspects jetsam properties for each launchd job. If these properties remain uninitialized, the kernel reaches a panic path that aborts the initproc, causing an endless crash loop that prevents the VM from finishing boot. The patch_launchd_jetsam routine in the Lakr233/vphone-cli repository eliminates this guard by surgically modifying the launchd binary to bypass the panic path entirely.

Understanding the Initproc Crash Loop

The initproc is the first user-space process started by the iOS kernel and is responsible for launching critical system daemons. When launchd encounters a job with an uninitialized jetsam property category, it triggers a kernel panic that kills the initproc. Because the kernel immediately restarts launchd to maintain system operation, the process repeats, creating an initproc crash loop that renders the system unbootable.

The panic occurs in a specific code path where launchd validates jetsam categories. If the validation fails, the code follows a conditional branch toward a panic routine rather than returning normally.

Locating the Jetsam Property Check

The patching algorithm begins by identifying unique string anchors embedded in the launchd binary that indicate the presence of the jetsam validation code. In scripts/patchers/cfw_patch_jetsam.py, the patcher searches for specific error messages that precede the panic path:

anchors = [
    b"jetsam property category (Daemon) is not initialized",
    b"jetsam property category",
    b"initproc exited -- exit reason namespace 7 subcode 0x1",
]                                   # L76-L80

These byte strings serve as landmarks within the __TEXT,__text section. By locating these references, the patcher identifies the exact function containing the conditional branch that needs modification.

Identifying the Conditional Branch

Once the string anchor is located, the patcher calculates the virtual address and finds the corresponding code reference using an ADRP/ADD instruction pair within the text section:

ref_va = _find_adrp_add_ref(code, text_va, str_start_va)   # L100-L102

From this reference point, the algorithm scans backwards through the instruction stream to locate a conditional branch instruction. The helper function _is_return_block (L14-L28) validates whether the branch target leads to a normal function return (ending with ret or retab) rather than the panic path:

if insn.mnemonic not in cond_mnemonics: …               # L22-L24

if _is_return_block(data, tgt, text_foff, text_size):   # L31-L33

When such a branch is identified, the patcher records the file offset (patch_off) and the target offset (patch_target) for the subsequent rewrite operation.

Rewriting the Branch Instruction

The final step overwrites the conditional instruction with an unconditional branch (b) that jumps directly to the return block. This ensures the function always exits normally, never reaching the jetsam panic code:

data[patch_off : patch_off + 4] = asm_at(f"b #0x{patch_target:X}", patch_off)   # L44-L45

After modification, the patched binary is written back to disk and a success message confirms the operation (L50-L51).

By converting the conditional check into an unconditional jump to the success path, the kernel no longer receives the jetsam panic signal, allowing initproc to continue boot normally.

Integration and Usage

The patch_launchd_jetsam function is exposed through the CLI entry point in scripts/patchers/cfw.py. You can apply the patch directly to a launchd binary extracted during a Custom Firmware (CFW) installation:


# Patch a launchd binary directly

$ python3 scripts/patchers/cfw.py patch-launchd-jetsam /tmp/launchd
  [+] Patched at 0x1234AB: jetsam panic guard bypass

The patch is also integrated into the automated jailbreak installation workflow via scripts/cfw_install_jb.sh, which invokes the patcher during the "JB-1" stage:

$ ./scripts/cfw_install_jb.sh
[JB-1] Patching launchd (jetsam guard + hook injection)...

To verify the patch was applied correctly, load the modified binary with MachO utilities and inspect the instruction at the recorded offset (patch_off)—it should now contain the b mnemonic instead of the original conditional branch.

Summary

  • The initproc crash loop occurs when launchd triggers a kernel panic due to uninitialized jetsam properties, causing the kernel to restart launchd indefinitely.
  • The patch targets specific string anchors in scripts/patchers/cfw_patch_jetsam.py to locate the validation routine within the launchd binary.
  • The conditional branch identified by scanning backwards from string references (using _find_adrp_add_ref and _is_return_block) is converted to an unconditional jump.
  • The modification ensures the jetsam validation function always returns successfully, preventing the panic and allowing normal boot completion.

Frequently Asked Questions

What is the initproc and why does it crash during boot?

The initproc is the first user-space process started by the iOS kernel, responsible for launching system daemons including launchd. It crashes because launchd contains a validation routine that triggers a kernel panic when encountering uninitialized jetsam property categories. Since the kernel automatically restarts launchd to maintain system integrity, the panic repeats indefinitely, creating a crash loop that prevents VM initialization from completing.

How does patch_launchd_jetsam locate the specific code to modify?

The function searches for unique byte strings in the launchd binary's __TEXT,__text section, such as "jetsam property category (Daemon) is not initialized". It then finds the ADRP/ADD instruction pair referencing these strings (L100-L102) and scans backwards to identify conditional branches that target return blocks, as validated by the _is_return_block helper (L14-L28).

Is the patch reversible once applied to the launchd binary?

Yes, the patch is reversible if you retain the original binary or backup the modified bytes at the specific offset (patch_off). Since the patcher only overwrites a single 4-byte instruction (converting a conditional branch to an unconditional b), you can restore the original conditional opcode if needed. However, the vphone-cli scripts do not currently implement an automatic revert function.

Which iOS versions and architectures does this patch support?

The patch is designed for 64-bit ARM architectures (arm64) used in modern iOS devices and simulators. The instruction patterns (ADRP/ADD pairs, conditional branch mnemonics) and string anchors target specific launchd implementations typically found in iOS versions where jetsam property validation causes initproc panics during early boot. According to the source code in Lakr233/vphone-cli, the implementation specifically handles Mach-O binaries with __TEXT,__text sections as found in standard iOS firmware images.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →