diff --git a/.github/patches/opainject-build.patch b/.github/patches/opainject-build.patch index bd1c7f6..974d7a6 100644 --- a/.github/patches/opainject-build.patch +++ b/.github/patches/opainject-build.patch @@ -108,13 +108,17 @@ diff --git a/rop_inject.m b/rop_inject.m index 77acb84..f125a72 100644 --- a/rop_inject.m +++ b/rop_inject.m -@@ -293,12 +293,22 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, +@@ -293,13 +293,22 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, thread_act_array_t cachedThreads; mach_msg_type_number_t cachedThreadCount; kr = task_threads(task, &cachedThreads, &cachedThreadCount); - if (kr != KERN_SUCCESS) return kr; + if (kr != KERN_SUCCESS) { -+ free(origThreadFullState); ++ // The target thread was already rewritten for the remote call. ++ // Restore it before returning or launchd may continue with a bad PC/SP. ++ thread_abort(targetThread); ++ thread_restore_state_arm64(targetThread, origThreadFullState); ++ origThreadFullState = NULL; // thread_restore_state_arm64 consumes it + vm_deallocate(task, remoteStack, STACK_SIZE); + thread_resume(targetThread); + return kr; @@ -132,15 +136,19 @@ index 77acb84..f125a72 100644 printf("[arbCall] Started thread, waiting for it to finish...\n"); // wait for arbitary call to finish (or not) -@@ -310,6 +320,11 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, +@@ -310,7 +320,15 @@ kern_return_t arbCall(task_t task, thread_act_t targetThread, uint64_t* retOut, + if(kr != KERN_SUCCESS) { - free(origThreadFullState); - printf("[arbCall] ERROR: failed to wait for thread to finish: %s\n", mach_error_string(kr)); -+ if (kr == KERN_OPERATION_TIMED_OUT) { -+ printf("[arbCall] Remote call still active; preserving its stack and thread state\n"); -+ return kr; -+ } +- free(origThreadFullState); ++ // The target was resumed before waiting. Fail closed: abort the ++ // remote call, restore the original context, then release its stack. ++ thread_suspend(targetThread); ++ thread_abort(targetThread); ++ thread_restore_state_arm64(targetThread, origThreadFullState); ++ origThreadFullState = NULL; // thread_restore_state_arm64 consumes it + vm_deallocate(task, remoteStack, STACK_SIZE); ++ thread_resume(targetThread); + printf("[arbCall] ERROR: failed to wait for thread to finish: %s\n", mach_error_string(kr)); return kr; } @@ -187,8 +195,11 @@ index 77acb84..f125a72 100644 - arbCall(task, pthread, (uint64_t*)&readExtensionRet, true, sandbox_extension_consumeAddr, 1, remoteExtString); - vm_deallocate(task, remoteExtString, remoteExtStringSize); + kern_return_t kr = arbCall(task, pthread, (uint64_t*)&readExtensionRet, true, sandbox_extension_consumeAddr, 1, remoteExtString); -+ if (kr != KERN_OPERATION_TIMED_OUT) vm_deallocate(task, remoteExtString, remoteExtStringSize); -+ if (kr != KERN_SUCCESS) return kr; ++ vm_deallocate(task, remoteExtString, remoteExtStringSize); ++ if (kr != KERN_SUCCESS) { ++ thread_terminate(pthread); ++ return kr; ++ } printf("[sandboxFixup] sandbox_extension_consume returned %lld for read extension\n", (int64_t)readExtensionRet); - retval |= (readExtensionRet <= 0); @@ -212,8 +223,11 @@ index 77acb84..f125a72 100644 - arbCall(task, pthread, (uint64_t*)&executableExtensionRet, true, sandbox_extension_consumeAddr, 1, remoteExtString); - vm_deallocate(task, remoteExtString, remoteExtStringSize); + kern_return_t kr = arbCall(task, pthread, (uint64_t*)&executableExtensionRet, true, sandbox_extension_consumeAddr, 1, remoteExtString); -+ if (kr != KERN_OPERATION_TIMED_OUT) vm_deallocate(task, remoteExtString, remoteExtStringSize); -+ if (kr != KERN_SUCCESS) return kr; ++ vm_deallocate(task, remoteExtString, remoteExtStringSize); ++ if (kr != KERN_SUCCESS) { ++ thread_terminate(pthread); ++ return kr; ++ } printf("[sandboxFixup] sandbox_extension_consume returned %lld for executable extension\n", (int64_t)executableExtensionRet); - retval |= (executableExtensionRet <= 0); @@ -243,7 +257,7 @@ index 77acb84..f125a72 100644 + kr = sandboxFixup(task, pthread, pid, dylibPath, allImageInfoAddr); + if (kr != KERN_SUCCESS) { + printf("[injectDylibViaRop] ERROR: sandbox fixup failed: %s\n", mach_error_string(kr)); -+ if (kr != KERN_OPERATION_TIMED_OUT) thread_terminate(pthread); ++ thread_terminate(pthread); + return kr; + } @@ -268,8 +282,11 @@ index 77acb84..f125a72 100644 - vm_deallocate(task, remoteDylibPath, remoteDylibPathSize); + void* dlopenRet = NULL; + kr = arbCall(task, pthread, (uint64_t*)&dlopenRet, true, dlopenAddr, 2, remoteDylibPath, RTLD_NOW); -+ if (kr != KERN_OPERATION_TIMED_OUT) vm_deallocate(task, remoteDylibPath, remoteDylibPathSize); -+ if (kr != KERN_SUCCESS) return kr; ++ vm_deallocate(task, remoteDylibPath, remoteDylibPathSize); ++ if (kr != KERN_SUCCESS) { ++ thread_terminate(pthread); ++ return kr; ++ } if (dlopenRet) { printf("[injectDylibViaRop] dlopen succeeded, library handle: %p\n", dlopenRet); @@ -278,7 +295,10 @@ index 77acb84..f125a72 100644 uint64_t remoteErrorString = 0; - arbCall(task, pthread, (uint64_t*)&remoteErrorString, true, dlerrorAddr, 0); + kr = arbCall(task, pthread, (uint64_t*)&remoteErrorString, true, dlerrorAddr, 0); -+ if (kr != KERN_SUCCESS) return kr; ++ if (kr != KERN_SUCCESS) { ++ thread_terminate(pthread); ++ return kr; ++ } char *errorString = task_copy_string(task, remoteErrorString); printf("[injectDylibViaRop] dlopen failed, error:\n%s\n", errorString); free(errorString); diff --git a/.github/patches/opainject-cleanup.patch b/.github/patches/opainject-cleanup.patch new file mode 100644 index 0000000..fe2462c --- /dev/null +++ b/.github/patches/opainject-cleanup.patch @@ -0,0 +1,25 @@ +diff --git a/rop_inject.m b/rop_inject.m +--- a/rop_inject.m ++++ b/rop_inject.m +@@ + kr = task_threads(task, &cachedThreads, &cachedThreadCount); + if (kr != KERN_SUCCESS) { ++ // The target thread was already rewritten for the remote call. ++ // Restore it before returning or launchd may continue with a bad PC/SP. ++ thread_abort(targetThread); ++ thread_restore_state_arm64(targetThread, origThreadFullState); + free(origThreadFullState); + vm_deallocate(task, remoteStack, STACK_SIZE); + thread_resume(targetThread); +@@ + if(kr != KERN_SUCCESS) + { ++ if (kr != KERN_OPERATION_TIMED_OUT) { ++ // All non-timeout errors must restore the hijacked target thread. ++ thread_suspend(targetThread); ++ thread_abort(targetThread); ++ thread_restore_state_arm64(targetThread, origThreadFullState); ++ thread_resume(targetThread); ++ } + free(origThreadFullState); + printf("[arbCall] ERROR: failed to wait for thread to finish: %s\n", mach_error_string(kr)); diff --git a/.github/patches/opainject-serial-cleanup.patch b/.github/patches/opainject-serial-cleanup.patch new file mode 100644 index 0000000..72bf150 --- /dev/null +++ b/.github/patches/opainject-serial-cleanup.patch @@ -0,0 +1,12 @@ +diff --git a/rop_inject.m b/rop_inject.m +index f125a72..d7c0b1a 100644 +--- a/rop_inject.m ++++ b/rop_inject.m +@@ -349,3 +349,0 @@ +- // release fake stack as it's no longer needed +- vm_deallocate(task, remoteStack, STACK_SIZE); +- +@@ -369,0 +367,3 @@ ++ // Keep the temporary stack mapped until the target has been restored. ++ vm_deallocate(task, remoteStack, STACK_SIZE); ++ diff --git a/.github/workflows/roothide.yml b/.github/workflows/roothide.yml index 648ac01..6625871 100644 --- a/.github/workflows/roothide.yml +++ b/.github/workflows/roothide.yml @@ -44,7 +44,8 @@ jobs: run: | set -euo pipefail git -C BaseBin/XPF apply "$GITHUB_WORKSPACE/.github/patches/XPF-roothide.patch" - git -C BaseBin/opainject apply "$GITHUB_WORKSPACE/.github/patches/opainject-build.patch" + git -C BaseBin/opainject apply --recount "$GITHUB_WORKSPACE/.github/patches/opainject-build.patch" + git -C BaseBin/opainject apply --unidiff-zero "$GITHUB_WORKSPACE/.github/patches/opainject-serial-cleanup.patch" # Xcode 15.4 is the last toolchain used by the original Dopamine 3 # project layout. Newer Xcodes treat some command-line targets as diff --git a/Application/Dopamine/Jailbreak/DOJailbreaker.m b/Application/Dopamine/Jailbreak/DOJailbreaker.m index a3949a1..c9fbd8c 100644 --- a/Application/Dopamine/Jailbreak/DOJailbreaker.m +++ b/Application/Dopamine/Jailbreak/DOJailbreaker.m @@ -375,7 +375,10 @@ - (NSError *)loadBasebinTrustcache { while (true) { xpc_object_t xdict = nil; - if (!xpc_pipe_receive(info->serverPort, &xdict)) { + if (xpc_pipe_receive(info->serverPort, &xdict)) { + break; + } + if (xdict) { if (jbserver_received_boomerang_xpc_message(&gBoomerangServer, xdict) == JBS_BOOMERANG_DONE) { dispatch_semaphore_signal(info->boomerangDone); break; @@ -389,51 +392,84 @@ - (NSError *)injectLaunchdHook { // Host a boomerang server that will be used by launchdhook to get the jailbreak primitives from this app mach_port_t serverPort = MACH_PORT_NULL; - mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &serverPort); - mach_port_insert_right(mach_task_self(), serverPort, serverPort, MACH_MSG_TYPE_MAKE_SEND); - - struct boomerang_info info; - info.serverPort = serverPort; - info.boomerangDone = dispatch_semaphore_create(0); + kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &serverPort); + if (kr != KERN_SUCCESS) { + return [NSError errorWithDomain:JBErrorDomain code:JBErrorCodeFailedLaunchdInjection userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Allocating boomerang server port failed: %s", mach_error_string(kr)]}]; + } + kr = mach_port_insert_right(mach_task_self(), serverPort, serverPort, MACH_MSG_TYPE_MAKE_SEND); + if (kr != KERN_SUCCESS) { + mach_port_destroy(mach_task_self(), serverPort); + return [NSError errorWithDomain:JBErrorDomain code:JBErrorCodeFailedLaunchdInjection userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Creating boomerang send right failed: %s", mach_error_string(kr)]}]; + } - pthread_t boomerangThread; - pthread_create(&boomerangThread, NULL, (void *(*)(void *))boomerang_server, &info); - pthread_detach(boomerangThread); + struct boomerang_info *info = calloc(1, sizeof(*info)); + info->serverPort = serverPort; + info->boomerangDone = dispatch_semaphore_create(0); // Stash port to server in launchd's initPorts[2] // Since we don't have the neccessary entitlements, we need to do it over jbctl posix_spawnattr_t attr; posix_spawnattr_init(&attr); - posix_spawnattr_set_registered_ports_np(&attr, (mach_port_t[]){MACH_PORT_NULL, MACH_PORT_NULL, serverPort}, 3); + int attrError = posix_spawnattr_set_registered_ports_np(&attr, (mach_port_t[]){MACH_PORT_NULL, MACH_PORT_NULL, serverPort}, 3); + if (attrError != 0) { + posix_spawnattr_destroy(&attr); + free(info); + mach_port_destroy(mach_task_self(), serverPort); + return [NSError errorWithDomain:JBErrorDomain code:JBErrorCodeFailedLaunchdInjection userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Preparing launchd port handoff failed with error code %d", attrError]}]; + } pid_t spawnedPid = 0; const char *jbctlPath = JBROOT_PATH("/basebin/jbctl"); int spawnError = posix_spawn(&spawnedPid, jbctlPath, NULL, &attr, (char *const *)(const char *[]){ jbctlPath, "internal", "launchd_stash_port", NULL }, NULL); + posix_spawnattr_destroy(&attr); if (spawnError != 0) { + free(info); + mach_port_destroy(mach_task_self(), serverPort); return [NSError errorWithDomain:JBErrorDomain code:JBErrorCodeFailedLaunchdInjection userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Spawning jbctl failed with error code %d", spawnError]}]; } - posix_spawnattr_destroy(&attr); int status = 0; do { if (waitpid(spawnedPid, &status, 0) == -1) { + free(info); + mach_port_destroy(mach_task_self(), serverPort); return [NSError errorWithDomain:JBErrorDomain code:JBErrorCodeFailedLaunchdInjection userInfo:@{NSLocalizedDescriptionKey : @"Waiting for jbctl failed"}];; } } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + free(info); + mach_port_destroy(mach_task_self(), serverPort); + return [NSError errorWithDomain:JBErrorDomain code:JBErrorCodeFailedLaunchdInjection userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"jbctl failed while stashing launchd port (status 0x%x)", status]}]; + } + + pthread_t boomerangThread; + int threadError = pthread_create(&boomerangThread, NULL, (void *(*)(void *))boomerang_server, info); + if (threadError != 0) { + free(info); + mach_port_destroy(mach_task_self(), serverPort); + return [NSError errorWithDomain:JBErrorDomain code:JBErrorCodeFailedLaunchdInjection userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Starting boomerang server failed with error code %d", threadError]}]; + } // Inject launchdhook.dylib into launchd via opainject printf("[launchdhook-build] launchd-availability-v3\n"); int r = exec_cmd(JBROOT_PATH("/basebin/opainject"), "1", JBROOT_PATH("/basebin/launchdhook.dylib"), NULL); if (r != 0) { + mach_port_destroy(mach_task_self(), serverPort); + pthread_cancel(boomerangThread); + pthread_detach(boomerangThread); return [NSError errorWithDomain:JBErrorDomain code:JBErrorCodeFailedLaunchdInjection userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"opainject failed with error code %d", r]}]; } // A broken launchd handoff must return an error instead of leaving the UI // blocked forever after opainject has already exited. dispatch_time_t handoffDeadline = dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC); - if (dispatch_semaphore_wait(info.boomerangDone, handoffDeadline) != 0) { - mach_port_deallocate(mach_task_self(), serverPort); + if (dispatch_semaphore_wait(info->boomerangDone, handoffDeadline) != 0) { + mach_port_destroy(mach_task_self(), serverPort); + pthread_cancel(boomerangThread); + pthread_detach(boomerangThread); return [NSError errorWithDomain:JBErrorDomain code:JBErrorCodeFailedLaunchdInjection userInfo:@{NSLocalizedDescriptionKey : @"Timed out waiting for launchd primitive handoff"}]; } - mach_port_deallocate(mach_task_self(), serverPort); + pthread_join(boomerangThread, NULL); + free(info); + mach_port_destroy(mach_task_self(), serverPort); return nil; } diff --git a/BaseBin/launchdhook/src/main.m b/BaseBin/launchdhook/src/main.m index 3461e16..91c9457 100644 --- a/BaseBin/launchdhook/src/main.m +++ b/BaseBin/launchdhook/src/main.m @@ -137,7 +137,10 @@ int sysctlbyname_hook(const char *name, void *oldp, size_t *oldlenp, void *newp, if (err != 0) { char msg[1000]; snprintf(msg, 1000, "Dopamine: Failed to recover primitives (error %d), cannot continue.", err); - abort_with_reason(7, 1, msg, 0); + // launchd is PID 1. Never terminate it because the handoff failed: + // doing so makes the kernel panic with "initproc exited" and reboots + // the device. Leave launchd untouched and report the failed attempt. + fprintf(stderr, "%s\n", msg); return; } @@ -190,3 +193,4 @@ int sysctlbyname_hook(const char *name, void *oldp, size_t *oldlenp, void *newp, roothide_launchd_postinit(firstLoad); /********** roothide specfic ********/ } + diff --git a/BaseBin/watchdoghook/src/main.m b/BaseBin/watchdoghook/src/main.m index bb54b7d..5019a85 100644 --- a/BaseBin/watchdoghook/src/main.m +++ b/BaseBin/watchdoghook/src/main.m @@ -43,10 +43,10 @@ kern_return_t IOConnectCallStructMethod_hook(mach_port_t connection, uint32_t se { ///////////////////////////// if(access("/var/log/.disable_watchdoghook", F_OK) == 0) { - return; -} + return; + } /////////////////////////////// MSHookFunction(IOServiceOpen, (void *)&IOServiceOpen_hook, (void **)&IOServiceOpen_orig); MSHookFunction(IOConnectCallStructMethod, (void *)&IOConnectCallStructMethod_hook, (void **)&IOConnectCallStructMethod_orig); -} \ No newline at end of file +}