Review: security-policy stand-ins and the kernel surface the port adds
Scope and method
Read-only audit. Nothing was built or run; no file other than this one was written.
Files read in full (line counts):
| File | Lines |
|---|---|
| xnu/files/bsd/kern/kern_minimac.c | 369 |
| xnu/files/libkern/amfi/miniamfi.c | 264 |
| xnu/files/libkern/amfi/miniamfi_entitlements.cpp | 123 |
| xnu/files/iokit/Kernel/MiniAppleKeyStore.cpp | 335 |
| xnu/files/iokit/Kernel/GenericARMPE.cpp | 407 |
| xnu/files/iokit/Kernel/MiniIONetworkStack.cpp | 114 |
| xnu/files/iokit/Kernel/MiniDrivers.h | 21 |
| xnu/files/osfmk/arm64/loader_return.c (the loader-return code) | 597 |
| xnu/files/osfmk/arm64/user_exception_trace.c | 440 |
| xnu/hakios.patch (all 73 hunks' files, every hunk read) | 3242 |
Also read, for the surface questions: the devfs/sysctl registration and the user-facing handlers in bsd/dev/hakios.{input,audio,mic,camera,haptic,torch,sparse,virtio_blk,ufs,apfs_check,sound,speaker,guard}.c and bsd/miscfs/apfs/apfs_vfsops.c (the three sysctls). The modem files (mpss, qmi, nas, ipa, cell) were skipped as instructed.
Method. For each stub I traced who calls it and what the caller does with the answer. For each patch hunk I read the
unchanged Apple function around it. Apple's original code was read with git show c54f35ca...:<path> (read-only) from
the XNU checkout that lives in WSL (/home/shimoku/hakios/xnu-src/xnu-8796.121.2), because this Windows tree has no
xnu-src. Line numbers of the form "Apple file:N" are in that original file; "patch:N" are lines of
xnu/hakios.patch; everything else is a line of the file named.
Overlap: the sound/input/camera reviewers (docs/driver-review/sound.md, input.md, camera-haptic-torch.md) found P2 and P3's touch/audio/mic parts independently; I re-derived them because the question asked for every device node, and I cross-reference instead of repeating their detail. P1, P4, P5, P6, P7 are not in those documents.
Findings
| id | severity | confidence | where | one line |
|---|---|---|---|---|
| P1 | High | CONFIRMED | patch:518-606 (Apple kern_exec.c:3097, 3107-3111, 3132-3135, 3591, 3985) | any process becomes uid 0 with posix_spawn persona OVERRIDE: the entitlement check and the persona lookup were removed, nothing replaced them |
| P2 | High (effect on hardware), Medium (as a bug) | CONFIRMED trace / NEEDS-HARDWARE effect | hakios.audio.c:259-275, hakios.sound.c:1267/1272/1279, hakios.speaker.c:897/914 | unprivileged ioctl value wraps in below + extra: loudest level past sound_below, speaker guard blinded, watcher thread stuck (same as sound.md S-01/S-02) |
| P3 | Medium | CONFIRMED (mode) | hakios.input.c:213, hakios.audio.c:343, hakios.mic.c:1732, hakios.camera.c:4000, hakios.haptic.c:828, hakios.torch.c:575 | six device nodes are mode 0666: any process can read the microphone and camera, read and forge every touch and button |
| P4 | Medium | CONFIRMED (hunk) / PLAUSIBLE (reach) | patch:828-839 (kern_priv.c) | PRIV_NET_PRIVILEGED_SOCKET_DELEGATE returned 0 for every credential, before the MAC deny policies; the reason given in the comment is stale |
| P5 | Low | CONFIRMED | patch:853-859 (kern_sig.c), user_exception_trace.c:112-145 | user_abort_trace() prints from pthread_kill(SIGABRT) with no boot-arg gate, unlike its sibling |
| P6 | Low | PLAUSIBLE | patch:726-742 (Apple kern_exec.c:6639) | the "do not bump the pid version" exception is keyed on the process name xpcproxy only |
| P7 | Low | PLAUSIBLE | kern_minimac.c:169-224 | Sandbox request 4 answers for any pid, from memory the target process can rewrite |
Counts: 4 CONFIRMED (P1, P3, P4, P5), 1 CONFIRMED trace with NEEDS-HARDWARE effect (P2), 2 PLAUSIBLE (P6, P7).
Once P1 holds, every "root required" statement below (sysctls, raw storage nodes) stops being a boundary for a
process that can call posix_spawn; I note that where it matters.
P1. Any process can become root: persona check removed (High, CONFIRMED)
Where. patch:518-606, which changes spawn_validate_persona() and spawn_persona_adopt() in bsd/kern/kern_exec.c.
What Apple's code did (Apple kern_exec.c:3092-3166, called from 3591 and 3985):
spawn_validate_persona()returned EPERM unless the caller heldPERSONA_MGMT_ENTITLEMENT(3097-3099), and ESRCH if the persona did not exist (3107-3111).spawn_persona_adopt()then appliedpspi_uid,pspi_gidand the groups from the caller'sposix_spawnargument to the child's credential (3139-3159).- The sibling argument
posix_cred_infois guarded differently:kauth_cred_issuser()or EPERM (Apple kern_exec.c:3604-3607).
What the patch does. Both guards are deleted (patch:522-524, 543-547, 556-558). When the persona does not exist
(which is always the case here) the new branch at patch:566-606 sets the uid, gid and groups from the caller's request
on the child with proc_update_label() and returns 0.
Failure scenario. Any process, at any uid, with no entitlement and no signature, calls posix_spawn with a persona
attribute (posix_spawnattr_set_persona_np, flags OVERRIDE, posix_spawnattr_set_persona_uid_np(attr, 0)) for any
path. The child is created with uid 0, gid and groups of the caller's choosing. Nothing in the spawn path checks the
caller. The patch's own comment says this is what Sileo relies on to become root ("Sileo asks posix_spawn for persona
99 with OVERRIDE and uid 0 ... that is how it runs apt and dpkg"). The comment justifies the removal with "the
entitlement is one nobody carries here (nothing is signed, AMFI is a stub)".
Why that justification is out of date. miniamfi_entitlements.cpp now answers entitlement queries from the entitlements
in the program's own signature, IOCurrentTaskHasEntitlement() reaches it (iokit/Kernel/IOUserClient.cpp:1507,
iokit/bsddev/IOKitBSDInit.cpp:1495-1559), and the sibling hunk in the same file keeps the check for bindfs
(patch:964-973). So the entitlement route works now; the unconditional removal is not needed for Sileo, whose binary
can carry com.apple.private.persona-mgmt in an ldid signature.
Consequences. A root child can open /dev/rufs_* and /dev/rvblk0 (see the table below), set kern.exec_inject, call
reboot(2) (which now returns to the loader, see halt_all_cpus in patch:2199-2211), and so on.
Fix (text only):
spawn_validate_persona(struct _posix_spawn_persona_info *px_persona)
{
int error = 0;
struct persona *persona = NULL;
+ if (!IOCurrentTaskHasEntitlement(PERSONA_MGMT_ENTITLEMENT)) {
+ return EPERM;
+ }
if (px_persona->pspi_flags & POSIX_SPAWN_PERSONA_GROUPS) {
and sign Sileo (and anything else that must do this) with that entitlement. Keep the persona == NULL branch in
spawn_persona_adopt(), which is only reachable after the check.
P2. Unprivileged volume ioctl wraps uint32: loudest level, guard blinded, thread stuck (CONFIRMED trace, NEEDS-HARDWARE effect)
Same defect as sound.md S-01/S-02; traced again here because the node is part of the user-reachable surface.
Trace. HAKIOS_AUDIO_SET_VOLUME (hakios.audio.c:259-275, node mode 0666 at :343) stores any uint32 below
(volume_below = volume->below, :268) with no range test. The sound watcher (hakios.sound.c:1253-1279) computes
below + extra in uint32_t (default below is 240) and passes it to hakios_speaker_volume() and guard_level().
In hakios_speaker_volume() (hakios.speaker.c:897 and :914) target = a->volume_at + below wraps again.
With extra = 0xFFFFFF0F the sum is 0xFFFFFFFF:
guard_level(0xFFFFFFFF)isHAKIOS_GUARD_SILENT(hakios.sound.c:1214-1216,guard_asdefaults to UINT32_MAX), so in hakios.guard.c:160to_speaker = 0: no power limit, only the full-scale clip at :225.target = volume_at - 1.set_volume()clamps up tovolume_at(hakios.speaker.c:707-709) and stores the clamped value ina->volume, soa->volume == targetnever holds (:919). Thefor (;;)at :909 never ends (withstep_ms == 1the fault check at :928 is skipped), and the watcher thread never reacheshakios_speaker_stop().
Failure scenario (sound=4 boot-arg, phone): any process does the ioctl {below = 0xFFFFFF0F, muted = 0} and writes
non-silent frames. The amplifiers go to Android's loudest level, the guard computes zero speaker power, and the
amplifiers are never powered down. Whether Android's level without the guard damages the speakers is NEEDS-HARDWARE.
Fix: see sound.md S-01 (clamp below at the ioctl, saturating add in sound.c and speaker.c); and fix P3 for this node.
P3. Six device nodes are world read/write (Medium, CONFIRMED for the mode)
Every node the port creates, with mode and owner:
| Node | Created at | Mode, owner | What an unprivileged opener can do |
|---|---|---|---|
| /dev/hakios-touch | hakios.input.c:213 | 0666 root:wheel | read every touch sample (and consume them: one shared queue, touch_read pops); write forged DOWN/MOVE/UP and button events, including Lock/Home/Volume (touch_write, :184-200; only real-driver fingers count towards the five-finger exit, :87-107) |
| /dev/hakios-audio | hakios.audio.c:343 | 0666 root:wheel | inject audio into the speaker path; set or reset volume and mute (P2) |
| /dev/hakios-mic | hakios.mic.c:1732 | 0666 root:wheel | mic_dev_open (:1522) switches the DSP microphone path on; mic_dev_read returns live samples to any reader; two readers split the stream (no lock on ring_read, memory-safe) |
| /dev/hakios-camera | hakios.camera.c:4000 | 0666 root:wheel | camdev_read (:3803) starts the sensor and returns frames; camdev_write sets exposure, focus, sensor, and fills the "picture for the host" buffer that the loader hands out (keep_picture, :3906-3940, bounded by want <= keep_bytes) |
| /dev/hakios-haptic | hakios.haptic.c:828 | 0666 root:wheel | drive the vibration motor (haptic_write) |
| /dev/hakios-torch | hakios.torch.c:575 | 0666 root:wheel | light the flashlight (torch_write; level clamped to 1000, the driver has a 30 s limit) |
| /dev/ufs_* , /dev/rufs_* | hakios.ufs.c:1517, 1522 | 0640 root:operator | raw phone storage, read only (see below) |
| /dev/rvblk0, /dev/rrvblk0 | hakios.virtio_blk.c:516-518 | 0600 root:operator | QEMU disk image |
| /dev/rsp0 | hakios.sparse.c:270 | 0600 root:operator | part of an image, block device |
| /dev/md*, /dev/rmd* | Apple bsd/dev/memdev.c:686-697 (unchanged) | 0600 root:operator | RAM disks, including the second image added at patch:1713-1723 |
Can an unprivileged process read the phone's raw storage? Not directly: /dev/ufs_* and /dev/rufs_* are 0640 root:operator (owner root, group operator read-only; on iOS the group has only root in it), /dev/rvblk0 and /dev/rsp0 are 0600. With P1, any process can reach root and therefore all of them. The write path to /dev/rufs_darwin is bounded (see "Checked and found fine").
The code does no check of its own in any d_open (touch_open, audio_open, mic_dev_open, camdev_open,
haptic_open, torch_open); file mode is the only gate. Whether the iOS sandbox adds one is out of reach here (the
Sandbox is a stub that allows everything, kern_minimac.c:230-242).
Fix: create these nodes 0600 root:wheel, or 0660 with the group of the one daemon that uses each (backboardd for touch,
mediaserverd for audio, mic, camera, haptic, torch), or test an entitlement in d_open, for example in touch_open:
static int
touch_open(...)
{
- return 0;
+ return IOCurrentTaskHasEntitlement("com.apple.hakios.touch") || suser(kauth_cred_get(), NULL) == 0 ? 0 : EPERM;
}
P4. A privilege that was refused is now granted to everyone, before the deny policies (Medium)
Where. patch:828-839, priv_check_cred() in bsd/kern/kern_priv.c:
if (priv == PRIV_NET_PRIVILEGED_SOCKET_DELEGATE) { return 0; }
placed before mac_priv_check() (Apple kern_priv.c:89-94), so no MAC policy can deny it, and for every credential, not
just mobile.
What it enables. The privilege is the gate for socket delegation: so_set_effective_pid()/so_set_effective_uuid()
when the target is not the caller (Apple uipc_socket.c:8111-8117, :8235), socket_delegate() (Apple
uipc_syscalls.c:270-276), NECP client delegation (necp_client.c:6917, necp.c:6847), and the Skywalk delegate checks
(fsw.c:973, nx_netif.c:1575). With the hunk any process can attribute its sockets to any other pid and executable UUID,
so per-process network policy, statistics and accounting see the victim instead of the caller.
The stated reason is stale. The comment says no process carries an entitlement. Today miniamfi_entitlements.cpp reads the
entitlements from each program's signature, and kern_minimac.c:290 already maps this very privilege to
com.apple.private.network.socket-delegate; trustd, nsurlsessiond and mediaserverd carry it in their Apple signatures.
So the hunk is redundant and makes the stub's entry dead code. (kern_minimac.c:253-258 says the NECP refusal "costs
nothing"; with this hunk the macro is never refused, so that comment and this hunk cannot both describe the current
tree.)
Reach is PLAUSIBLE only because I did not run it: whether anything in this port enforces NECP policy per app was not examined.
Fix: delete the hunk. The entitlement path in minisandbox_priv_grant() grants the privilege to the daemons that hold
the entitlement.
P5. An unconditional kernel printf on a user-controlled path (Low, CONFIRMED)
patch:853-859 adds, in __pthread_kill():
if (signum == SIGABRT) { user_abort_trace(); }
user_abort_trace() (user_exception_trace.c:112-145) has no gate. Its sibling user_exception_trace() returns at once
unless the boot-arg systrace is set (:48); ustacks is boot-arg driven. The file's header says the facility is on
"with boot-arg systrace set".
Failure scenario. A process ignores SIGABRT (signal(SIGABRT, SIG_IGN)) and calls pthread_kill(pthread_self(), SIGABRT) in a loop. Each call does one printf of up to 24 return addresses, plus the process name (which the process
chooses: its executable's last path component, so it can embed newlines). That goes to the console (video console and
UART, which the loader comments call very slow), to the kernel message buffer, and from there into the loader's log
(which keeps the first 256 KB and the last 1 MB, loader_return.c:30-38): the log's evidence is pushed out and the CPUs
that printf are slowed. Not a memory-safety problem: the frame walk is bounded (24 frames, monotonic, copyin).
Fix:
void
user_abort_trace(void)
{
thread_t thread = current_thread();
+ if (!user_exception_trace_enabled) {
+ return;
+ }
P6. Pid-version bump skipped by process name only (Low, PLAUSIBLE)
patch:726-742 skips proc_setpidversion() in exec_handle_sugid() (Apple kern_exec.c:6639) when
strncmp(p->p_comm, "xpcproxy", ...) == 0. At that point p_comm is still the name of the image that is exec'ing
(set_proc_name() runs later, Apple kern_exec.c:1924, after exec_handle_sugid at 1581). So the exception applies to
any process whose comm is xpcproxy, for any exec it does, not only the exec into the service xpcproxy was spawned
for. A process inherits the comm through fork, and a user can name a program xpcproxy.
The pid version in the audit token is what makes a daemon that holds an audit token notice an exec; without the bump a process called xpcproxy can send an XPC message as itself, exec an entitled system binary, and the daemon's later entitlement check on the unchanged token resolves to the new image (the classic exec race). On this port the same effect is already cheaper through a self-written signature (the AMFI stub reads entitlements without validation), so the practical gain is small; but the hunk regresses a property the kernel otherwise keeps. Reach depends on daemon behaviour I did not examine, hence PLAUSIBLE.
Fix: also require the spawn that created the process to be launchd's, for example only skip when p->p_ppid == 1 and
the target is not xpcproxy itself, or mark xpcproxy's spawn with a posix_spawn flag that the kernel records in the
proc, and test that instead of the name.
P7. Sandbox request 4: container of any pid, read from memory the process owns (Low, PLAUSIBLE)
kern_minimac.c:147-225 answers sandbox_container_path_for_pid() for any args.pid the caller names (:173-176): no
check of the caller's credential, of its relation to the target, or of an entitlement. The answer is taken by scanning
the target's exec strings for CFFIXED_USER_HOME= in its live user stack (:196-222). That memory is writable by the
target after exec, and at exec time it is whatever environment the parent passed.
Failure scenario. Process A rewrites the CFFIXED_USER_HOME= string in its own stack (or execs itself with the
variable set). A daemon that asks the kernel "what is A's container" (the call exists for exactly that) gets the path A
chose, for example another application's data container, and may then read or write there on A's behalf. A different
process learns the container path of any other process. The kernel side is memory-safe: strnlen and s + len < end
bound the scan, len < args.size bounds the copyout, the map is referenced, and the page-rounded copy covers argslen.
The comment (kern_minimac.c:137-142) says the environment is where the container comes from, so this is by design; what the comment does not say is that the answer is exposed for other pids and that the source is mutable. Which user-space daemons use the answer for an access decision was not examined.
Fix, if it matters: cache the string in struct proc at exec time (the kernel already walks the strings in
exec_extract_strings) instead of re-reading the stack, and require kauth_cred_getuid equality or
PRIV_... for pid != caller.
Checked and found fine
Policy stand-ins
- AMFI (miniamfi.c):
vnode_check_signaturesets CS_SIGNED and, for ad hoc signatures, CS_PLATFORM_BINARY, and returns 0 (:45-62). That is stated at kern_minimac.c:11-17. The consequences I traced:task_conversion_eval_internal(Apple ipc_tt.c:2489) lets a platform binary resolve other platform binaries' task ports, andF_ASSERT_BG_ACCESS(Apple kern_descrip.c:5048) is platform-only; both are therefore open to any ad hoc signed program, which anyone can make. By design; noted so that nobody reads "platform" as a trust statement. - The trust-cache and OSEntitlements tables answer "not found" or "unsupported" everywhere except the three
...WithProcqueries, which read the program's signature (miniamfi.c:93-124); only theWithProcvariants are called by the kernel (IOKitBSDInit.cpp:1495/1526/1559, IOUserClient.cpp:1507). No stub answers "yes" for an entitlement the signature lacks; an absent signature has none (cs_entitlements_blob_getreturns NULL when CS_SIGNED is unset, Apple kern_cs.c:1725-1727). copy_entitlements(miniamfi_entitlements.cpp:32-64): rejectslength <= sizeof(CS_GenericBlob), copieslength - 8into a buffer of+1and terminates it. Notes, not findings: the plist is re-parsed on every query (no cache), and its size is bounded only by the code-signature blob limit.- dyld policy request 0x5a and boot-arg query 0x60 (miniamfi.c:89-111): fixed 8-byte copyout to the address the caller
gives;
copyinof exactly 16 bytes; everything else is ENOPOLICY. It answers "allow everything" to every process, including DYLD_* variables and interposing for restricted programs; that is the purpose (kern_minimac.c:64-69). - Sandbox (kern_minimac.c:230-242): every request except 4 returns 0 and writes nothing. A caller of an "issue extension" style request then reads an unwritten out buffer in user space; stated in the file's header.
minisandbox_priv_grant(:318-349): consulted only after the root test (Apple kern_priv.c:104-119); the entitlement is read from the current process and only whencred == kauth_cred_get(); PRIV_WORK_INTERVAL is granted to all (:325, needed by UIKit; it gatesWORK_INTERVAL_OPERATION_CREATE2, Apple sys_work_interval.c:71, and the effect of the work-interval classes needs a workload id, which I did not trace). The table maps privileges to entitlements that a user can write into their own signature (no validation: stated at miniamfi_entitlements.cpp:4-9), so for non-root these privileges are as strong as the signature a user chooses. Log lines are bounded (32 privileges).- AppleKeyStore (MiniAppleKeyStore.cpp): answers "no passcode, unlocked" for state; lock, unlock and
UnlockBagreturn success for any input, which is the right answer for a device with no passcode and, since no change-passcode selector exists (kIOReturnUnsupported, :314), no caller can set one. Wrap/unwrap are a reversible XOR (:173-181) and the file says so. Bounds: wrap needs1..64bytes intowrapped[72](:261-277); unwrap9..72intokey[64](:285-290); DER buffersder[128]hold 91 and 30 bytes;scalarOutputCountandstructureOutputSizeare checked before writes. The user client has no task check, but nothing it returns or does is secret or persistent. - Data protection: the APFS port implements no protection class and
apfs_vnop_ioctlreturns ENOTTY (apfs_vnops.c:1190-1197), soF_SETPROTECTIONCLASS-style callers get "unsupported", not success. - MiniIONetworkStack: a service, an openable user client,
setPropertiesrefuses (:85-94); the open logs the registry state; no state is shared. - GenericARMPE: CPU nubs are indexed only after
number >= MAX_CPUSis excluded (:234);reglength checked (:77);cpus[]is static; the thread-call lambda touches onlycpus[]and the topology. The device tree is the loader's.
Surface
- Every sysctl the port adds (grep over xnu/files; the modem files were not read):
- writable:
kern.exec_inject(root; see below),kern.hakios_switch_root(pid 1 and root, once, input< 512bytes, hakios.apfs_check.c:1753-1766),kern.apfs_synthetic(explicitsuser,newlen < MAXPATHLEN, NUL-terminated, apfs_vfsops.c:660-671),kern.apfs_stats(write zeroes the counters, root through the sysctl layer),kern.hakios.var_save,kern.hakios.var_busy,kern.hakios.ufs_native(sysctl_io_number, a small state machine, root). - read-only:
kern.hakios.version,kern.hakios.battery_*,kern.hakios.ufs_*counters,kern.hakios_memory(apfs_vfsops.c:474-478). - none uses CTLFLAG_ANYBODY (grep), and Apple's
sysctl_rootdemandsproc_suserfor any write to an OID without it (Apple kern_newsysctl.c:1737-1745). So all writes need uid 0, which P1 makes available to any process.
- writable:
kern.exec_inject(patch:625-677):SYSCTL_STRING(... exec_inject_conf, 512); Apple'ssysctl_io_stringrefusesnewlen >= 512and terminates atnewlen(kern_newsysctl.c:530-536), so the buffer always has a NUL. The parser advances at least one byte per iteration or exits at NUL; the append testused + 1 + liblen < sizeleaves room for the:and the NUL;inject_envis 512 bytes, the same size as the buffer; matching is on the file's basename with an exact length compare. An unprivileged user can only get the library into a process they run themselves by naming it; that gives them nothing they lack. The variable is added after the caller's own environment, so a caller-suppliedDYLD_INSERT_LIBRARIEScomes first.- bindfs exemption (patch:964-973): the entitlement test remains (and can now be satisfied from a signature); the extra
pid == 1clause only matters to a process that is pid 1, which stays launchd after init execs it. Mounting still needs root. - Raw-storage write path (hakios.ufs.c:566-625, 823-857): block aligned, only the one partition selected by the
ufs_write=1boot-arg, fromUFS_WRITE_FIRSTon,offsetandlenchecked against the partition size in uint64, the character open of that partition clears FWRITE before the open check but the node mode (0640, owner root) still decides who may open it for writing, and one writer at a time through thevar_busyclaim. - Camera, mic, audio, haptic, torch handlers: every
uiomovelength is bounded by the destination size (audio_writechunks to the stack buffer;mic_dev_readn <= RING_FRAMES;camdev_readn <= ring_frame_bytes;haptic_write256 samples;torch_writeclamps to 1000); exposure and focus values are clamped before they reach registers (exposure_limits:3306,af_move:3039). - User-visible patch hunks, regression check:
mdevrw(patch:311-321) keeps the original treatment of a negative offset (root-only, Apple's behaviour);km.cTIOCSCTTY refusal only affects the console; work-interval cases 6-8 (patch:924-939) return 0 only for CoreAudio threads, otherwise the old ENOTSUP; vm_unix.c shared-region hunk (patch:1442-1462) adds an accept branch for a volume mounted directly inside Preboot's Cryptexes (root-only mount; theva_uid == 0owner check at Apple vm_unix.c:2475 still applies earlier) and keepsvnode_putbalanced in all three arms;percpu_foreach_secondaryrewrite is integer arithmetic with the same bounds;halt_all_cpusandkdp_machine_reboot_typecallloader_return()first, which returns at once if no loader stub exists; thesystrace,applog,regtrace,exec_tracehooks are boot-arg gated and bounded (copyinstrinto 160- and 32-byte buffers, at most 6 mib words);startup.cdeclaresminimac_policy_initinside#if CONFIG_VNGUARDwhile the call is outside it (a RELEASE configuration without vnguard would not compile; DEVELOPMENT builds are unaffected). - Loader return (loader_return.c, start.s hunk patch:2392-2631): the stub address and the PA-VA offset are read from
loader_stub_slot, which the loader patches in the kernel image and nothing user-reachable writes;loader_jumprejectst0szoutside 25..33 or a stub that does not fit under it, soloader_l1[stub >> 30]is below 512; the ring arithmetic keeps every index belowLOADER_LOG_SIZE; the early exception vectors branch to the loader only while VBAR holds a physical address and spin as before when no stub was patched. The kernel does not check the slot's magic or that the stub lies outside the kernel, and it acts onloader_live.stopas soon as it is non-zero; both are writable only by the loader or by whoever has kernel memory. The loader in turn trusts theloader_recording/loader_soundphysical addresses and lengths (at most 1 GB) that the kernel writes; the only user-influenced writer is the camera'skeep_picture, which stores the address of its own buffer. Minor:loader_return_enteredis tested and set without an atomic, so two processors reachingloader_jumptogether (a panic and the five-finger exit) could both pass it;psci_smp_stop_others()runs first and I did not trace its handling of that.
Not checked
- IOKit user clients of MiniIOSurface.cpp, MiniIOMobileFramebuffer.cpp, hakios.gpu.cpp, hakios.wlan.cpp, hakios.battery.cpp (external methods, shared memory): outside the file list given to me.
- bsd/miscfs/ramfs and apfs vnode operations: how ownership, setuid bits and
accessare enforced there (ramfs storesva_mode & ALLPERMS, ramfs.c:152, 1032). - What iOS user space does with the stub answers: whether cfprefsd, lsd or containermanagerd use P7's container path
for an access decision; whether dyld honours DYLD_* for setuid programs given the allow-all AMFI answer; whether this
port ships any setuid-root program; whether
task_for_pidis otherwise gated for ad hoc signed callers. - Behaviour on hardware: P2's effect on the speakers, whether the Sandbox stub's
0for unknown requests produces usable values inlibsystem_sandbox. - gic_v3_irq.c, psci_smp.c, generic_arm64_support.c, minicrypto, the cellular files, xnuloader's USB side.
- The scheduler hunks for slower cores (sched_prim.c, machine_routines.c): read, not analysed for correctness; they are policy, not privilege.