03 — iOS XNU Internals
Overview
iOS is the most security-focused general-purpose operating system deployed at consumer scale. It runs on over a billion active devices, handles payments, biometrics, health data, and real-time communication — all on hardware and software designed with the assumption that adversarial users will attempt to subvert every control. The iOS security model is a deep layering of hardware and software protections: from the first instruction executed at power-on, through every binary that executes, to the runtime permissions model that governs what each app can access.
At iOS's core is XNU — the same kernel that runs macOS — running on 64-bit ARM hardware (Apple Silicon A-series and M-series chips). What distinguishes iOS from macOS is not the kernel architecture but the security layers applied on top: mandatory codesigning, comprehensive sandboxing, hardware security through the Secure Enclave, and a runtime permission system that requires explicit user consent for sensitive resources. This document traces each layer from hardware boot to application execution.
Prerequisites
- XNU architecture: Mach + BSD hybrid kernel (see 02-operating-system-history/05-xnu-and-macos-history.md)
- ARM64 architecture basics (see 06-cpu-architecture/04-arm-architecture.md)
- Understanding of code signing and cryptographic certificates
- Familiarity with Apple Silicon (see 02-operating-system-history/05-xnu-and-macos-history.md)
- Basic understanding of sandboxing and MAC frameworks
iOS Security Layer Diagram
+------------------------------------------------------------------+
| USER-VISIBLE LAYER |
| TCC Prompts: "App wants to access Camera" [Allow] [Don't Allow]|
+------------------------------------------------------------------+
| APP LAYER |
| App Sandbox (container filesystem, Seatbelt MAC policy) |
| Entitlements (declared capabilities in signed binary) |
| App Store distribution (binary review, codesigning) |
+------------------------------------------------------------------+
| RUNTIME SECURITY LAYER |
| TCC daemon (Transparency Consent Control) |
| launchd (PID 1, service management, XPC) |
| Codesigning daemon (amfid, libmis) |
| MACF (Mandatory Access Control Framework) hook layer |
+------------------------------------------------------------------+
| XNU KERNEL LAYER |
| Codesigning enforcement (cs_validate_page at page fault) |
| W^X enforcement (write XOR execute, page table bits) |
| Sandbox kernel extension (hooks: open, connect, mach_msg, etc.) |
| Entitlement checks (IOKit, Mach trap handlers) |
| Memory management: no swap, aggressive OOM killer |
+------------------------------------------------------------------+
| HARDWARE SECURITY LAYER |
| Secure Enclave (SEP): biometrics, disk keys, Apple Pay keys |
| Secure Boot: each stage verified by Apple Root CA |
| Hardware AES engine: per-file encryption keys |
| Pointer Authentication Codes (PAC): ARM64e feature |
| Memory Tagging (Speculative isolation): A15+ hardware |
+------------------------------------------------------------------+
| HARDWARE |
| ARM64 SoC (A-series), NAND flash, Secure Element (NFC/payment) |
+------------------------------------------------------------------+
Secure Boot Chain
iOS implements a verified boot chain where each stage is cryptographically signed and verified before execution. No unsigned or incorrectly signed code executes at any boot stage.
Boot ROM
The Boot ROM (also called SecureROM or iBoot ROM) is read-only code burned into the Apple SoC during manufacturing. It is the first code that executes on power-on and is immutable — no software update can change it. The Boot ROM:
- Initializes CPU state and minimum hardware
- Generates the device's Unique ID (UID) key (derived from hardware fuses — unique per device, never leaves chip)
- Loads the next boot stage (LLB or iBoot1) from NAND
- Verifies the next stage's signature against Apple's Root CA certificate, which is hardcoded in the Boot ROM
If verification fails at any stage, the device enters Device Firmware Update (DFU) mode — a recovery state accessible via USB.
Boot Chain Verification
Power On
|
+---v-------------------+
| Boot ROM (read-only) | -> Verify iBoot signature (Apple Root CA)
| (in SoC, immutable) |
+---+-------------------+
|
| signature OK
v
+---+-------------------+
| iBoot / LLB | -> Verify XNU kernel signature
| (bootloader) | -> Verify devicetree, ramdisk, TrustCache
+---+-------------------+
|
| all signatures OK
v
+---+-------------------+
| XNU Kernel | -> Verify each binary as pages are loaded
| (iOS kernel) | -> via cs_validate_page at page fault time
+---+-------------------+
|
v
+---+-------------------+
| launchd (PID 1) | -> Signed, entitlements checked
| System daemons | -> All signed
| App processes | -> Signed + sandboxed
+---+-------------------+
Each step verifies the next. A compromised bootloader cannot load a valid kernel because the kernel signature is checked against Apple's CA, not the bootloader. A compromised kernel cannot load unsigned binaries because codesigning is enforced page-by-page. This chain means an attacker must compromise Apple's signing infrastructure to persistently run arbitrary code across reboots.
Codesigning
Every binary executable that runs on iOS must have a valid Apple-issued code signature. This includes system binaries, app binaries, frameworks, and dylibs. The signature covers:
- The code directory (CodeDirectory): A hash tree of the binary's pages. Each 4KB page has a SHA-256 hash stored in the CodeDirectory.
- The entitlements blob: The capability declarations for this binary, covered by the signature.
- The CMS signature: An RSA or EC signature over the CodeDirectory, issued by Apple's certificate authority.
At page load time (when a page fault occurs and a new page from a binary is mapped), XNU calls cs_validate_page() which:
1. Hashes the page content (SHA-256)
2. Compares against the stored hash in the CodeDirectory
3. Verifies the CodeDirectory signature chain
4. Kills the process if the page is invalid
This means even if an attacker can write to a file on disk, the modified page will be detected when it is mapped for execution.
Signing Identities
Three signing paths exist: - App Store: Developer submits signed binary → Apple re-signs with Apple Distribution certificate → App Store delivers Apple-signed binary to devices - Developer provisioning: Developer registers device UDID with Apple → Apple issues provisioning profile → Binary signed with developer certificate + provisioning profile → Installs only on registered devices - Enterprise provisioning: Organizations with Apple Developer Enterprise Program can sign binaries for internal distribution without App Store review (subject to volume and usage restrictions)
On macOS, Developer ID allows signing for direct distribution outside the App Store. iOS does not have an equivalent — all end-user binaries come through App Store or provisioning profiles.
W^X: Write XOR Execute
iOS enforces a strict W^X invariant at the hardware level:
- A memory page that is writable (has the write permission bit set) cannot simultaneously be executable
- A page that is executable cannot be simultaneously writable
- Attempting to make a page both writable and executable triggers a fault
This means an attacker who can write arbitrary data to memory cannot then execute that data. Classic stack buffer overflow shellcode injection is blocked at the hardware level.
Exception: JIT entitlement
JavaScript engines (and other JIT compilers) require executable writable memory to generate machine code at runtime. Apps with the dynamic-codesigning entitlement may use mprotect() to flip a page between writable and executable, but not simultaneously. The sequence is:
1. mmap(PROT_READ|PROT_WRITE) — allocate writable memory
2. Write JIT code to the page
3. mprotect(PROT_READ|PROT_EXEC) — make the page executable (removes write)
4. Execute the JIT code
5. To patch: mprotect(PROT_READ|PROT_WRITE) → write → mprotect(PROT_READ|PROT_EXEC)
Safari's JavaScriptCore holds this entitlement. Third-party browsers on iOS (Chrome, Firefox) must use Apple's WKWebView, which also has JIT capabilities via the entitlement held by the system WebContent process. Third-party browsers cannot use their own JIT engines without this entitlement.
Pointer Authentication Codes (PAC)
Starting with Apple A12 and ARM64e, Pointer Authentication Codes are used to sign pointers. Return addresses on the stack and function pointers in critical data structures are signed with a cryptographic key that is unique per process. The AUTIA/AUTIB instructions verify the signature before using the pointer. Forged or corrupted pointers cause a fault. This significantly raises the cost of ROP exploitation — gadget chains must carry valid PAC signatures.
iOS Sandboxing
Every third-party iOS app runs inside a Seatbelt sandbox. Seatbelt is Apple's MAC (Mandatory Access Control) framework — a kernel extension that intercepts and filters virtually every sensitive operation: file system access, network connections, Mach port lookups, device access.
Sandbox Architecture
App Process
|
| open("/var/mobile/Media/Photos/foo.jpg")
|
v
XNU VFS layer
|
| MAC hook: mac_vnode_check_open()
|
v
Sandbox kernel extension
+-> Evaluate sandbox policy for this process
+-> Is "/var/mobile/Media/Photos/foo.jpg" allowed?
|
[Policy: read-only access to photos via Photos framework only,
not direct file path access]
|
v
DENY -> EPERM returned to app
The sandbox policy is a compiled scheme-like policy language. Each app's sandbox profile defines:
- Filesystem access: App container directory (read/write), system frameworks (read only), no access to other app containers
- Network: Outbound network connections (if NSAppTransportSecurity policy permits), no raw sockets
- IPC: Which Mach bootstrap services the app may look up
- Device access: Camera, microphone — mediated through TCC, not direct device access
- System calls: Certain syscalls restricted or blocked entirely
Container Directory
Each app has a unique container directory at:
/var/mobile/Containers/Data/Application/<UUID>/
Documents/ -- user-visible documents
Library/ -- app data, caches, preferences
tmp/ -- temporary files
The app's binary lives in:
/var/containers/Bundle/Application/<UUID>/MyApp.app/
The container UUID changes on reinstall. No other app can access these directories. System services access them only via the sharing APIs (UIDocumentPickerViewController, share sheets).
Entitlements
Entitlements are capability tokens embedded in the signed binary's code signature. They declare what the app is allowed to do. The App Store review process checks entitlements against Apple's policy — requesting capabilities the app demonstrably doesn't need leads to rejection.
<!-- Entitlements in signed binary -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC ...>
<plist version="1.0">
<dict>
<key>application-identifier</key>
<string>TEAMID.com.example.myapp</string>
<key>com.apple.developer.healthkit</key>
<true/>
<key>aps-environment</key>
<string>production</string>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
The kernel checks entitlements in multiple places:
- IOKit user clients check for specific entitlements before allowing privileged device access
- Mach trap handlers (mach_vm_allocate for JIT) check for dynamic-codesigning
- The sandbox policy itself incorporates entitlement checks
TCC: Transparency Consent Control
TCC governs user-facing permission prompts for sensitive resources: camera, microphone, location, contacts, calendar, photos, health, and others. TCC operates as a daemon (tccd) that maintains a SQLite database at:
/var/mobile/Library/TCC/TCC.db -- user preferences
/private/var/db/TCC/TCC.db -- system-level
When an app first attempts to access a protected resource (e.g., AVCaptureDevice.requestAccess(for: .video)), TCC:
1. Checks the database for an existing decision
2. If no decision exists, prompts the user with the app's NSCameraUsageDescription string
3. Records the decision and informs the calling framework
The tccd daemon communicates via XPC (Mach ports + launchd). Critically, tccd itself has an entitlement that allows it to access all protected resources — this is the single privileged point of enforcement for the entire TCC system.
System apps (Phone, Messages, FaceTime) have pre-granted access to their required resources via entitlements without requiring user prompts.
Secure Enclave
The Secure Enclave Processor (SEP) is an embedded processor within Apple's SoC (present since A7 in iPhone 5s, 2013). It runs its own operating system (sepOS, an L4-based microkernel) and is completely isolated from the main application processor.
Main SoC:
+--------------------------------------------------+
| Application Processor (ARM64) |
| XNU kernel |
| | mailbox interface only |
| v |
| +------------------------+ |
| | Secure Enclave (SEP) | |
| | sepOS (L4 microkernel)| |
| | | |
| | - Biometric data | <- never leaves SEP |
| | (Face ID model) | |
| | - File encryption | <- derives keys, |
| | keys (class keys) | never exposes raw |
| | - UID derivation | |
| | - Apple Pay keys | |
| | - Secure token | |
| +------------------------+ |
+--------------------------------------------------+
Separate hardware:
AES encryption engine (hardware-accelerated, separate from SEP)
True Random Number Generator (TRNG)
What the SEP does: - Stores and processes biometric data (Touch ID fingerprint templates, Face ID neural network model and secure data). The templates never leave the SEP. - Derives the file system encryption class keys. When XNU needs to open a file of a given protection class, it sends a request to the SEP, which derives the key and performs decryption/encryption via the hardware AES engine. The key material never appears in main memory. - Manages the Secure Enclave UID (UID key), a unique key burned in at manufacture time, used in key derivation so that encrypted data cannot be decrypted on any other device even with the same passcode. - Processes Apple Pay transactions — the payment token generation happens in the SEP; XNU and the app only see the final token.
What XNU cannot do: XNU cannot read raw biometric templates, extract encryption keys from the SEP, or perform cryptographic operations using the UID key directly. Even with a full kernel compromise (ring-0 code execution), an attacker cannot extract keys from the SEP. This is why device passcode brute-forcing is bounded by the SEP's rate limiting (10 attempts allowed, then escalating delays/device wipe) — each attempt requires the SEP to derive and test the key.
iOS Memory Management
iOS has no swap space. When physical memory is low, iOS kills processes rather than swapping to disk. The design decision is driven by NAND flash performance characteristics (limited write cycles, latency) and security (swap data on disk can be forensically recovered; in-memory data protected by disk encryption is safer when the device is locked).
Memory Pressure and OOM
XNU's memory pressure system has three levels:
Normal Warning Critical
| | |
v v v
All OK UIApplication OS kills
delegate: background
didReceive processes
MemoryWarning (jetsam)
The jetsam subsystem (iOS-specific XNU code) maintains a priority queue of processes ordered by:
- Memory consumption
- Background vs. foreground status
- App state (suspended, background, foreground)
- Jetsam priority assigned at launch
Under memory pressure, jetsam kills suspended background processes first (highest memory, lowest priority), then background apps in background tasks, and eventually anything that isn't the foreground app. Apps receive didReceiveMemoryWarning to release caches before being killed.
NSObject Memory and ARC
ARC (Automatic Reference Counting), implemented by the Swift and Objective-C compilers with LLVM, inserts retain/release calls at compile time. Unlike garbage collection, ARC is deterministic — objects are deallocated exactly when their reference count reaches zero. This means:
- No GC pauses
- Predictable memory profile
- But: retain cycles can prevent deallocation (use weak references to break cycles)
OTA Delta Updates
iOS delivers updates as delta packages that modify only changed portions of the system. The update mechanism:
- Apple signs the update package (including a new
BuildManifest.plistwith hashes of all updated files) - The device downloads the delta
- The update process runs in a restricted environment, applies the delta
- On reboot, the Boot ROM verifies the new iBoot, iBoot verifies the new kernel, and the chain is re-verified
Nonce-based anti-downgrade: Apple's signing servers issue a nonce that is mixed into the personalized authorization of each signed component. Old SHSH blobs (saved device-specific authorization tokens for older firmware) cannot be replayed to downgrade iOS because Apple no longer signs older versions. This prevents attackers from downgrading to firmware with known vulnerabilities.
Jailbreaking: History and Kernel Exploit Path
A jailbreak is a privilege escalation that bypasses codesigning, removes sandbox restrictions, and installs a package manager to allow arbitrary code installation. Every successful jailbreak involves at minimum:
- Kernel exploit: A vulnerability in XNU that allows unprivileged code to gain kernel read/write access (ring-0)
- Codesigning bypass: Using kernel access to disable or bypass the codesigning check (typically by patching
cs_enforcement_disableor similar flags in memory, or by PPL bypass) - Sandbox escape: Removing the sandbox profile for the privileged process
- Persistence (for untethered jailbreaks): Surviving reboots by modifying the boot chain or exploiting a second vulnerability in the early boot process
Jailbreak Evolution
2007-2008: First iPhone jailbreaks — limited security model, relatively easy
2010: PDF exploit in MobileSafari (JailbreakMe.com)
-> Visit website -> kernel exploit via PDF parser bug
2012-2014: iOS security model matures — jailbreaks require kernel CVEs
2015: Pangu jailbreak — untethered, multiple CVEs chained
2018: iOS 12 security model — Processor Authorization Level (PAC)
introduced on A12, makes exploits harder
2020: checkm8 — Boot ROM vulnerability (unpatchable, tethered)
-> Works on all devices up to A11 (iPhone X), forever
-> Requires physical access and USB connection each reboot
2021-2023: iOS 15/16 — PAC + PPL make kernel exploitation very difficult
-> Few public jailbreaks; those that exist are semi-untethered
-> Require kernel exploit + PAC bypass + PPL bypass
checkm8 is notable: it exploits a bug in Apple's Boot ROM (SecureROM) that cannot be patched via software update because the Boot ROM is read-only hardware. However, it requires physical USB access and must be re-triggered after every reboot, limiting its threat model.
Debugging Notes
Crash Logs
iOS crash logs are stored at:
/var/mobile/Library/Logs/CrashReporter/ (device)
~/Library/Developer/Xcode/Devices/<UDID>/... (synced via Xcode)
Key fields in a crash log:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000018
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Exception Note: EXC_CORPSE_NOTIFY
Thread 0 Crashed::
0 libsystem_c.dylib 0x00000001ddc4e318 strlen + 8
1 CoreFoundation 0x00000001c9a1f3b8 ...
Symbolication
Crash logs contain unsymbolicated addresses (ASLR randomizes them). Xcode's Organizer or atos tool symbolicates using the dSYM file from the build:
xcrun atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
-l <load_address> <crash_address>
Sandbox Violations
Sandbox denials are logged to the system log:
deny(1) file-read-data /private/var/mobile/Library/AddressBook/...
Access via:
# On device (jailbroken or developer device):
idevicesyslog | grep -i "deny\|sandbox"
# In Xcode: Window -> Devices and Simulators -> Open Console
Security Implications
iOS's layered security model creates significant barriers for attackers:
- No persistent code execution without Boot ROM compromise: Software-only attacks don't survive reboots unless they also exploit the boot chain.
- Codesigning + PAC makes exploitation expensive: Bypassing both requires finding a kernel CVE, a PAC bypass (or a PAC-unprotected code path), and a PPL (Page Protection Layer) bypass — a three-stage chain that costs hundreds of thousands to millions of dollars on the zero-day market.
- SEP key protection: Even a full kernel compromise does not expose decryption keys or biometric data. Forensic extraction requires knowledge of the device passcode.
- TCC prevents silent data access: Malicious apps cannot silently access camera, microphone, or location without triggering the TCC prompt (which is displayed by a system process, not the app itself).
Performance Implications
- Codesigning overhead at page fault:
cs_validate_page()is called for each new executable page loaded. SHA-256 hashing of 4KB pages adds overhead to cold launches. Mitigated by caching validated signatures. - No swap = OOM kills: Apps that use too much memory are killed rather than swapped out. Large memory consumers (games, graphics editing apps) must actively manage memory, using
didReceiveMemoryWarningto release caches. - ARC vs. GC: ARC's synchronous retain/release is lower latency than GC for typical object lifetimes but has higher overhead for very high allocation rates (many small objects). Autorelease pools batch releases at runloop boundaries.
- Secure Enclave latency: SEP communication has measurable latency (~10-50ms) because it involves cross-processor IPC. File system decryption uses hardware AES directly (not SEP) for performance; the SEP derives the keys but the AES engine does the bulk encryption.
Failure Modes
- OOM kill during critical operation: A payment or data sync operation killed mid-way due to memory pressure. Apps must handle this with transactional logic and resume mechanisms.
- Codesigning certificate expiration: An enterprise distribution certificate expires — all apps signed with it immediately stop launching on managed devices until re-signed. This has caused high-profile outages (e.g., Facebook's enterprise certificate revocation in 2019).
- Entitlement mismatch after update: An app update submitted without a previously-held entitlement loses access to that capability immediately on update. Users see sudden loss of features.
- TCC database corruption: The TCC database has historically been corruptible, leading to apps either always being denied or bypassing prompts. Fixed by additional TCC daemon hardening in iOS 14+.
Exercises
- Using a development device with Xcode, create a simple app that calls
AVCaptureDevice.requestAccess(for: .video). Observe the TCC prompt, grant access, then revoke it in Settings → Privacy. Useinstruments -t "System Trace"to capture the XPC calls made totccdduring the permission request. - Using Xcode's Memory Report and Instruments → Allocations, measure baseline memory usage of an empty app vs. one that loads 100 UIImages. Trigger a simulated memory warning in Xcode and verify your
didReceiveMemoryWarningimplementation releases image caches. - Examine the entitlements of a system app:
ldid -e /usr/libexec/mobile_storage_proxy(on a jailbroken device or extracted IPSW). List every entitlement and categorize each as: security-critical, capability, identity, or operational. - Study the checkm8 vulnerability disclosure (https://checkm8.info/). Identify: (a) the vulnerability class, (b) why it is in the Boot ROM and therefore unpatchable, (c) why it requires physical USB access, (d) what attacker capabilities it provides that a kernel-only exploit would not.
- Build and run a test app that intentionally accesses a path outside its sandbox container (e.g.,
open("/var/mobile/Library/AddressBook/AddressBook.sqlitedb", O_RDONLY)). Observe the console log for the sandbox denial message. Identify the exact Seatbelt rule that generated the denial. - Using the XNU source code (https://github.com/apple-oss-distributions/xnu), find
cs_validate_page()inbsd/kern/ubc_subr.c. Trace the code path that is called when a page fault occurs for an executable page. Identify where the hash comparison happens and what action is taken on mismatch.
References
- Apple Platform Security Guide (2024): https://support.apple.com/guide/security/welcome/web
- Levin, J. (2017). *OS Internals Volume III: Security & Insecurity. NewOSXBook. (Most complete reference for iOS security internals)
- XNU Source Code: https://github.com/apple-oss-distributions/xnu
- Azimov, I. et al. "Examining the iOS Sandbox." Virus Bulletin 2011.
- Chen, D. et al. "iOS Passcode Bypass — Secure Enclave." DEF CON 2018.
- Ian Beer (Project Zero). "Attacking the XNU kernel." Various 2016–2022 writeups. https://googleprojectzero.blogspot.com/
- checkm8 vulnerability disclosure: https://checkm8.info/
- Lockdown Mode technical overview: https://support.apple.com/en-us/HT212650
- Apple Security Research Device Program: https://security.apple.com/research/device-program/
- Mandt, K. "Targeting the iOS Kernel." Argus Labs / Black Hat 2016.
- "iOS Security Guide" (historical versions): https://www.apple.com/business/docs/iOS_Security_Guide.pdf