Repository navigation
Fix App Decrypt on large apps: OOM, Mach port leak, and live progress - #328
ShintoKosei wants to merge 1 commit into
Conversation
|
CI note: the For reference, the exact same workflow was run against this PR's head commit ( Two earlier builds of the same source also passed:
Maintainer edits are enabled on this PR; happy to adjust anything. |
App Decrypt works for small apps but hangs and gets killed on large ones (e.g. a ~2.45 GB app). Root causes: * createZipArchive() loaded every file of the bundle into RAM via Data(contentsOf:) and retained them all until the archive was written, so a multi-GB app was an immediate jetsam kill. It also used ZIP32-only fields and a pure-Swift byte-by-byte CRC loop. * decrypt_to_output() called vmmapremotepage() once per page. Each call creates a Mach memory-entry port and bumps the remote vm_object refcount via a kernel write, but only the mapping was deallocated - the port was never released. Cost/leak grows linearly with app size. * The progress callback was wired in C but never registered from Swift, and the size enumeration ran on the main thread while the only background task assertion ended ~3 s after launch. Changes: * zipmgr.swift: add createZipArchiveStreaming() - one file at a time, constant memory, zlib crc32, proper ZIP64 (extra fields, EOCD64 and locator), and a progress closure. The original writer is left intact. * vm.m / vm.h: add vmreleaseshmem() to release both the mapping and the memory-entry port; also fix two early-return paths that leaked the port. * decrypt.m: use it at every call site; patch the input buffer in place instead of allocating a second full Mach-O copy; loop read/write for short I/O; fix a 32-bit page mask that wiped the upper half of >4 GB addresses; throttle progupdate() to ~1% boundaries; log page progress. * DecryptView.swift: use the streaming writer, post progress notifications on the main queue (NotificationCenter.Publisher delivers on the posting thread, so background posts mutated @State off-main and the bar only jumped to 100% at the end), split the bar into copy/decrypt/pack phases with stage labels, move size enumeration off the main thread, keep one background task assertion for the whole job, log disk/copy failures and preserve Payload/ if packing fails. Verified on device: decrypting a ~2.45 GB app now finishes in ~10 s and produces an IPA; the progress bar updates live. Both the original fix and the progress follow-up were built successfully with the repo's own GitHub Actions workflow (macos-26, Xcode, ldid). Relates to rooootdev#298, rooootdev#317.
|
Hi @rooootdev and @neonmodder123 - when you have a moment, I'd appreciate a look at this. One thing worth flagging: the This addresses the App Decrypt hang/crash on large apps reported in #298 and #317, and adds a live progress bar. It's been verified on-device with a ~2.45 GB app (about ten seconds end to end). No rush at all - happy to split the change, rebase, or adjust anything to better fit the codebase. Thanks for maintaining lara! |
Summary
App Decrypt works for small apps but hangs and then gets killed on large ones. This PR makes it work on multi-GB apps and gives it a live progress bar.
Verified on an iPad: decrypting a ~2.45 GB app now finishes in roughly ten seconds and produces an IPA, and the progress bar updates in real time.
Root causes
OOM in the ZIP writer (the crash).
createZipArchive()inlara/classes/zipmgr.swiftcallsData(contentsOf:)on every file of the bundle and keeps all of them alive infileEntriesuntil the archive is fully written. A 2.45 GB app therefore needs >2.45 GB of resident memory inside lara and is killed by jetsam. It also uses a pure-Swift byte-by-byte CRC loop and only ZIP32 fields (UInt32sizes/offsets,UInt16entry count).Mach port leak per page.
decrypt_to_output()callsvmmapremotepage()once per page.vmcreateshmemwithobj()creates a memory-entry Mach port withmach_make_memory_entry_64()and bumps the remotevm_objectrefcount via a kernel write, but callers onlymach_vm_deallocate()the mapping -shmem.portwas never released, and two early-return paths leaked it as well. Leak and kernel work grow linearly with app size.No live progress.
set_progress_callback()exists in C but was never registered from Swift. Once wired up, notifications were posted from the background decrypt queue;NotificationCenter.Publisherdelivers synchronously on the posting thread, so.onReceivemutated@Stateoff-main and SwiftUI only repainted at the very end - the bar appeared to jump straight to 100%. The size enumeration also ran on the main thread, and the only background task assertion ended ~3 s after launch.Smaller issues: a second full-size copy of the Mach-O was allocated; single-shot
read()/write()did not handle short I/O;pg_virt & ~(page_size - 1)used auint32_tmask and wiped the upper half of >4 GB addresses; copy failures were swallowed bytry?.Changes
zipmgr.swift- addcreateZipArchiveStreaming(): one file at a time, constant memory, zlibcrc32, proper ZIP64 (LFH/CD extra fields, EOCD64 + locator), and aprogress:closure. The original writer is left untouched for other callers.TaskRop/vm.m/vm.h- addvmreleaseshmem()to release both the mapping and the memory-entry port; fix the two early-return paths that leaked the port.decrypt.m- use it at all six call sites; patch the input buffer in place rather than allocating a second full Mach-O; loopread/writefor short I/O; fix the 64-bit page mask; throttleprogupdate()to ~1% boundaries (max ~101 callbacks per binary); log page progress.DecryptView.swift- use the streaming writer; post progress notifications on the main queue with a lock-protected value; split the bar into copy (0-35%), page decrypt (35-85%) and pack (85-100%) phases with stage labels; keep it monotonic so framework binaries restarting at 0% cannot move it backwards; move size enumeration off the main thread; hold one background task assertion for the whole job; log free space and copy failures; preservePayload/if packing fails so the decrypted binary is recoverable.The kernel exploit itself is not touched.
Testing
.github/workflows/build.yml(macos-26, Xcode,ldid) - bothbuildandreleasejobs succeeded.Related