Skip to content

DeveloperDiskImage Mounting fails on legacy devices #1897

Description

@prosch88

What happened?

Trying to mount a DeveloperDiskImage (14.4) on an iPhone 6 Plus raises a "connection lost" error.

error_mount.txt

Older pmd3 versions (pre asyncio) are working fine.
Auto-mount fails too here.

How can we reproduce this?

pymobiledevice3 mounter mount-developer DeveloperDiskImage.dmg DeveloperDiskImage.dmg.signature

Environment

HostOS: Manjaro Linux
Python: 3.11.15 (venv)
pymobiledevice3: 11.2.5
Device: iPhone7,1
Target OS: iOS 12.5.7

Relevant log output

pymobiledevice3 mounter mount-developer DeveloperDiskImage.dmg DeveloperDiskImage.dmg.signature
╭─────────────────────────────────────── Traceback (most recent call last) ────────────────────────────────────────╮
│ /home/prosch/.pyenv/versions/3.11.15/lib/python3.11/site-packages/typer_injector/_inject.py:154 in wrapper       │
│                                                                                                                  │
│ /home/prosch/.pyenv/versions/3.11.15/lib/python3.11/site-packages/typer_injector/_inject.py:125 in               │
│ invoke_with_dependencies                                                                                         │
│                                                                                                                  │
│ /home/prosch/.pyenv/versions/3.11.15/lib/python3.11/site-packages/pymobiledevice3/cli/mounter.py:30 in           │
│ catch_function                                                                                                   │
│                                                                                                                  │
│    27 def catch_errors(func: Callable[..., Any]) -> Callable[..., Any]:                                          │
│    28 │   def catch_function(*args: Any, **kwargs: Any) -> Any:                                                  │
│    29 │   │   try:                                                                                               │
│ ❱  30 │   │   │   return func(*args, **kwargs)                                                                   │
│    31 │   │   except AlreadyMountedError:                                                                        │
│    32 │   │   │   logger.error("Given image was already mounted")                                                │
│    33 │   │   except UnsupportedCommandError:                                                                    │
│                                                                                                                  │
│ /home/prosch/.pyenv/versions/3.11.15/lib/python3.11/site-packages/pymobiledevice3/cli/cli_common.py:230 in       │
│ wrapper                                                                                                          │
│                                                                                                                  │
│   227 │   def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:                                                   │
│   228 │   │   task = cli_loop.create_task(func(*args, **kwargs))                                                 │
│   229 │   │   try:                                                                                               │
│ ❱ 230 │   │   │   return cli_loop.run_until_complete(task)                                                       │
│   231 │   │   except KeyboardInterrupt:                                                                          │
│   232 │   │   │   # Ensure graceful coroutine finalization on Ctrl-C; otherwise Python                           │
│   233 │   │   │   # may report "coroutine ignored GeneratorExit" during GC shutdown.                             │
│                                                                                                                  │
│ /home/prosch/.pyenv/versions/3.11.15/lib/python3.11/asyncio/base_events.py:654 in run_until_complete             │
│                                                                                                                  │
│    651 │   │   if not future.done():                                                                             │
│    652 │   │   │   raise RuntimeError('Event loop stopped before Future completed.')                             │
│    653 │   │                                                                                                     │
│ ❱  654 │   │   return future.result()                                                                            │
│    655 │                                                                                                         │
│    656 │   def stop(self):                                                                                       │
│    657 │   │   """Stop running the event loop.                                                                   │
│                                                                                                                  │
│ /home/prosch/.pyenv/versions/3.11.15/lib/python3.11/site-packages/pymobiledevice3/cli/mounter.py:120 in          │
│ mounter_mount_developer                                                                                          │
│                                                                                                                  │
│   117 │   ],                                                                                                     │
│   118 ) -> None:                                                                                                 │
│   119 │   """mount developer image"""
│ ❱ 120 │   await DeveloperDiskImageMounter(lockdown=service_provider).mount(image, signature)                     │
│   121 │   logger.info("Developer image mounted successfully")                                                    │
│   122                                                                                                            │
│   123                                                                                                            │
│                                                                                                                  │
│ /home/prosch/.pyenv/versions/3.11.15/lib/python3.11/site-packages/pymobiledevice3/services/mobile_image_mounter. │
│ py:340 in mount                                                                                                  │
│                                                                                                                  │
│   337 │   │                                                                                                      │
│   338 │   │   image_bytes = Path(image).read_bytes()                                                             │
│   339 │   │   signature_bytes = Path(signature).read_bytes()                                                     │
│ ❱ 340 │   │   await self.upload_image(self.IMAGE_TYPE, image_bytes, signature_bytes)                             │
│   341 │   │   await self.mount_image(self.IMAGE_TYPE, signature_bytes)                                           │
│   342 │                                                                                                          │
│   343 │   async def umount(self) -> None:                                                                        │
│                                                                                                                  │
│ /home/prosch/.pyenv/versions/3.11.15/lib/python3.11/site-packages/pymobiledevice3/services/mobile_image_mounter. │
│ py:200 in upload_image                                                                                           │
│                                                                                                                  │
│   197 │   │   :raises PyMobileDevice3Exception: If the device does not acknowledge or does not complete          │
│   198 │   │   │   the transfer.                                                                                  │
│   199 │   │   """                                                                                                │
│ ❱ 200 │   │   result = await self.service.send_recv_plist({                                                      │
│   201 │   │   │   "Command": "ReceiveBytes",                                                                     │
│   202 │   │   │   "ImageType": image_type,                                                                       │
│   203 │   │   │   "ImageSize": len(image),                                                                       │
│                                                                                                                  │
│ /home/prosch/.pyenv/versions/3.11.15/lib/python3.11/site-packages/pymobiledevice3/service_connection.py:315 in   │
│ send_recv_plist                                                                                                  │
│                                                                                                                  │
│   312 │   │   :return: The received plist as a dictionary.                                                       │
│   313 │   │   """
│   314 │   │   # Every request/response service behind lockdownd answers with a dictionary-rooted plist.          │
│ ❱ 315 │   │   return cast(PlistDict, parse_plist(await self.send_recv_prefixed(build_plist(data, endianity,      │
│       fmt), endianity)))                                                                                         │
│   316 │                                                                                                          │
│   317 │   def recvall_sync(self, size: int) -> bytes:                                                            │
│   318 │   │   """                                                                                                │
│                                                                                                                  │
│ /home/prosch/.pyenv/versions/3.11.15/lib/python3.11/site-packages/pymobiledevice3/service_connection.py:296 in   │
│ send_recv_prefixed                                                                                               │
│                                                                                                                  │
│   293 │   │   :return: The raw response bytes (may be empty).                                                    │
│   294 │   │   """
│   295 │   │   async with self._transaction_lock:                                                                 │
│ ❱ 296 │   │   │   await self.sendall(data)                                                                       │
│   297 │   │   │   return await self.recv_prefixed(endianity=endianity)                                           │
│   298 │                                                                                                          │
│   299 │   async def send_recv_plist(                                                                             │
│                                                                                                                  │
│ /home/prosch/.pyenv/versions/3.11.15/lib/python3.11/site-packages/pymobiledevice3/service_connection.py:418 in   │
│ sendall                                                                                                          │
│                                                                                                                  │
│   415 │   │   _, writer = await self._ensure_started()                                                           │
│   416 │   │   try:                                                                                               │
│   417 │   │   │   writer.write(payload)                                                                          │
│ ❱ 418 │   │   │   await writer.drain()                                                                           │
│   419 │   │   except ssl.SSLEOFError as e:                                                                       │
│   420 │   │   │   raise ConnectionTerminatedError from e                                                         │
│   421                                                                                                            │
│                                                                                                                  │
│ /home/prosch/.pyenv/versions/3.11.15/lib/python3.11/asyncio/streams.py:392 in drain                              │
│                                                                                                                  │
│   389 │   │   │   # in a loop would never call connection_lost(), so it                                          │
│   390 │   │   │   # would not see an error when the socket is closed.                                            │
│   391 │   │   │   await sleep(0)                                                                                 │
│ ❱ 392 │   │   await self._protocol._drain_helper()                                                               │
│   393 │                                                                                                          │
│   394 │   async def start_tls(self, sslcontext, *,                                                               │
│   395 │   │   │   │   │   │   server_hostname=None,                                                              │
│                                                                                                                  │
│ /home/prosch/.pyenv/versions/3.11.15/lib/python3.11/asyncio/streams.py:166 in _drain_helper                      │
│                                                                                                                  │
│   163 │                                                                                                          │
│   164 │   async def _drain_helper(self):                                                                         │
│   165 │   │   if self._connection_lost:                                                                          │
│ ❱ 166 │   │   │   raise ConnectionResetError('Connection lost')                                                  │
│   167 │   │   if not self._paused:                                                                               │
│   168 │   │   │   return                                                                                         │
│   169 │   │   waiter = self._loop.create_future()                                                                │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
ConnectionResetError: Connection lost

Search

  • I searched for existing open and closed issues before opening this

AI assistance

none

Code of Conduct

  • I agree to follow this project's Code of Conduct

Additional context

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtriageWaiting collaborator triage

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions