From f6e4012f716cbd462f3cd85b0e4243d31c08985f Mon Sep 17 00:00:00 2001 From: bluseliu50 <265648257+bluseliu50@users.noreply.github.com> Date: Sun, 25 Oct 2026 22:53:57 +0800 Subject: [PATCH] fix(xnu): let Safari start its WebKit extensions from the OS cryptex Show the OS cryptex where iPadOS looks for it, under /private/preboot, and let a board option skip the launch constraints that refused App cryptex daemons and owner-trusted binaries. The App cryptex trust module joins the owner module of the trust bundle, and the lookup trace also records lsd. --- patches/xnu/native-lookup-trace.patch | 42 +++-- patches/xnu/zzyb-native-cryptex-os-view.patch | 176 ++++++++++++++++++ .../zzyc-owner-skip-launch-constraints.patch | 31 +++ profiles/arch/arm64/patches/xnu/series | 2 + scripts/build-xnu-4k-baseline.sh | 5 +- scripts/make-owner-trust-bundle.py | 15 +- 6 files changed, 249 insertions(+), 22 deletions(-) create mode 100644 patches/xnu/zzyb-native-cryptex-os-view.patch create mode 100644 patches/xnu/zzyc-owner-skip-launch-constraints.patch diff --git a/patches/xnu/native-lookup-trace.patch b/patches/xnu/native-lookup-trace.patch index 29f9514..bce9138 100644 --- a/patches/xnu/native-lookup-trace.patch +++ b/patches/xnu/native-lookup-trace.patch @@ -1,46 +1,52 @@ +diff --git a/bsd/vfs/vfs_lookup.c b/bsd/vfs/vfs_lookup.c +index 3db098f..16d6bbe 100644 --- a/bsd/vfs/vfs_lookup.c +++ b/bsd/vfs/vfs_lookup.c -@@ -100,6 +100,33 @@ +@@ -100,6 +100,47 @@ #if NAMEDRSRCFORK #include #endif +/* -+ * Board bring-up diagnostic: the distinct lookups that failed for MobileSafari (path and errno), read by the boot log. ++ * Board bring-up diagnostic: the distinct lookups that failed for MobileSafari (S) and lsd (L), errno and path (its last 96 characters ++ * when longer), read by the boot log. Localization folders, Trial and logging preferences are left out: they fail by design. + */ -+char native_lookup_log[96][112]; ++char native_lookup_log[160][112]; +volatile unsigned native_lookup_count; ++static bool ++native_lookup_has(const char *text, const char *part) ++{ ++ const size_t length = strlen(part); ++ for (; *text; ++text) { ++ if (strncmp(text, part, length) == 0) { ++ return true; ++ } ++ } ++ return false; ++} +static void +native_lookup_note(const char *path, int error) +{ + char name[MAXCOMLEN + 1]; + proc_selfname(name, sizeof(name)); -+ if (strcmp(name, "MobileSafari") != 0) { ++ const char tag = !strcmp(name, "MobileSafari") ? 'S' : !strcmp(name, "lsd") ? 'L' : 0; ++ if (!tag || native_lookup_has(path, ".lproj") || native_lookup_has(path, "/Trial/") || ++ native_lookup_has(path, "/Preferences/Logging/")) { + return; + } ++ const size_t length = strlen(path); + char entry[112]; -+ snprintf(entry, sizeof(entry), "e%d %s", error, path); -+ unsigned seen = native_lookup_count < 96 ? native_lookup_count : 96; ++ snprintf(entry, sizeof(entry), "%c e%d %s%s", tag, error, length > 96 ? "..." : "", length > 96 ? path + length - 96 : path); ++ unsigned seen = native_lookup_count < 160 ? native_lookup_count : 160; + for (unsigned i = 0; i < seen; ++i) { + if (strncmp(native_lookup_log[i], entry, sizeof(entry)) == 0) { + return; + } + } + unsigned slot = os_atomic_inc_orig(&native_lookup_count, relaxed); -+ if (slot < 96) { ++ if (slot < 160) { + strlcpy(native_lookup_log[slot], entry, sizeof(native_lookup_log[slot])); + } +} -+ /* * The minimum volfs-style pathname is 9. * Example: "/.vol/1/2" -@@ -526,6 +553,9 @@ - vnode_put(ndp->ni_vp); - } - error_out: -+ if (error && error != ERECYCLE && cnp->cn_pnbuf) { -+ native_lookup_note(cnp->cn_pnbuf, error); -+ } - if (startdir_with_usecount) { - vnode_rele(startdir_with_usecount); - startdir_with_usecount = NULLVP; diff --git a/patches/xnu/zzyb-native-cryptex-os-view.patch b/patches/xnu/zzyb-native-cryptex-os-view.patch new file mode 100644 index 0000000..272abdd --- /dev/null +++ b/patches/xnu/zzyb-native-cryptex-os-view.patch @@ -0,0 +1,176 @@ +diff --git a/bsd/kern/kern_exec.c b/bsd/kern/kern_exec.c +--- a/bsd/kern/kern_exec.c ++++ b/bsd/kern/kern_exec.c +@@ -7483,7 +7483,162 @@ + native_gpu_addon_written++; + } + at += (packed + 7) & ~7ULL; ++ } ++} ++/* The OS cryptex where iPadOS looks for it. The root volume's entries for cryptex content are links into /System/Cryptexes/OS, which ++ * leads to /private/preboot/Cryptexes/OS; WebKit's processes are ExtensionKit extensions reached that way ++ * (/System/Library/ExtensionKit/Extensions/WebContentExtension.appex and its Networking and GPU siblings), so with the path missing ++ * Safari could not start them and its pages never loaded. The SystemOS volume is already the upper layer of the root union: its ++ * /System and /usr are bound read-only over /private/preboot/Cryptexes/OS/{System,usr}, not as unions, so a lookup there stays on the ++ * SystemOS volume and never meets the root's links again. The two mount points are owner Data directories under ++ * /private/var/ngpu/preboot, shown over the root's empty /private/preboot as a read-only union. Never fatal; stage and status go to the ++ * boot log: 1 mount points made, 2 preboot shown, 3 System bound, 4 usr bound, 5 an extension of WebKit visible through the path. */ ++volatile int native_cryptex_os_stage = 0; ++volatile int native_cryptex_os_status = -1; ++static int ++native_cryptex_os_bind(proc_t p, user_addr_t scratch, const char *source, const char *point, int flags) ++{ ++ struct mount_args request = {.type = scratch + 0xa00, .path = scratch + 0xa20, .flags = MNT_RDONLY | flags, ++ .data = scratch + 0xb00}; ++ int32_t result = 0; ++ int error; ++ if ((error = copyout("bindfs", request.type, 7)) || (error = copyout(point, request.path, strlen(point) + 1)) || ++ (error = copyout(source, request.data, strlen(source) + 1))) { ++ return error; ++ } ++ native_bindfs_boot_mount = 1; ++ error = mount(p, &request, &result); ++ native_bindfs_boot_mount = 0; ++ return error; ++} ++static void ++native_cryptex_os_view(proc_t p, user_addr_t scratch) ++{ ++ static const char *const points[] = {"/private/var/ngpu/preboot/Cryptexes/OS/System/", "/private/var/ngpu/preboot/Cryptexes/OS/usr/"}; ++ vnode_t probe = NULLVP; ++ int error = 0; ++ for (size_t i = 0; i < sizeof(points) / sizeof(points[0]) && !error; ++i) { ++ error = native_gpu_addon_parents(p, scratch, points[i]); ++ } ++ if (error) { ++ native_cryptex_os_status = error; ++ return; ++ } ++ native_cryptex_os_stage = 1; ++ if ((error = native_cryptex_os_bind(p, scratch, "/private/var/ngpu/preboot", "/private/preboot", MNT_UNION))) { ++ native_cryptex_os_status = error; ++ return; ++ } ++ native_cryptex_os_stage = 2; ++ if ((error = native_cryptex_os_bind(p, scratch, "/System", "/private/preboot/Cryptexes/OS/System", 0))) { ++ native_cryptex_os_status = error; ++ return; ++ } ++ native_cryptex_os_stage = 3; ++ if ((error = native_cryptex_os_bind(p, scratch, "/usr", "/private/preboot/Cryptexes/OS/usr", 0))) { ++ native_cryptex_os_status = error; ++ return; ++ } ++ native_cryptex_os_stage = 4; ++ error = vnode_lookup("/System/Cryptexes/OS/System/Library/ExtensionKit/Extensions/WebContentExtension.appex/Info.plist", 0, &probe, ++ vfs_context_current()); ++ if (!error) { ++ vnode_put(probe); ++ native_cryptex_os_stage = 5; ++ } ++ native_cryptex_os_status = error; ++} ++/* The device answers MobileGestalt cached while the display gamut was P3: removed so they are worked out again from the device tree ++ * (now sRGB). 0 removed, ENOENT none there (already regenerated or never made). */ ++volatile int native_gestalt_cache_status = -1; ++static void ++native_gestalt_cache_drop(proc_t p, user_addr_t scratch) ++{ ++ static const char cache[] = "/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/" ++ "com.apple.MobileGestalt.plist"; ++ struct unlink_args request = {.path = scratch + 0xc00}; ++ int32_t result = 0; ++ int error = copyout(cache, request.path, sizeof(cache)); ++ if (!error) { ++ error = unlink(p, &request, &result); ++ } ++ native_gestalt_cache_status = error; ++} ++/* LaunchServices' database of this Data store was built while /System/Cryptexes/OS led nowhere, so it never registered what lies behind ++ * it (WebKit's extensions among them) and lsd does not register system content again on a later boot of the same OS build. Its files ++ * are removed once, so lsd builds the database again with the path in place; the marker file keeps later boots from repeating it. ++ * Status for the boot log: files removed, or -2 done before, or -errno. */ ++#include ++volatile int native_ls_store_status = -1; ++static void ++native_ls_store_drop(proc_t p, user_addr_t scratch) ++{ ++ static const char marker[] = "/private/var/ngpu/ls-store-rebuilt-1"; ++ static const char caches[] = "/private/var/mobile/Containers/Data/InternalDaemon/5C583048-0C53-456F-B110-BB12068B787F/Library/Caches"; ++ static const char prefix[] = "com.apple.LaunchServices-"; ++ vfs_context_t ctx = vfs_context_current(); ++ vnode_t vp = NULLVP; ++ if (!vnode_lookup(marker, 0, &vp, ctx)) { ++ vnode_put(vp); ++ native_ls_store_status = -2; ++ return; ++ } ++ int error = vnode_lookup(caches, 0, &vp, ctx); ++ if (error) { ++ native_ls_store_status = -error; ++ return; ++ } ++ char names[8][64]; ++ unsigned found = 0; ++ char *buffer = kalloc_data(4096, Z_WAITOK); ++ uio_t uio = buffer ? uio_create(1, 0, UIO_SYSSPACE, UIO_READ) : NULL; ++ int eof = 0; ++ while (uio && !eof && found < 8) { ++ uio_reset(uio, uio_offset(uio), UIO_SYSSPACE, UIO_READ); ++ uio_addiov(uio, CAST_USER_ADDR_T(buffer), 4096); ++ int entries = 0; ++ if ((error = VNOP_READDIR(vp, uio, 0, &eof, &entries, ctx))) { ++ break; ++ } ++ const size_t used = 4096 - (size_t)uio_resid(uio); ++ if (!used) { ++ break; ++ } ++ for (size_t at = 0; at + sizeof(struct dirent) - sizeof(((struct dirent *)0)->d_name) <= used && found < 8;) { ++ const struct dirent *entry = (const struct dirent *)(buffer + at); ++ if (!entry->d_reclen) { ++ break; ++ } ++ if (entry->d_namlen < sizeof(names[0]) && !strncmp(entry->d_name, prefix, sizeof(prefix) - 1)) { ++ memcpy(names[found], entry->d_name, entry->d_namlen); ++ names[found++][entry->d_namlen] = 0; ++ } ++ at += entry->d_reclen; ++ } ++ } ++ if (uio) { ++ uio_free(uio); ++ } ++ if (buffer) { ++ kfree_data(buffer, 4096); ++ } ++ vnode_put(vp); ++ if (error) { ++ native_ls_store_status = -error; ++ return; ++ } ++ int removed = 0; ++ char path[192]; ++ for (unsigned i = 0; i < found; ++i) { ++ snprintf(path, sizeof(path), "%s/%s", caches, names[i]); ++ struct unlink_args request = {.path = scratch + 0xc00}; ++ int32_t result = 0; ++ if (!copyout(path, request.path, strlen(path) + 1) && !unlink(p, &request, &result)) { ++ removed++; ++ } + } ++ (void)native_gpu_addon_write(marker, (const uint8_t *)"1\n", 2, 0644, ctx); ++ native_ls_store_status = removed; + } + static void + native_gpu_addon_prepare(proc_t p, user_addr_t scratch) +@@ -7512,6 +7667,9 @@ + "7570.iPadOS_17_Flat_Background-1024w-1366h@2x~ipad.ca/assets/J120.HEIC", + "/Library/Wallpaper/Collections/iPadOS_17~ipad.wallpaperCollection/Wallpapers/7570.iPadOS_17_Flat-1024w-1366h@2x~ipad.wallpaper/" + "7570.iPadOS_17_Flat_Background-1024w-1366h@2x~ipad.ca/J120.png", &native_gpu_addon3_stage, &native_gpu_addon3_status); ++ native_cryptex_os_view(p, scratch); ++ native_gestalt_cache_drop(p, scratch); ++ native_ls_store_drop(p, scratch); + } + + /* The deletable system apps (Weather, Notes, Calendar, ...) ship in the OS volume's /private/var/staged_system_apps. A restore copies diff --git a/patches/xnu/zzyc-owner-skip-launch-constraints.patch b/patches/xnu/zzyc-owner-skip-launch-constraints.patch new file mode 100644 index 0000000..dc2d458 --- /dev/null +++ b/patches/xnu/zzyc-owner-skip-launch-constraints.patch @@ -0,0 +1,31 @@ +diff --git a/security/mac_process.c b/security/mac_process.c +index b64da95..c548aa1 100644 +--- a/security/mac_process.c ++++ b/security/mac_process.c +@@ -77,6 +77,7 @@ + #include + #include + #include ++#include + + #include + +@@ -892,9 +893,18 @@ mac_proc_check_settid(proc_t curp, uid_t uid, gid_t gid) + return error; + } + ++// Board bring-up: a system service the original AMFI gates on a platform binary is refused with "Launch Constraint ++// Violation" when the binary is an App cryptex daemon or an owner-trusted one, although its signature and trust-cache ++// entry are valid: the constraint category of an owner or cryptex binary is not the platform one. The original release ++// AMFI ignores its own amfi_* boot arguments, so the check is cleared here when the board asks for it. An option of the ++// instrumented board only: the boot argument, not the default, decides. + int + mac_proc_check_launch_constraints(proc_t curp, struct image_params *imgp, os_reason_t *reasonp) + { ++ uint32_t skip = 0; ++ if (PE_parse_boot_argn("sheng-no-lc", &skip, sizeof(skip)) && skip != 0) { ++ return 0; ++ } + char *fatal_failure_desc = NULL; + size_t fatal_failure_desc_len = 0; + diff --git a/profiles/arch/arm64/patches/xnu/series b/profiles/arch/arm64/patches/xnu/series index 7c32f66..64a8df8 100644 --- a/profiles/arch/arm64/patches/xnu/series +++ b/profiles/arch/arm64/patches/xnu/series @@ -51,3 +51,5 @@ ../../../../../patches/xnu/zzx-pmap-sync-64bit-pa.patch ../../../../../patches/xnu/zzy-native-gpu-addon-mount.patch ../../../../../patches/xnu/zzy-staged-system-apps.patch +../../../../../patches/xnu/zzyb-native-cryptex-os-view.patch +../../../../../patches/xnu/zzyc-owner-skip-launch-constraints.patch diff --git a/scripts/build-xnu-4k-baseline.sh b/scripts/build-xnu-4k-baseline.sh index 7c49d9d..a036489 100755 --- a/scripts/build-xnu-4k-baseline.sh +++ b/scripts/build-xnu-4k-baseline.sh @@ -37,8 +37,9 @@ if [ -z "${UNIXNU_PATCH_LIST:-}" ]; then # Remove them first so earlier independent patches retain their idempotent checks. # The same holds for the boot-time GPU addon mount and the staged system apps copy (zzy), which edit the owner Data mount function # inside the systemos patch. Reverse newest first; the loop below re-applies them in name order. -for LATE_PATCH in "$ROOT/patches/xnu/zzz-boot-progress.patch" "$ROOT/patches/xnu/zzy-staged-system-apps.patch" \ - "$ROOT/patches/xnu/zzy-native-gpu-addon-mount.patch"; do +for LATE_PATCH in "$ROOT/patches/xnu/zzz-boot-progress.patch" "$ROOT/patches/xnu/zzyb-native-cryptex-os-view.patch" \ + "$ROOT/patches/xnu/zzy-staged-system-apps.patch" "$ROOT/patches/xnu/zzy-native-gpu-addon-mount.patch" \ + "$ROOT/patches/xnu/zzyc-owner-skip-launch-constraints.patch"; do if git -C .cache/xnu-work apply --reverse --check "$LATE_PATCH" >/dev/null 2>&1; then git -C .cache/xnu-work apply --reverse "$LATE_PATCH" fi diff --git a/scripts/make-owner-trust-bundle.py b/scripts/make-owner-trust-bundle.py index bb4ab68..f88ba3c 100644 --- a/scripts/make-owner-trust-bundle.py +++ b/scripts/make-owner-trust-bundle.py @@ -17,9 +17,20 @@ assert count == 2 offsets = struct.unpack_from('<2I', original, 4) originals = [original[offsets[0]:offsets[1]], original[offsets[1]:]] trust = json.loads((ROOT / 'build/adreno-metal-plugin/trust.json').read_text()) +# The App cryptex (Cryptex1,AppOS) runs from owner Data, so its own trust module's hashes must be trusted too: the bundle +# carries exactly one owner module followed by both original Apple modules, and the cryptex entries join the owner module. +cryptex_trust = ROOT / 'build/app-cryptex/app-trustcache.bin' +cryptex_hashes = [] +if cryptex_trust.is_file(): + module = cryptex_trust.read_bytes() + entries = struct.unpack_from('