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.
This commit is contained in:
1 parent
688fa88c65
commit
f6e4012f71
6 files changed
+249
-22
No files matched your search
@@ -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 <sys/xattr.h>
|
||||
#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;
|
||||
@@ -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 <sys/dirent.h>
|
||||
+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
|
||||
@@ -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 <mach/mach_types.h>
|
||||
#include <kern/task.h>
|
||||
#include <kern/zalloc.h>
|
||||
+#include <pexpert/pexpert.h>
|
||||
|
||||
#include <os/hash.h>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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('<I', module, 20)[0]
|
||||
if struct.unpack_from('<I', module)[0] != 2 or len(module) != 24 + entries * 24:
|
||||
raise SystemExit('The App cryptex trust module is not a complete version-2 module')
|
||||
cryptex_hashes = [module[24 + 24 * entry:24 + 24 * entry + 20] for entry in range(entries)]
|
||||
# DarwinBoot's ValidTrustCaches (and the kernel's binary search) need the hashes of a module in strictly ascending order.
|
||||
hashes = sorted([bytes.fromhex(trust['cdhash']), bytes.fromhex(trust['daemon_cdhash']), bytes.fromhex(trust['turnip_cdhash'])])
|
||||
if len(set(hashes)) != len(hashes):
|
||||
hashes = sorted({bytes.fromhex(trust['cdhash']), bytes.fromhex(trust['daemon_cdhash']),
|
||||
bytes.fromhex(trust['turnip_cdhash']), *cryptex_hashes})
|
||||
if len(hashes) != 3 + len(set(cryptex_hashes)):
|
||||
raise SystemExit('Duplicate CDHash')
|
||||
cdhash = hashes[0]
|
||||
identifier = uuid.uuid5(uuid.NAMESPACE_URL, 'org.blu-sharky.adreno-metal:' + ''.join(h.hex() for h in hashes))
|
||||
|
||||
Reference in new issue
Block a user