worse_query Escape

How forcequitOS/worse_query obtains sandbox extensions from containermanagerd on iOS 26.0–26.7.2 and iOS 27.0–27.3 Beta 1.

  • Repo: forcequitOS/worse_query
  • Stars: 587
  • Mechanism: MCM class-7 symlink
  • Flags: 0x8100000000
Macro view of a high-density processor board and memory controller assembly
Container inode and sandbox token enforcement at the system level

How worse_query works (and why bad_query was patched)

Published by Taj C (forcequitOS/worse_query, 587 stars, GPL-3.0) and documented in detail by murk-sus/kamui-escape and kolbicz/WQFiles, worse_query is a container-level sandbox escape targeting Apple’s containermanagerd (MobileContainerManager / MCM).

Its predecessor, bad_query, passed relative path traversal (../../../../../../../..) inside container_query_operation_set_part_domain on MCM class 13 (`SystemGroup`). In iOS 27.2, Apple patched bad_query in two places:

  1. MCMXPCMessageQuery explicitly rejects any query whose partDomain contains .. (logging Rejecting query with invalid partDomain).
  2. MCMFileManager realPathForURL:ifChildOfURL: resolves symlinks and rejects targets outside the container when partDomain is non-empty.

Apple’s iOS 27.2 fix validated partDomain, but it did not validate the container’s part directory itself when partDomain is left empty:

  1. Inside the app’s own container (MCM class 2), iOS 27.2 sandbox rules block rename() on Library/Caches, Library/Preferences, Documents, and tmp with EPERM (errno 1).
  2. However, inside an App Group container (MCM class 7), the part directory <app group>/Library/Caches remains writable and renameable by the app.
  3. worse_query backs up <app group>/Library/Caches, replaces it with a symbolic link pointing to the target MCM container path, and sends an XPC query to containermanagerd with:
    • class = 7 (App Group)
    • group_identifiers = <app’s own signed App Group>
    • part = 3 (Library/Caches)
    • partDomain = "" (empty string: bypassing the 27.2 partDomain checks)
    • flags = 0x8100000000 (return path + issue read/write sandbox extension type 3)
  4. containermanagerd constructs <app group>/Library/Caches, resolves the symlink to the target container, and issues a valid sandbox extension token for the resolved path.
  5. The app calls sandbox_extension_consume() to gain read/write access, then immediately removes the symlink and restores the original Library/Caches folder.
Escape StrategyOriginApp Group Required?Behavior Across iOS 26 & iOS 27
Strategy 1: Class 13 + partDomain traversalLegacy bad_queryNoiOS 27.0 / 27.0.1 builds where .. in partDomain was not yet rejected. Patched in iOS 27.2.
Strategy 2: Class 7 part-directory symlinkPrimary worse_queryYes (Mandatory)iOS 26.0–26.7.2 and iOS 27.0–27.3 Beta 1. Replaces <app group>/Library/Caches with a symlink and queries MCM class 7, part 3, empty partDomain.
Strategy 3: Class 13 direct MG queryDirect SystemGroup queryNoReturns the container path on some builds, but issues no sandbox token unless the caller already holds the system entitlement.
Strategy 4: Class 12 geod + traversalDaemon fallbackNoOlder pre-27.2 builds only.

Reachable MCM container paths

Filesystem Path PatternMCM ClassOS AvailabilityAccess LevelWhat Lives Inside
/var/mobile/Containers/Data/Application/*MCM Class 2iOS 26.0–26.7.2 & iOS 27.xRead / Write (`mobile`)Every installed user and system app’s Documents, Library, Caches, and tmp directories.
/var/mobile/Containers/Shared/AppGroup/*MCM Class 7iOS 26.0–26.7.2 & iOS 27.xRead / Write (`mobile`)Shared App Group containers used by apps and their extensions/widgets.
/var/mobile/Containers/Data/PluginKitPlugin/*MCM Class 4iOS 26.0–26.7.2 & iOS 27.xRead / Write (`mobile`)App extensions (Share extensions, widgets, notification service extensions).
/var/mobile/Containers/Data/InternalDaemon/*MCM Class 10iOS 26.0–26.7.2 & iOS 27.xRead / Write (`mobile`)User-level background daemons including lsd’s LaunchServices database container.
/var/containers/Shared/SystemGroup/*MCM Class 13iOS 27.x onlyRead / Write (`mobile` caches)Holds systemgroup.com.apple.mobilegestaltcache and Supervision configuration profiles.
/var/containers/Data/SystemMCM Class 12iOS 27.x onlyDirectory listing onlyContents inside System Data are owned by root and protected by POSIX DAC.

Container enumeration: LaunchServices DB vs fsgetpath inode scan

Because iOS sandbox rules prevent listing the parent directories (such as /var/mobile/Containers/Data/Application) directly, tools need a way to discover each container’s UUID folder name:

  • fsgetpath brute-force inode scan (used by KAMUI, Regitela’s pathleak, and WQFiles v0.1–v0.7): Calls fsgetpath() across inode numbers on the data volume until container directories are found. On a heavily used device, the first scan can take several minutes (and up to an hour on iOS 26).
  • LaunchServices database lookup (introduced in WQFiles v0.8): WQFiles locates lsd’s container among recent inodes within seconds, reads the system LaunchServices database directly, and extracts the exact container paths, bundle IDs, and localized names for all App Data, App Groups, and Extension Data without waiting for a full-volume inode scan.
Desktop workstation with dual Apple displays and keyboard for plist inspection
Desktop workstation for MobileGestalt backup inspection and SQLite comparison
Side view of a MacBook Air notebook computer used for USB IPA signing and sideloading
macOS USB sideloading host for AltStore, SideStore, Impactor, and iLoader
Compact developer desk setup with laptop and external monitor
Local signing and WebCrypto SHA-256 verification workspace

Common questions

Can worse_query read or write root-owned files in /var or /System?

No. worse_query only causes containermanagerd to issue sandbox tokens for MCM containers, and standard POSIX permissions (DAC) still apply. Files owned by root inside /var/containers/Data/System cannot be modified by user mobile.

Why does WQFiles keep a Caches backup during token requests?

Because the class-7 technique temporarily swaps /Library/Caches with a symlink during the XPC query, both WQFiles and KAMUI keep a recovery backup (such as Caches.bqtool) so an interrupted run restores the folder cleanly on next launch.