From bb64dc0b1c52be6ffa9985166a3135887d8a5819 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Mon, 5 Jan 2026 12:00:11 -0700 Subject: [PATCH] Implement lockdown enter recovery --- ffi/src/lockdown.rs | 31 ++++++++++++++++++++++++++++++- idevice/src/services/lockdown.rs | 17 +++++++++++++++++ tools/src/lockdown.rs | 8 ++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/ffi/src/lockdown.rs b/ffi/src/lockdown.rs index 0912797..046dfa2 100644 --- a/ffi/src/lockdown.rs +++ b/ffi/src/lockdown.rs @@ -179,7 +179,7 @@ pub unsafe extern "C" fn lockdownd_get_value( domain: *const libc::c_char, out_plist: *mut plist_t, ) -> *mut IdeviceFfiError { - if out_plist.is_null() { + if client.is_null() || out_plist.is_null() { return ffi_err!(IdeviceError::FfiInvalidArg); } @@ -221,6 +221,35 @@ pub unsafe extern "C" fn lockdownd_get_value( } } +/// Tells the device to enter recovery mode +/// +/// # Arguments +/// * `client` - A valid LockdowndClient handle +/// +/// # Returns +/// An IdeviceFfiError on error, null on success +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lockdownd_enter_recovery( + client: *mut LockdowndClientHandle, +) -> *mut IdeviceFfiError { + if client.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + let res: Result<(), IdeviceError> = run_sync_local(async move { + let client_ref = unsafe { &mut (*client).0 }; + client_ref.enter_recovery().await + }); + + match res { + Ok(_) => null_mut(), + Err(e) => ffi_err!(e), + } +} + /// Frees a LockdowndClient handle /// /// # Arguments diff --git a/idevice/src/services/lockdown.rs b/idevice/src/services/lockdown.rs index a6a4ea5..34d88b9 100644 --- a/idevice/src/services/lockdown.rs +++ b/idevice/src/services/lockdown.rs @@ -326,6 +326,23 @@ impl LockdownClient { } } } + + /// Tell the device to enter recovery mode + pub async fn enter_recovery(&mut self) -> Result<(), IdeviceError> { + self.idevice + .send_plist(crate::plist!({ + "Request": "EnterRecovery" + })) + .await?; + + let res = self.idevice.read_plist().await?; + + if res.get("Request").and_then(|x| x.as_string()) == Some("EnterRecovery") { + Ok(()) + } else { + Err(IdeviceError::UnexpectedResponse) + } + } } impl From for LockdownClient { diff --git a/tools/src/lockdown.rs b/tools/src/lockdown.rs index 3d0af6d..42eb87c 100644 --- a/tools/src/lockdown.rs +++ b/tools/src/lockdown.rs @@ -29,6 +29,10 @@ pub fn register() -> JkCommand { .required(true), ), ) + .with_subcommand( + "recovery", + JkCommand::new().help("Tell the device to enter recovery mode"), + ) .with_flag( JkFlag::new("domain") .with_help("The domain to set/get in") @@ -86,6 +90,10 @@ pub async fn main(arguments: &CollectedArguments, provider: Box eprintln!("Error setting value: {e}"), } } + "recovery" => lockdown_client + .enter_recovery() + .await + .expect("Failed to enter recovery"), _ => unreachable!(), } }