Skip to content

Commit

Permalink
Rewrite shared_region_root_dir patch & re-enable it on 16+
Browse files Browse the repository at this point in the history
  • Loading branch information
Siguza committed Jun 10, 2023
1 parent d0f788c commit 6b87493
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 95 deletions.
128 changes: 128 additions & 0 deletions checkra1n/kpf/bindfs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* pongoOS - https://checkra.in
*
* Copyright (C) 2019-2023 checkra1n team
*
* This file is part of pongoOS.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/

#include "kpf.h"
#include <kerninfo.h>
#include <pongo.h>
#include <xnu/xnu.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

static bool do_bind_mounts = false;

static bool kpf_shared_region_root_dir_callback(struct xnu_pf_patch *patch, uint32_t *opcode_stream)
{
static bool found_shared_region_root_dir = false;
if(found_shared_region_root_dir)
{
panic("kpf_shared_region_root_dir: Found twice");
}

opcode_stream[4] = 0xeb00001f; // cmp x0, x0
found_shared_region_root_dir = true;

puts("KPF: Found shared region root dir");
return true;
}

static void kpf_shared_region_root_dir_patch(xnu_pf_patchset_t *xnu_text_exec_patchset)
{
// Doing bind mounts means the shared cache is not on the volume mounted at /.
// XNU has a check to require that though, so we patch that out.
//
// 0xfffffff007dcabc8 001140f9 ldr x0, [x8, 0x20]
// 0xfffffff007dcabcc 086c40f9 ldr x8, [x0, 0xd8]
// 0xfffffff007dcabd0 e91b40f9 ldr x9, [sp, 0x30]
// 0xfffffff007dcabd4 296d40f9 ldr x9, [x9, 0xd8]
// 0xfffffff007dcabd8 1f0109eb cmp x8, x9
// 0xfffffff007dcabdc 210f0054 b.ne 0xfffffff007dcadc0
//
// /x 001040f9086c40f9e90340f9296d40f91f0109eb00000054:1ffcffffffffffffff03c0ffffffffffffffffff1e0000ff
uint64_t matches[] =
{
0xf9401000, // ldr x0, [x*, 0x20]
0xf9406c08, // ldr x8, [x0, 0xd8]
0xf94003e9, // ldr x9, [sp, 0x...]
0xf9406d29, // ldr x9, [x9, 0xd8]
0xeb09011f, // cmp x8, x9
0x54000000, // b.{eq|ne} 0x...
};
uint64_t masks[] =
{
0xfffffc1f,
0xffffffff,
0xffc003ff,
0xffffffff,
0xffffffff,
0xff00001e,
};
xnu_pf_maskmatch(xnu_text_exec_patchset, "shared_region_root_dir", matches, masks, sizeof(matches)/sizeof(uint64_t), false, (void*)kpf_shared_region_root_dir_callback);
}

static void kpf_bindfs_patches(xnu_pf_patchset_t *xnu_text_exec_patchset)
{
// iOS 15.0: Union mounts no longer work
if(do_bind_mounts)
{
kpf_shared_region_root_dir_patch(xnu_text_exec_patchset);
}
}

static void kpf_bindfs_init(struct mach_header_64 *hdr, xnu_pf_range_t *cstring, checkrain_option_t kpf_flags, checkrain_option_t checkra1n_flags)
{
const char rootvp_string[] = "rootvp not authenticated after mounting";
const char *rootvp_string_match = memmem(cstring->cacheable_base, cstring->size, rootvp_string, sizeof(rootvp_string) - 1); // don't match null byte

#ifdef DEV_BUILD
// 15.0 beta 1 onwards
if((rootvp_string_match != NULL) != (gKernelVersion.darwinMajor >= 21))
{
panic("rootvp_auth panic doesn't match expected Darwin version");
}
#endif

do_bind_mounts = rootvp_string_match != NULL;
}

static void kpf_bindfs_finish(struct mach_header_64 *hdr, checkrain_option_t *checkra1n_flags)
{
// Signal to ramdisk that we can't have union mounts
checkrain_set_option(*checkra1n_flags, checkrain_option_bind_mount, do_bind_mounts);
}

kpf_component_t kpf_bindfs =
{
.init = kpf_bindfs_init,
.finish = kpf_bindfs_finish,
.patches =
{
{ NULL, "__TEXT_EXEC", "__text", XNU_PF_ACCESS_32BIT, kpf_bindfs_patches },
{},
},
};
1 change: 1 addition & 0 deletions checkra1n/kpf/kpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ extern struct kernel_version

/********** ********** ********** ********** ********** Components ********** ********** ********** ********** **********/

extern kpf_component_t kpf_bindfs;
extern kpf_component_t kpf_developer_mode;
extern kpf_component_t kpf_dyld;
extern kpf_component_t kpf_launch_constraints;
Expand Down
96 changes: 1 addition & 95 deletions checkra1n/kpf/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1529,89 +1529,6 @@ void kpf_fsctl_dev_by_role(xnu_pf_patchset_t *patchset)
xnu_pf_maskmatch(patchset, "vnode_open_close", vn_matches, vn_masks, sizeof(vn_masks)/sizeof(uint64_t), true, (void*)vnode_open_close_callback);
}

static bool found_shared_region_root_dir = false;
bool shared_region_root_dir_callback(struct xnu_pf_patch *patch, uint32_t *opcode_stream) {
// Make sure regs match
if(opcode_stream[0] != opcode_stream[3] || (opcode_stream[2] & 0x1f) != (opcode_stream[5] & 0x1f))
{
DEVLOG("shared_region_root_dir: reg mismatch");
return false;
}
uint32_t reg = opcode_stream[5] & 0x1f;
// There's a cmp+b.cond afterwards, but there can be a load from stack in between,
// so we find that dynamically.
uint32_t *cmp = find_next_insn(opcode_stream + 6, 2, 0xeb00001f, 0xffe0fc1f);
if(!cmp || (((cmp[0] >> 5) & 0x1f) != reg && ((cmp[0] >> 16) & 0x1f) != reg) ||
(cmp[1] & 0xff00001e) != 0x54000000 // Mask out lowest bit to catch both b.eq and b.ne
)
{
DEVLOG("shared_region_root_dir: Failed to find cmp/b.cond");
return false;
}
// Now that we're sure this is a match, check that we haven't matched already
if(found_shared_region_root_dir)
{
panic("Multiple matches for shared_region_root_dir");
}
// The thing we found isn't what we actually want to patch though.
// The check right here is fine, but there's one further down that's
// much harder to identify, so we use this as a landmark.
uint32_t *ldr1 = find_next_insn(cmp + 2, 120, 0xf9406c00, 0xfffffc00); // ldr xN, [xM, 0xd8]
if(!ldr1 || ((*ldr1 >> 5) & 0x1f) == 0x1f) // no stack loads
{
panic_at(cmp, "shared_region_root_dir: Failed to find ldr1");
}
uint32_t *ldr2 = find_next_insn(ldr1 + 1, 2, 0xf9406c00, 0xfffffc00); // ldr xN, [xM, 0xd8]
if(!ldr2 || ((*ldr2 >> 5) & 0x1f) == 0x1f) // no stack loads
{
panic_at(ldr1, "shared_region_root_dir: Failed to find ldr2");
}
size_t idx = 2;
uint32_t reg1 = (*ldr1 & 0x1f),
reg2 = (*ldr2 & 0x1f),
cmp2 = ldr2[1],
bcnd = ldr2[idx];
if(cmp2 != (0xeb00001f | (reg1 << 16) | (reg2 << 5)) && cmp2 != (0xeb00001f | (reg1 << 5) | (reg2 << 16)))
{
panic_at(ldr2 + 1, "shared_region_root_dir: Bad cmp");
}
if((bcnd & 0xbfc003f0) == 0xb94003f0) // ldr x{16-31}, [sp, ...]
{
bcnd = ldr2[++idx];
}
if((bcnd & 0xff00001e) != 0x54000000) // Mask out lowest bit to catch both b.eq and b.ne
{
panic_at(ldr2 + idx, "shared_region_root_dir: Bad b.cond");
}
ldr2[1] = 0xeb00001f; // cmp x0, x0
found_shared_region_root_dir = true;
return true;
}

void kpf_shared_region_root_dir_patch(xnu_pf_patchset_t* patchset) {
// Doing bind mounts means the shared cache is not on the volume mounted at /.
// XNU has a check to require that though, so we patch that out.
// This finds the inlined call to vm_shared_region_root_dir and subsequent NULL check.
// /x e00310aa00000094100e40f9e00310aa00000094100000b4:fffff0ff000000fc10fefffffffff0ff000000fc100000ff
uint64_t matches[] = {
0xaa1003e0, // mov x0, x{16-31}
0x94000000, // bl IOLockLock
0xf9400210, // ldr x{16-31}, [x{16-31}, .*]
0xaa1003e0, // mov x0, x{16-31}
0x94000000, // bl IOLockUnlock
0xb4000010, // cbz x{16-31}, ...
};
uint64_t masks[] = {
0xfff0ffff,
0xfc000000,
0xffc00210,
0xfff0ffff,
0xfc000000,
0xff000010,
};
xnu_pf_maskmatch(patchset, "shared_region_root_dir", matches, masks, sizeof(masks)/sizeof(uint64_t), true, (void*)shared_region_root_dir_callback);
}

#if 0
bool root_livefs_callback(struct xnu_pf_patch *patch, uint32_t *opcode_stream) {
puts("KPF: Found root_livefs");
Expand Down Expand Up @@ -1717,6 +1634,7 @@ static void kpf_cmd(const char *cmd, char *args)

kpf_component_t* const kpf_components[] =
{
&kpf_bindfs,
&kpf_developer_mode,
&kpf_dyld,
&kpf_launch_constraints,
Expand Down Expand Up @@ -1790,23 +1708,17 @@ static void kpf_cmd(const char *cmd, char *args)

const char rootvp_string[] = "rootvp not authenticated after mounting";
const char *rootvp_string_match = memmem(text_cstring_range->cacheable_base, text_cstring_range->size, rootvp_string, sizeof(rootvp_string) - 1);
const char cryptex_string[] = "/private/preboot/Cryptexes";
const char *cryptex_string_match = memmem(text_cstring_range->cacheable_base, text_cstring_range->size, cryptex_string, sizeof(cryptex_string));
#if 0
const char livefs_string[] = "Rooting from the live fs of a sealed volume is not allowed on a RELEASE build";
const char *livefs_string_match = apfs_text_cstring_range ? memmem(apfs_text_cstring_range->cacheable_base, apfs_text_cstring_range->size, livefs_string, sizeof(livefs_string) - 1) : NULL;
if(!livefs_string_match) livefs_string_match = memmem(text_cstring_range->cacheable_base, text_cstring_range->size, livefs_string, sizeof(livefs_string) - 1);
#endif

#ifdef DEV_BUILD
// 15.0 beta 1 onwards
if((rootvp_string_match != NULL) != (gKernelVersion.darwinMajor >= 21)) panic("rootvp_auth panic doesn't match expected Darwin version");
#if 0
// 15.0 beta 1 onwards, but only iOS/iPadOS
if((livefs_string_match != NULL) != (gKernelVersion.darwinMajor >= 21 && xnu_platform() == PLATFORM_IOS)) panic("livefs panic doesn't match expected Darwin version");
#endif
// 16.0 beta 1 onwards
if((cryptex_string_match != NULL) != (gKernelVersion.darwinMajor >= 22)) panic("Cryptex presence doesn't match expected Darwin version");
#endif

for(size_t i = 0; i < sizeof(kpf_components)/sizeof(kpf_components[0]); ++i)
Expand Down Expand Up @@ -1967,12 +1879,6 @@ static void kpf_cmd(const char *cmd, char *args)
{
kpf_fsctl_dev_by_role(xnu_text_exec_patchset);
kpf_vnop_rootvp_auth_patch(xnu_text_exec_patchset);
if(!cryptex_string_match)
{
kpf_shared_region_root_dir_patch(xnu_text_exec_patchset);
}
// Signal to ramdisk that we can't have union mounts
checkra1n_flags |= checkrain_option_bind_mount;
}

xnu_pf_emit(xnu_text_exec_patchset);
Expand Down

0 comments on commit 6b87493

Please sign in to comment.