summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am3
-rw-r--r--src/deviceinterfaced.c295
-rw-r--r--src/deviceinterfaced.h28
-rw-r--r--src/idevicerestore.c39
4 files changed, 365 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 2b7084e..c61dfd7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -47,6 +47,9 @@ idevicerestore_SOURCES = \
ace3.c ace3.h \
download.c download.h \
locking.c locking.h
+if DARWIN
+idevicerestore_SOURCES += deviceinterfaced.c deviceinterfaced.h
+endif
if HAVE_LIMERA1N
idevicerestore_SOURCES += limera1n_payload.h limera1n.c limera1n.h
endif
diff --git a/src/deviceinterfaced.c b/src/deviceinterfaced.c
new file mode 100644
index 0000000..345a59a
--- /dev/null
+++ b/src/deviceinterfaced.c
@@ -0,0 +1,295 @@
+/*
+ * deviceinterfaced.c
+ * Control deviceinterfaced while accessing restore devices on macOS
+ *
+ * Copyright (c) 2026 libimobiledevice contributors. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <spawn.h>
+#include <stdatomic.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <libimobiledevice-glue/thread.h>
+
+#include "deviceinterfaced.h"
+#include "log.h"
+
+#define DEVICEINTERFACED_LABEL "com.apple.deviceinterfaced"
+#define DEVICEINTERFACED_TARGET "system/" DEVICEINTERFACED_LABEL
+#define DEVICEINTERFACED_PLIST "/Library/Apple/System/Library/PrivateFrameworks/DeviceInterface.framework/Support/com.apple.deviceinterfaced.plist"
+#define SUDO_PATH "/usr/bin/sudo"
+#define LAUNCHCTL_PATH "/bin/launchctl"
+#define SUDO_PROMPT "[sudo] password to manage " DEVICEINTERFACED_LABEL ": "
+/* "sudo" "-p" prompt "--" "/bin/launchctl" + subcommand args (max observed: 3) + NULL */
+#define LAUNCHCTL_ARGV_MAX 10
+
+extern char **environ;
+
+enum deviceinterfaced_control_state {
+ DEVICEINTERFACED_INACTIVE,
+ DEVICEINTERFACED_BOOTED_OUT,
+ DEVICEINTERFACED_KILLING,
+ DEVICEINTERFACED_NEEDS_KICKSTART
+};
+
+static int cleanup_registered = 0;
+static enum deviceinterfaced_control_state control_state = DEVICEINTERFACED_INACTIVE;
+static atomic_bool stop_killer;
+static THREAD_T killer_thread = THREAD_T_NULL;
+
+/*
+ * Runs "launchctl <args...>". Read-only queries (e.g. "print") never need
+ * elevated privileges and are run directly. State-changing subcommands
+ * ("bootout", "bootstrap", "kickstart", "kill") act on a system-domain
+ * LaunchDaemon and require root, so they are wrapped with "sudo" instead of
+ * requiring the whole idevicerestore process to run as root. sudo prompts
+ * for the password on the controlling terminal (/dev/tty) directly, so
+ * redirecting stdout/stderr below does not hide the prompt; it is only
+ * suppressed here to keep launchctl's own (uninteresting) output quiet.
+ * Any sudo diagnostic (e.g. "a password is required") is left visible on
+ * stderr for privileged calls so the user can see why it failed.
+ */
+static int launchctl_run(char *const args[], int privileged)
+{
+ posix_spawn_file_actions_t actions;
+ char *argv[LAUNCHCTL_ARGV_MAX];
+ const char *program;
+ pid_t pid;
+ pid_t waited;
+ int error;
+ int status;
+ int argc = 0;
+ int i;
+
+ if (privileged) {
+ argv[argc++] = SUDO_PATH;
+ argv[argc++] = "-p";
+ argv[argc++] = SUDO_PROMPT;
+ argv[argc++] = "--";
+ argv[argc++] = LAUNCHCTL_PATH;
+ program = SUDO_PATH;
+ } else {
+ argv[argc++] = LAUNCHCTL_PATH;
+ program = LAUNCHCTL_PATH;
+ }
+ for (i = 0; args[i] != NULL; i++) {
+ if (argc >= LAUNCHCTL_ARGV_MAX - 1) {
+ return -ENAMETOOLONG;
+ }
+ argv[argc++] = args[i];
+ }
+ argv[argc] = NULL;
+
+ error = posix_spawn_file_actions_init(&actions);
+ if (error != 0) {
+ return -error;
+ }
+ error = posix_spawn_file_actions_addopen(&actions, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+ if (error == 0 && !privileged) {
+ error = posix_spawn_file_actions_addopen(&actions, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+ }
+ if (error != 0) {
+ posix_spawn_file_actions_destroy(&actions);
+ return -error;
+ }
+
+ error = posix_spawn(&pid, program, &actions, NULL, argv, environ);
+ posix_spawn_file_actions_destroy(&actions);
+ if (error != 0) {
+ return -error;
+ }
+
+ do {
+ waited = waitpid(pid, &status, 0);
+ } while (waited < 0 && errno == EINTR);
+ if (waited < 0) {
+ return -errno;
+ }
+
+ if (!WIFEXITED(status)) {
+ return -EIO;
+ }
+ return WEXITSTATUS(status);
+}
+
+static void log_launchctl_failure(const char *operation, int status)
+{
+ if (status < 0) {
+ logger(LL_ERROR, "Could not %s " DEVICEINTERFACED_LABEL ": %s.\n", operation, strerror(-status));
+ } else {
+ logger(LL_ERROR, "Could not %s " DEVICEINTERFACED_LABEL ": launchctl exited with status %d.\n", operation, status);
+ }
+}
+
+static int service_is_loaded(int *loaded)
+{
+ char *const argv[] = {
+ "print", DEVICEINTERFACED_TARGET, NULL
+ };
+ int status = launchctl_run(argv, 0);
+
+ if (status < 0) {
+ return status;
+ }
+ *loaded = (status == 0);
+ return 0;
+}
+
+static int wait_for_service_to_unload(void)
+{
+ int i;
+ int loaded;
+
+ for (i = 0; i < 100; i++) {
+ int status = service_is_loaded(&loaded);
+ if (status < 0) {
+ return status;
+ }
+ if (!loaded) {
+ return 0;
+ }
+ usleep(50000);
+ }
+ return -ETIMEDOUT;
+}
+
+static void *kill_service(void *data)
+{
+ char *const argv[] = {
+ "kill", "SIGKILL", DEVICEINTERFACED_TARGET, NULL
+ };
+
+ (void)data;
+ while (!atomic_load_explicit(&stop_killer, memory_order_relaxed)) {
+ launchctl_run(argv, 1);
+ usleep(100000);
+ }
+ return NULL;
+}
+
+void deviceinterfaced_control_stop(void)
+{
+ int status;
+
+ if (control_state == DEVICEINTERFACED_KILLING) {
+ atomic_store_explicit(&stop_killer, 1, memory_order_relaxed);
+ status = thread_join(killer_thread);
+ if (status != 0) {
+ logger(LL_ERROR, "Could not stop " DEVICEINTERFACED_LABEL " termination loop: %s.\n", strerror(status));
+ return;
+ }
+ killer_thread = THREAD_T_NULL;
+ control_state = DEVICEINTERFACED_NEEDS_KICKSTART;
+ }
+
+ if (control_state == DEVICEINTERFACED_BOOTED_OUT) {
+ char *const bootstrap_argv[] = {
+ "bootstrap", "system", DEVICEINTERFACED_PLIST, NULL
+ };
+ status = launchctl_run(bootstrap_argv, 1);
+ if (status != 0) {
+ log_launchctl_failure("bootstrap", status);
+ logger(LL_ERROR, "Run 'sudo launchctl bootstrap system " DEVICEINTERFACED_PLIST "' to load it.\n");
+ return;
+ }
+ control_state = DEVICEINTERFACED_NEEDS_KICKSTART;
+ }
+
+ if (control_state == DEVICEINTERFACED_NEEDS_KICKSTART) {
+ char *const kickstart_argv[] = {
+ "kickstart", DEVICEINTERFACED_TARGET, NULL
+ };
+ status = launchctl_run(kickstart_argv, 1);
+ if (status != 0) {
+ log_launchctl_failure("kickstart", status);
+ logger(LL_ERROR, "Run 'sudo launchctl kickstart " DEVICEINTERFACED_TARGET "' to start it.\n");
+ return;
+ }
+ control_state = DEVICEINTERFACED_INACTIVE;
+ logger(LL_INFO, "Restored " DEVICEINTERFACED_LABEL ".\n");
+ }
+}
+
+int deviceinterfaced_control_start(void)
+{
+ char *const bootout_argv[] = {
+ "bootout", DEVICEINTERFACED_TARGET, NULL
+ };
+ int loaded;
+ int status;
+
+ if (control_state != DEVICEINTERFACED_INACTIVE) {
+ return 0;
+ }
+
+ if (!cleanup_registered) {
+ if (atexit(deviceinterfaced_control_stop) != 0) {
+ logger(LL_ERROR, "Could not register " DEVICEINTERFACED_LABEL " cleanup.\n");
+ return -1;
+ }
+ cleanup_registered = 1;
+ }
+
+ status = service_is_loaded(&loaded);
+ if (status < 0) {
+ log_launchctl_failure("query", status);
+ return -1;
+ }
+ if (!loaded) {
+ logger(LL_INFO, DEVICEINTERFACED_LABEL " is not loaded.\n");
+ return 0;
+ }
+
+ status = launchctl_run(bootout_argv, 1);
+ if (status == 0) {
+ control_state = DEVICEINTERFACED_BOOTED_OUT;
+ status = wait_for_service_to_unload();
+ if (status < 0) {
+ if (status == -ETIMEDOUT) {
+ logger(LL_ERROR, "Timed out waiting for " DEVICEINTERFACED_LABEL " to stop.\n");
+ } else {
+ log_launchctl_failure("query", status);
+ }
+ return -1;
+ }
+ logger(LL_INFO, "Booted out " DEVICEINTERFACED_LABEL " for the duration of the restore.\n");
+ return 0;
+ }
+ if (status < 0) {
+ log_launchctl_failure("boot out", status);
+ return -1;
+ }
+
+ logger(LL_WARNING, "Could not boot out " DEVICEINTERFACED_LABEL " (launchctl exit status %d); terminating it while the restore runs.\n", status);
+ atomic_store_explicit(&stop_killer, 0, memory_order_relaxed);
+ status = thread_new(&killer_thread, kill_service, NULL);
+ if (status != 0) {
+ logger(LL_ERROR, "Could not start " DEVICEINTERFACED_LABEL " termination loop: %s.\n", strerror(status));
+ return -1;
+ }
+ control_state = DEVICEINTERFACED_KILLING;
+ return 0;
+}
diff --git a/src/deviceinterfaced.h b/src/deviceinterfaced.h
new file mode 100644
index 0000000..c8623ad
--- /dev/null
+++ b/src/deviceinterfaced.h
@@ -0,0 +1,28 @@
+/*
+ * deviceinterfaced.h
+ * Control deviceinterfaced while accessing restore devices on macOS
+ *
+ * Copyright (c) 2026 libimobiledevice contributors. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef DEVICEINTERFACED_H
+#define DEVICEINTERFACED_H
+
+int deviceinterfaced_control_start(void);
+void deviceinterfaced_control_stop(void);
+
+#endif
diff --git a/src/idevicerestore.c b/src/idevicerestore.c
index 04070a1..11d7ec4 100644
--- a/src/idevicerestore.c
+++ b/src/idevicerestore.c
@@ -61,6 +61,10 @@
#include "locking.h"
+#ifdef __APPLE__
+#include "deviceinterfaced.h"
+#endif
+
#define VERSION_XML "version.xml"
#ifndef IDEVICERESTORE_NOMAIN
@@ -91,6 +95,9 @@ static struct option longopts[] = {
{ "ignore-errors", no_argument, NULL, 1 },
{ "variant", required_argument, NULL, 2 },
{ "logfile", required_argument, NULL, 3 },
+#ifdef __APPLE__
+ { "exclusive-usb", no_argument, NULL, 4 },
+#endif
{ NULL, 0, NULL, 0 }
};
@@ -101,6 +108,14 @@ static void usage(int argc, char* argv[], int err)
#else
#define PWN_FLAG_LINE ""
#endif
+#ifdef __APPLE__
+#define EXCLUSIVE_USB_ACCESS_FLAG_LINE \
+ " --exclusive-usb Keep deviceinterfaced from claiming the USB device\n" \
+ " during the restore (may prompt for your password\n" \
+ " via sudo)\n"
+#else
+#define EXCLUSIVE_USB_ACCESS_FLAG_LINE ""
+#endif
char* name = strrchr(argv[0], '/');
fprintf((err) ? stderr : stdout,
"Usage: %s [OPTIONS] PATH\n" \
@@ -141,6 +156,7 @@ static void usage(int argc, char* argv[], int err)
" -v, --version Print version information\n" \
"\n" \
"Advanced/experimental options:\n"
+ EXCLUSIVE_USB_ACCESS_FLAG_LINE \
" -c, --custom Restore with a custom firmware (requires bootrom exploit)\n" \
" -s, --server URL Override default signing server request URL\n" \
" -x, --exclude Exclude nor/baseband upgrade (legacy devices)\n" \
@@ -1777,6 +1793,9 @@ int main(int argc, char* argv[])
int ipsw_info = 0;
int result = 0;
const char* logfile = NULL;
+#ifdef __APPLE__
+ int exclusive_usb_access = 0;
+#endif
logger_set_print_func(tty_print);
@@ -1978,6 +1997,12 @@ int main(int argc, char* argv[])
logfile = optarg;
break;
+#ifdef __APPLE__
+ case 4:
+ exclusive_usb_access = 1;
+ break;
+#endif
+
default:
usage(argc, argv, 1);
return EXIT_FAILURE;
@@ -2036,12 +2061,26 @@ int main(int argc, char* argv[])
curl_global_init(CURL_GLOBAL_ALL);
+#ifdef __APPLE__
+ if (exclusive_usb_access && deviceinterfaced_control_start() < 0) {
+ idevicerestore_client_free(client);
+ curl_global_cleanup();
+ return EXIT_FAILURE;
+ }
+#endif
+
client->flags |= FLAG_IN_PROGRESS;
result = idevicerestore_start(client);
client->flags &= ~FLAG_IN_PROGRESS;
idevicerestore_client_free(client);
+#ifdef __APPLE__
+ if (exclusive_usb_access) {
+ deviceinterfaced_control_stop();
+ }
+#endif
+
curl_global_cleanup();
return (result == 0) ? EXIT_SUCCESS : EXIT_FAILURE;