From a2e9cbb5705b4d2a32482b1059bf35010ff5dc63 Mon Sep 17 00:00:00 2001 From: Siguza Date: Fri, 8 Sep 2023 22:00:21 +0200 Subject: [PATCH] Rewrite TZ driver, TZ0 unlock patch for all devices --- src/boot/patches.S | 193 +++++++++++++- src/boot/stage3.c | 138 ++++++++++ src/drivers/sep/sep.c | 20 +- src/drivers/tz/tz.c | 517 ++++++++++++++++++++++++++++-------- src/drivers/tz/tz.h | 17 +- src/drivers/tz/tz_private.h | 37 +++ src/dynamic/modload.c | 8 +- src/kernel/main_task.c | 1 + 8 files changed, 812 insertions(+), 119 deletions(-) create mode 100644 src/drivers/tz/tz_private.h diff --git a/src/boot/patches.S b/src/boot/patches.S index 9bd3ab5d..308357a4 100644 --- a/src/boot/patches.S +++ b/src/boot/patches.S @@ -461,7 +461,6 @@ In r2: We search for the pattern, then seek backwards for a "cbz w0, ..." with positive offset. If we find none within a short range, we skip this match, otherwise we replace that instruction with an unconditional branch. - */ sym fuse_jump @@ -514,3 +513,195 @@ sym fuse_jump bfi w8, w12, 0, 26 str w8, [x10] ret + + +// void antitrust(volatile void *boot_image) + +/* +Keep TrustZone unlocked. + +There are a total of 4 variants of this patch, and this is just one of them. +In the matrix below: + +- s2 = A10+ on iOS 14.0+. Stage2-only. +- XX = A7, any version, and A8-A9 on 10.x and lower. Pongo-only. +- YY = A9X, any version. Pongo-only. +- ZZ = The rest, this is us. The only patch that is shared between stage2 and Pongo. + +For A8-A9, ZZ could be used on iOS 8 and 10 as well, but not on iOS 9. +But XX works on those too, so we don't care. + ++--------+----+----+-----+----+-----+-----+------+----+-----+ +| | A7 | A8 | A8X | A9 | A9X | A10 | A10X | T2 | A11 | ++--------+----+----+-----+----+-----+-----+------+----+-----+ +| iOS 7 | XX | | | | | | | | | ++--------+----+----+-----+----+-----+-----+------+----+-----+ +| iOS 8 | XX | XX | XX | | | | | | | ++--------+----+----+-----+----+-----+-----+------+----+-----+ +| iOS 9 | XX | XX | XX | XX | YY | | | | | ++--------+----+----+-----+----+-----+-----+------+----+-----+ +| iOS 10 | XX | XX | XX | XX | YY | ZZ | ZZ | | | ++--------+----+----+-----+----+-----+-----+------+----+-----+ +| iOS 11 | XX | ZZ | ZZ | ZZ | YY | ZZ | ZZ | ZZ | ZZ | ++--------+----+----+-----+----+-----+-----+------+----+-----+ +| iOS 12 | XX | ZZ | ZZ | ZZ | YY | ZZ | ZZ | ZZ | ZZ | ++--------+----+----+-----+----+-----+-----+------+----+-----+ +| iOS 13 | | ZZ | ZZ | ZZ | YY | ZZ | ZZ | ZZ | ZZ | ++--------+----+----+-----+----+-----+-----+------+----+-----+ +| iOS 14 | | ZZ | ZZ | ZZ | YY | s2 | s2 | s2 | s2 | ++--------+----+----+-----+----+-----+-----+------+----+-----+ +| iOS 15 | | ZZ | ZZ | ZZ | YY | s2 | s2 | s2 | s2 | ++--------+----+----+-----+----+-----+-----+------+----+-----+ +| iOS 16 | | ZZ | | ZZ | YY | s2 | s2 | s2 | s2 | ++--------+----+----+-----+----+-----+-----+------+----+-----+ +| iOS 17 | | ZZ | | | | s2 | s2 | s2 | | ++--------+----+----+-----+----+-----+-----+------+----+-----+ + +We start by looking for these two instructions: + ++---------------------+ +| lsr xN, xN, 0xc | +| stur wN, [xM, -0xc] | ++---------------------+ + +Where N < 16 and M >= 16. + +In r2: + +/x 00fc4cd300421fb8:10feffff10feffff + +After that, we search for a "str wT, [xM]" (same base register, offset 0) within 20 instructions. +We expect one of the two following sequences there: + ++----------------+ +| str wT, [xM] | +| ldr wS, [xM] | +| tbz wS, 0, ... | ++----------------+ +| str wT, [xM] | +| bl ... | +| ldr wS, [xM] | +| tbz wS, 0, ... | ++----------------+ + +Where S < 16. + +Right after that, devices with TZ1 have another block with the same base register but offset 4: + ++-----------------+ +| str wT, [xM, 4] | +| ldr wS, [xM, 4] | +| tbz wS, 0, ... | ++-----------------+ +| str wT, [xM, 4] | +| bl ... | +| ldr wS, [xM, 4] | +| tbz wS, 0, ... | ++-----------------+ + +Again with S < 16. + +We NOP out the str, ldr and tbz in both blocks. + +*/ + +sym antitrust + mov x2, x0 // instr + mov w7, 0xfffffe10 + mov w8, 0xd34c0000 // lsr xN, xN, 0xc + movk w8, 0xfc00 + mov w9, 0xb81f0000 // stur wN, [xM, -0xc] + movk w9, 0x4200 + // Search for pattern +1: + ldr w3, [x2], 0x4 + and w4, w3, w7 + cmp w4, w8 + b.ne 1b + + bfi w9, w3, 0, 5 + ldr w3, [x2] + and w4, w3, 0xfffffe1f + cmp w4, w9 + b.ne 1b + + // Search for "str wT, [xM]" + and w7, w3, 0x3e0 + movk w7, 0xb900, lsl 16 // str wT, [xM] + add x3, x2, 0x50 +2: + ldr w5, [x2, 0x4]! + and w4, w5, 0xffffffe0 + cmp w4, w7 + b.eq 3f + cmp x2, x3 + b.lo 2b + b . // XXX: fail + +3: + // Patch 1st write + mov w10, 0xd5030000 // nop + movk w10, 0x201f + str w10, [x2] + + // Check for bl + ldr w3, [x2, 0x4]! + ubfx w4, w3, 26, 6 + cmp w4, 0x25 // bl + b.ne 4f + ldr w3, [x2, 0x4]! + +4: + // 1s load + orr w7, w7, 0x00400000 // str to ldr + and w4, w3, 0xfffffff0 + cmp w4, w7 + b.ne . // XXX: fail + // Patch 1st load + str w10, [x2] + + // 1s tbz + mov w8, 0x36000000 // tbz wS, 0, ... + bfi w8, w3, 0, 5 + ldr w3, [x2, 0x4]! + and w3, w3, 0xfffc001f + cmp w3, w8 + b.ne . // XXX: fail + // Patch 1st tbz + str w10, [x2] + + // Check for 2nd set of str, ldr, tbz + orr w5, w5, 0x400 // [xM] to [xM, 4] + ldr w3, [x2, 0x4]! + cmp w3, w5 + b.ne Ligma + // Patch 2nd write + str w10, [x2] + + // Check for bl + ldr w3, [x2, 0x4]! + ubfx w4, w3, 26, 6 + cmp w4, 0x25 // bl + b.ne 5f + ldr w3, [x2, 0x4]! + +5: + // 2nd load + orr w7, w7, 0x400 // [xM] to [xM, 4] + and w4, w3, 0xfffffff0 + cmp w4, w7 + b.ne . // XXX: fail + // Patch + str w10, [x2] + + // 2nd tbz + bfi w8, w3, 0, 5 + ldr w3, [x2, 0x4]! + and w3, w3, 0xfffc001f + cmp w3, w8 + b.ne . // XXX: fail + // Patch + str w10, [x2] + +Ligma: + ret diff --git a/src/boot/stage3.c b/src/boot/stage3.c index c68534e0..601110f7 100644 --- a/src/boot/stage3.c +++ b/src/boot/stage3.c @@ -39,6 +39,7 @@ void iorvbar_yeet(volatile void *boot_image) __asm__("iorvbar_yeet"); void aes_keygen(volatile void *boot_image) __asm__("aes_keygen"); void recfg_yoink(volatile void *boot_image) __asm__("recfg_yoink"); void fuse_jump(volatile void *boot_image) __asm__("fuse_jump"); +void antitrust(volatile void *boot_image) __asm__("antitrust"); extern uint8_t need_to_release_L3_SRAM; @@ -139,6 +140,139 @@ void patch_bootloader(void* boot_image) } } + // TrustZone patches. + // We have two of them here, see patches.S for a device/version matrix. + bool tz_done = false; + for(volatile uint32_t *p = boot_image, *end = (volatile uint32_t*)((uintptr_t)boot_image + SAFE_TEXT_SIZE); p < end; ++p) + { + uint32_t op1 = p[0], + op2 = p[1], + op3 = p[2]; + + // A7 (any version) and A8-A9 (iOS 10 and lower). + // We look for the following sequence: + // str wN, [xM] + // ldr wT, [xM] + // tbz wT, 0, ... + // str wN, [xM, 4] + // ldr wS, [xM, 4] + // tbz wS, 0, ... + // On iOS 7 specifically, there is an immediate generated in the middle, + // and thus the second load and store have no offset: + // str wN, [xM] + // ldr wT, [xM] + // tbz wT, 0, ... + // movz xM, 0x200000000 + // movk xM, 0x914 + // str wN, [xM] + // ldr wS, [xM] + // tbz wS, 0, ... + // We require that T and S are <16, and that the two tbz have positive offset. + // /x 000000b9000040b900000036000400b9000440b900000036:00fcffff10fcffff0000fcff00fcffff10fcffff0000fcff + // /x 000000b9000040b9000000364000c0d2802281f2000000b9000040b900000036:00fcffff10fcffff0000fcffe0ffffffe0ffffff00fcffff10fcffff0000fcff + if((op1 & 0xfffffc00) == 0xb9000000 && (op2 & 0xfffffff0) == ((op1 & 0x000003e0) | 0xb9400000) && (op3 & 0xfffc001f) == ((op2 & 0x0000001f) | 0x36000000)) + { + volatile uint32_t *one = p, + *two = p + 3; + uint32_t op4 = two[0], + op5 = two[1]; + uint32_t imm = 1; // 1 << 2 + if(op4 == (((op1 & 0x000003e0) >> 5) | 0xd2c00040) && op5 == (((op1 & 0x000003e0) >> 5) | 0xf2812280)) + { + two += 2; + op4 = two[0]; + op5 = two[1]; + imm = 0; + } + if(op4 == ((op1 & 0x000003ff) | (imm << 10) | 0xb9000000) && (op5 & 0xfffffff0) == ((op1 & 0x000003e0) | (imm << 10) | 0xb9400000) && (two[2] & 0xfffc001f) == ((op5 & 0x0000001f) | 0x36000000)) + { + // Nop them all out. + one[0] = 0xd503201f; + one[1] = 0xd503201f; + one[2] = 0xd503201f; + two[0] = 0xd503201f; + two[1] = 0xd503201f; + two[2] = 0xd503201f; + tz_done = true; + break; + } + } + // A9X patch, any version. + // The reason A9X is separate is because it has two sets of TZ registers + // rather than just one, which makes for *very* different codegen! + // The signature sequence we look for is: + // add xS, xD, 0x10 + // orr xN, xM, xS + // str wT, [xN] + // After that, there is a load from xN and a tbz based on bit 0 of the value. + // Then we have another store, another load, but this time a tbnz because it's a loop. + // In addition, there can be various other instructions scattered between these. + // Only the block above seems to reliably get emitted contiguously. + // /x c84200911a0308aa570300b9:00fcffff00fce0ff00fcffff + else if((op1 & 0xfffffc00) == 0x91004000 && (op2 & 0xfffffc00) == (((op1 & 0x1f) << 16) | 0xaa000000) && (op3 & 0xffffffe0) == (((op2 & 0x1f) << 5) | 0xb9000000)) + { + // Within a few instructions, there has to be a load from the same base reg as op3 + uint32_t ins = (op3 & 0x3e0) | 0xb9400000; + uint32_t op = 0; + volatile uint32_t *ldr = NULL; + for(size_t i = 0; i < 4; ++i) + { + op = p[3 + i]; + if((op & 0xffffffe0) == ins) + { + ldr = p + 3 + i; + break; + } + } + if(!ldr) + { + goto fail; + } + // NOP the write and turn the load into an immediate move + p[2] = 0xd503201f; + *ldr = (op & 0x1f) | 0x52800020; + + // There is another store after this, with the same value register as op3, but possibly a different base register. + ins = op3 & 0xfffffc1f; + volatile uint32_t *str = NULL; + for(size_t i = 1; i <= 8; ++i) + { + op = ldr[i]; + if((op & 0xfffffc1f) == ins) + { + str = ldr + i; + break; + } + } + if(!str) + { + goto fail; + } + // And another load like above + ins = (op & 0x3e0) | 0xb9400000; + ldr = NULL; + for(size_t i = 1; i <= 4; ++i) + { + op = str[i]; + if((op & 0xffffffe0) == ins) + { + ldr = str + i; + break; + } + } + if(!ldr) + { + goto fail; + } + // Same patch + *str = 0xd503201f; + *ldr = (op & 0x1f) | 0x52800020; + + tz_done = true; + break; + } + } + iorvbar_yeet(boot_image); aes_keygen(boot_image); // Ultra dirty hack: 16K support = Reconfig Engine @@ -147,6 +281,10 @@ void patch_bootloader(void* boot_image) recfg_yoink(boot_image); } fuse_jump(boot_image); + if(!tz_done) + { + antitrust(boot_image); + } return; fail:; diff --git a/src/drivers/sep/sep.c b/src/drivers/sep/sep.c index 3281e5f4..a04c7016 100644 --- a/src/drivers/sep/sep.c +++ b/src/drivers/sep/sep.c @@ -336,7 +336,11 @@ static bool seprom_config_integrity_tree(bool sync) { else // iOS <=12 { // Best we can do, I guess? - tree_size = tz0_size() / 0x910; + uint64_t tz0_size; + if (!tz_get(0, NULL, &tz0_size)) { + panic("Failed to get TZ0 size"); + } + tree_size = tz0_size / 0x910; if(tree_size >= 0x4000) tree_size = 0x4000; else tree_size &= 0x3ff0; } @@ -812,7 +816,11 @@ void seprom_fwload_race() { shmshc[ct++] = 0; *remote_addr = remote_shared_value_ptr; - map_range(0xc00000000, tz0_base(), tz0_size(), 3, 2, true); + uint64_t tz0_base, tz0_size; + if (!tz_get(0, &tz0_base, &tz0_size)) { + panic("Failed to get TZ0 bounds"); + } + map_range(0xc00000000, tz0_base, tz0_size, 3, 2, true); if (!tz_blackbird()) goto out; @@ -827,8 +835,8 @@ void seprom_fwload_race() { return; } - void* image_victim = tz0_calculate_encrypted_block_addr(0); - __unused void* tz0_shc = image_victim + (victim_offset * 2); + void* image_victim = (void*)((uintptr_t)0xc00000000 + tz0_calculate_encrypted_block_offset(0)); + void* tz0_shc = (void*)((uintptr_t)0xc00000000 + (victim_offset * 2)); memcpy(replay_layout, image_victim, range_size * 2); @@ -883,7 +891,7 @@ void seprom_fwload_race() { void seprom_load_art(void* art, char mode) { disable_interrupts(); - seprom_execute_opcode(6, mode, (vatophys((uint64_t)art)) >> 12); + seprom_execute_opcode(7, mode, (vatophys((uint64_t)art)) >> 12); event_wait_asserted(&sep_msg_event); } void seprom_artload() { @@ -1087,7 +1095,7 @@ void sep_auto(const char* cmd, char* args) // - There was no iBoot patch and SEP is in exactly the state XNU expects it in // - There was no iBoot patch and while we'd need to pwn, we are powerless to do so // - The user did something on the command line. Now it's their responsibility. - if(tz0_is_locked()) + if(tz_locked(0)) { return; } diff --git a/src/drivers/tz/tz.c b/src/drivers/tz/tz.c index ee57af7d..90f9967f 100644 --- a/src/drivers/tz/tz.c +++ b/src/drivers/tz/tz.c @@ -24,164 +24,473 @@ * SOFTWARE. * */ -#import -volatile uint32_t *gTZRegbase; +#include +#include +#include +#include +#include +#include "pongo.h" +#include "tz.h" -static void tz_command(const char* cmd, char* args) { - uint32_t raw[4]; - uint32_t shift; - if(socnum == 0x8960) +static bool gHaveTZ1 = false; +static volatile uint32_t *gTZRegbase[2]; + +static bool tz_idx_shift(uint8_t which, uint8_t *idx, uint8_t *shift) +{ + if(shift) *shift = socnum == 0x8960 ? 20 : 12; + switch(which) { - uint32_t one = gTZRegbase[2]; - uint32_t two = gTZRegbase[3]; - raw[0] = one & 0xffff; - raw[1] = (one >> 16) & 0xffff; - raw[2] = two & 0xffff; - raw[3] = (two >> 16) & 0xffff; - shift = 20; + case 0: + if(idx) *idx = socnum == 0x8960 ? 2 : 0; + return true; + + case 1: + if(!gHaveTZ1) + { + return false; + } + if(idx) *idx = socnum == 0x8960 ? 3 : 2; + return true; + + default: + return false; } - else +} + +static bool tz_get_raw(uint8_t which, uint8_t slot, uint8_t *shift, uint32_t *start, uint32_t *end) +{ + uint8_t idx; + if(!tz_idx_shift(which, &idx, shift)) { - raw[0] = gTZRegbase[0]; - raw[1] = gTZRegbase[1]; - raw[2] = gTZRegbase[2]; - raw[3] = gTZRegbase[3]; - shift = 12; - } - uint64_t real[4]; - real[0] = ( (uint64_t)raw[0] << shift) + 0x800000000ULL; - real[1] = (((uint64_t)raw[1] + 1) << shift) + 0x800000000ULL; - real[2] = ( (uint64_t)raw[2] << shift) + 0x800000000ULL; - real[3] = (((uint64_t)raw[3] + 1) << shift) + 0x800000000ULL; - iprintf("TZ0 (%s):\n" - " base: %x (%llx)\n" - " end: %x (%llx)\n" - "\n" - "TZ1 (%s):\n" - " base: %x (%llx)\n" - " end: %x (%llx)\n" - "\n", - gTZRegbase[4] ? "locked" : "unlocked", - raw[0], real[0], - raw[1], real[1], - gTZRegbase[5] ? "locked" : "unlocked", - raw[2], real[2], - raw[3], real[3]); -} - -void tz_lockdown(void) { - bool have_tz0, have_tz1; + return false; + } + + volatile uint32_t *reg = gTZRegbase[slot]; + if(!reg) + { + return false; + } if(socnum == 0x8960) { - have_tz0 = (gTZRegbase[2] & 0xffff) != 0; - have_tz1 = (gTZRegbase[3] & 0xffff) != 0; + uint32_t val = reg[idx]; + *start = val & 0xffff; + *end = (val >> 16) & 0xffff; } else { - have_tz0 = gTZRegbase[0] != 0; - have_tz1 = gTZRegbase[2] != 0; + *start = reg[idx]; + *end = reg[idx+1]; } - if(have_tz0) gTZRegbase[4] = 1; - if(have_tz1) gTZRegbase[5] = 1; + return true; } -bool tz_blackbird(void) { - if(socnum == 0x8960) +bool tz_get(uint8_t which, uint64_t *base, uint64_t *size) +{ + uint8_t shift; + uint32_t start, end; + if(!tz_get_raw(which, 0, &shift, &start, &end)) { - iprintf("Not supported on this SoC\n"); return false; } - if(gTZRegbase[4]) + if(base) *base = ((uint64_t)start << shift) + 0x800000000ULL; + if(size) *size = ((uint64_t)(end - start + 1) << shift); + return true; +} + +static bool tz_set_raw(uint8_t which, uint32_t start, uint32_t end) +{ + uint8_t idx; + if(!tz_idx_shift(which, &idx, NULL)) { - iprintf("Registers are locked\n"); return false; } - // XXX: This used to be XOR, but that doesn't work well with the expectations in sep.c. - // This was probably here to allow toggling from the shell, but that could be done via tz0_set. - gTZRegbase[0] |= 0x100000; + + for(uint8_t i = 0; i < sizeof(gTZRegbase)/sizeof(*gTZRegbase); ++i) + { + volatile uint32_t *reg = gTZRegbase[i]; + if(!reg) + { + continue; + } + if(socnum == 0x8960) + { + reg[idx] = (start & 0xffff) | ((end & 0xffff) << 16); + } + else + { + reg[idx] = start; + reg[idx+1] = end; + } + __asm__ volatile("dmb sy"); + } return true; } -static void tz0_set(const char* cmd, char* args) { - // TODO: would be nice to have this exported for code too, but modifying - // the actual TZ0 range in a XNU-compatible way would be a pain... - if (! *args) { - iprintf("tz_set usage: tz0_set [base] [end]\n"); - return; +bool tz_set(uint8_t which, uint64_t base, uint64_t size) +{ + uint8_t shift; + if(!tz_idx_shift(which, NULL, &shift)) + { + return false; } - char* arg1 = command_tokenize(args, 0x1ff - (args - cmd)); - if (!*arg1) { - iprintf("tz_set usage: tz0_set [base] [end]\n"); - return; + + uint32_t start = base >> shift; + uint32_t end = (base + size - 1) >> shift; + return tz_set_raw(which, start, end); +} + +bool tz_locked(uint8_t which) +{ + switch(which) + { + case 0: + break; + + case 1: + if(!gHaveTZ1) + { + return false; + } + break; + + default: + return false; } - uint64_t base = strtoull(args, NULL, 16); - uint64_t end = strtoull(arg1, NULL, 16); - if (gTZRegbase[4]) { - iprintf("registers are locked\n"); - return; + + bool locked = true; + for(uint8_t i = 0; i < sizeof(gTZRegbase)/sizeof(*gTZRegbase); ++i) + { + volatile uint32_t *reg = gTZRegbase[i]; + if(!reg) + { + continue; + } + if((reg[4 + which] & 1) == 0) + { + locked = false; + } } - if(socnum == 0x8960) + return locked; +} + +bool tz_lock(uint8_t which) +{ + switch(which) { - gTZRegbase[2] = (base & 0xffff) | ((end & 0xffff) << 16); + case 0: + break; + + case 1: + if(!gHaveTZ1) + { + return false; + } + break; + + default: + return false; } - else + + bool locked = true; + for(uint8_t i = 0; i < sizeof(gTZRegbase)/sizeof(*gTZRegbase); ++i) { - gTZRegbase[0] = base; - gTZRegbase[1] = end; + volatile uint32_t *reg = gTZRegbase[i]; + if(!reg) + { + continue; + } + reg[4 + which] = 1; + __asm__ volatile("dmb sy"); + if((reg[4 + which] & 1) == 0) + { + locked = false; + } } + return locked; } -void *tz0_calculate_encrypted_block_addr(uint64_t offset) { - uint64_t offset_block = (offset & (~0x1f)); - offset_block <<= 1; // * 2 - // TODO: get rid of this magic constant - // Maybe change the API to just return an offset and let the caller add it to their memory base? - return (void*)(0xc00000000ULL + offset_block); +bool tz_lockdown(void) +{ + bool lock0 = tz_lock(0); + bool lock1 = gHaveTZ1 ? tz_lock(1) : true; + return lock0 && lock1; } -bool tz0_is_locked(void) +bool tz_blackbird(void) { - return gTZRegbase[4] != 0; + if(socnum == 0x8960 || tz_locked(0)) + { + return false; + } + for(uint8_t i = 0; i < sizeof(gTZRegbase)/sizeof(*gTZRegbase); ++i) + { + volatile uint32_t *reg = gTZRegbase[i]; + if(!reg) + { + continue; + } + if((reg[4] & 1) != 0) + { + return false; + } + reg[0] |= 0x100000; + __asm__ volatile("dmb sy"); + } + return true; } -uint64_t tz0_base(void) +uint64_t tz0_calculate_encrypted_block_offset(uint64_t offset) { - if(socnum == 0x8960) + switch(socnum) + { + case 0x8960: + return offset & ~0xfULL; + + case 0x8015: + return (offset & ~0x3fULL) << 1; + + default: + return (offset & ~0x1fULL) << 1; + } +} + +struct tz_command +{ + const char* name; + const char* desc; + void (*cb)(const char *cmd, char *args); +}; + +static void tz_cmd_help(const char *cmd, char *args); +static void tz_cmd_status(const char *cmd, char *args); +static void tz_cmd_lock(const char *cmd, char *args); +static void tz_cmd_set(const char *cmd, char *args); +static void tz_cmd_blackbird(const char *cmd, char *args); + +static const struct tz_command command_table[] = +{ + {"help", "show usage", tz_cmd_help}, + {"status", "print trustzone registers", tz_cmd_status}, + {"lock", "lock trustzone registers", tz_cmd_lock}, + {"set", "set trustzone registers to custom values", tz_cmd_set}, + {"blackbird", "trustzone blackbird attack", tz_cmd_blackbird}, +}; + +static void tz_cmd_help(const char *cmd, char *args) +{ + iprintf("tz usage: tz [subcommand]\nsubcommands:\n"); + for(size_t i = 0; i < sizeof(command_table) / sizeof(command_table[0]); ++i) { - return ((uint64_t)(gTZRegbase[2] & 0xffff) << 20) + 0x800000000ULL; + iprintf("%12s | %s\n", command_table[i].name, command_table[i].desc); } - else +} + +static void tz_cmd_status(const char *cmd, char *args) +{ + for(uint8_t i = 0; i < sizeof(gTZRegbase)/sizeof(*gTZRegbase); ++i) { - return ((uint64_t)gTZRegbase[0] << 12) + 0x800000000ULL; + for(uint8_t which = 0; which <= 1; ++which) + { + uint8_t shift; + uint32_t start, end; + if(tz_get_raw(which, i, &shift, &start, &end)) + { + uint64_t base = ((uint64_t)start << shift) + 0x800000000ULL; + uint64_t top = ((uint64_t)(end + 1) << shift) + 0x800000000ULL; + iprintf("TZ%hhu (%s):\n" + " base: 0x%x (0x%llx)\n" + " end: 0x%x (0x%llx)\n" + "\n", + which, (gTZRegbase[i][4 + which] & 1) != 0 ? "locked" : "unlocked", + start, base, end, top); + } + } } } -uint64_t tz0_size(void) +static void tz_cmd_lock(const char *cmd, char *args) { - if(socnum == 0x8960) + bool lock0 = false, + lock1 = false; + if(args[0] == '\0') + { + lock0 = true; + lock1 = gHaveTZ1; + } + else if(strcmp(args, "0") == 0) { - uint32_t reg = gTZRegbase[2]; - return (uint64_t)(((reg >> 16) & 0xffff) - (reg & 0xffff) + 1) << 20; + lock0 = true; + } + else if(strcmp(args, "1") == 0) + { + lock1 = true; } else { - return (uint64_t)(gTZRegbase[1] - gTZRegbase[0] + 1) << 12; + iprintf("Bad argument: %s\n", args); + return; + } + + if(lock0) + { + if(tz_locked(0)) + { + iprintf("TZ0 already locked.\n"); + } + else if(!tz_lock(0)) + { + iprintf("Failed to lock TZ0.\n"); + } + } + if(lock1) + { + if(tz_locked(1)) + { + iprintf("TZ1 already locked.\n"); + } + else if(!tz_lock(1)) + { + iprintf("Failed to lock TZ1.\n"); + } } } -void tz_setup(void) { +static void tz_cmd_set(const char *cmd, char *args) +{ + char *zone = command_tokenize(args, 0x1ff - (args - cmd)); + if(zone && *zone) + { + char *base = command_tokenize(zone, 0x1ff - (zone - cmd)); + if(base && *base) + { + char *size = command_tokenize(base, 0x1ff - (base - cmd)); + if(size && *size) + { + uint8_t zone_arg = strcmp(zone, "0") == 0 ? 0 + : strcmp(zone, "1") == 0 ? 1 + : -1; + char *base_end = NULL; + char *size_end = NULL; + unsigned long long base_arg = strtoull(base, &base_end, 0); + unsigned long long size_arg = strtoull(size, &size_end, 0); + if(zone_arg != 0 && zone_arg != 1) iprintf("Bad zone argument: %s\n", zone); + if(*base_end) iprintf("Bad base argument: %s\n", base); + if(*size_end) iprintf("Bad size argument: %s\n", size); + if((zone_arg == 0 || zone_arg == 1) && !*base_end && !*size_end) + { + if(!tz_set(zone_arg, base_arg, size_arg)) + { + iprintf("Failed to set TZ values.\n"); + } + return; + } + } + } + } + iprintf("tz set usage: tz set [zone] [base] [size]\n"); +} + +static void tz_cmd_set_legacy(const char *cmd, char *args) +{ + char *reg0 = command_tokenize(args, 0x1ff - (args - cmd)); + if(reg0 && *reg0) + { + char *reg1 = command_tokenize(reg0, 0x1ff - (reg0 - cmd)); + if(reg1 && *reg1) + { + char *reg0_end = NULL; + char *reg1_end = NULL; + unsigned long long reg0_arg = strtoull(reg0, ®0_end, 0); + unsigned long long reg1_arg = strtoull(reg1, ®1_end, 0); + if(*reg0_end) iprintf("Bad reg0 argument: %s\n", reg0); + if(*reg1_end) iprintf("Bad reg1 argument: %s\n", reg1); + if(!*reg0_end || !*reg1_end) + { + if(!tz_set_raw(0, (uint32_t)reg0_arg, (uint32_t)reg1_arg)) + { + iprintf("Failed to set TZ0 values.\n"); + } + return; + } + } + } + iprintf("tz0_set usage: tz0_set [reg0] [reg1]\n"); +} + +static void tz_cmd_blackbird(const char *cmd, char *args) +{ if(socnum == 0x8960) { - gTZRegbase = (volatile uint32_t*)(gIOBase + 0x900); + iprintf("Not supported on this SoC.\n"); + return; } - else + if(tz_locked(0)) + { + iprintf("TZ0 is already locked.\n"); + return; + } + if(!tz_blackbird()) + { + iprintf("Failed to perform blackbird.\n"); + } +} + +static void tz_cmd(const char* cmd, char *args) +{ + char *arguments = command_tokenize(args, 0x1ff - (args - cmd)); + if(arguments) + { + for(size_t i = 0; i < sizeof(command_table) / sizeof(command_table[0]); ++i) + { + if(strcmp(args, command_table[i].name) == 0) + { + command_table[i].cb(cmd, arguments); + return; + } + } + if(args[0] != '\0') + { + iprintf("tz: invalid command %s\n", args); + } + tz_cmd_help(cmd, arguments); + } +} + +void tz_setup(void) +{ + switch(socnum) { - gTZRegbase = (volatile uint32_t*)(gIOBase + 0x480); + case 0x8960: + gHaveTZ1 = true; + gTZRegbase[0] = (volatile uint32_t*)(gIOBase + 0x900); + break; + + case 0x8001: + gTZRegbase[1] = (volatile uint32_t*)(gIOBase + 0x200480); + /* fallthrough */ + + case 0x7000: + case 0x7001: + case 0x8000: + case 0x8003: + gHaveTZ1 = true; + /* fallthrough */ + + case 0x8010: + case 0x8011: + case 0x8012: + case 0x8015: + gTZRegbase[0] = (volatile uint32_t*)(gIOBase + 0x480); + break; + + default: + panic("Unsupported SoC"); } - // TODO: would be nicer to have everything under just a "tz" command, similar to how "sep" and "recfg" work. - command_register("tz", "trustzone info", tz_command); - command_register("tz0_set", "change tz0 registers", (void*)tz0_set); - command_register("tz_lockdown", "trustzone lockdown", (void*)tz_lockdown); - command_register("tz_blackbird", "trustzone blackbird attack", (void*)tz_blackbird); + + command_register("tz", "trustzone management", tz_cmd); + // Keep these for legacy compat, but hide them from help + _command_register_internal("tz0_set", NULL, tz_cmd_set_legacy, true); + _command_register_internal("tz_lockdown", NULL, tz_cmd_lock, true); + _command_register_internal("tz_blackbird", NULL, tz_cmd_blackbird, true); } diff --git a/src/drivers/tz/tz.h b/src/drivers/tz/tz.h index dbf0ad4b..0d37edb9 100644 --- a/src/drivers/tz/tz.h +++ b/src/drivers/tz/tz.h @@ -25,13 +25,18 @@ * */ +#ifndef TZ_H +#define TZ_H + #include #include -void tz_setup(void); -void tz_lockdown(void); +bool tz_get(uint8_t which, uint64_t *base, uint64_t *size); +bool tz_set(uint8_t which, uint64_t base, uint64_t size); +bool tz_locked(uint8_t which); +bool tz_lock(uint8_t which); +bool tz_lockdown(void); bool tz_blackbird(void); -void *tz0_calculate_encrypted_block_addr(uint64_t offset); -bool tz0_is_locked(void); -uint64_t tz0_base(void); -uint64_t tz0_size(void); +uint64_t tz0_calculate_encrypted_block_offset(uint64_t offset); + +#endif diff --git a/src/drivers/tz/tz_private.h b/src/drivers/tz/tz_private.h new file mode 100644 index 00000000..c8b8cb0f --- /dev/null +++ b/src/drivers/tz/tz_private.h @@ -0,0 +1,37 @@ +/* + * 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. + * + */ + +#ifndef TZ_PRIVATE_H +#define TZ_PRIVATE_H + +#ifdef PONGO_PRIVATE + +void tz_setup(void); + +#endif + +#endif diff --git a/src/dynamic/modload.c b/src/dynamic/modload.c index e551bf99..02236727 100644 --- a/src/dynamic/modload.c +++ b/src/dynamic/modload.c @@ -568,9 +568,13 @@ PONGO_EXPORT(task_critical_exit); PONGO_EXPORT(task_bind_to_irq); PONGO_EXPORT(task_release); PONGO_EXPORT(task_reference); -PONGO_EXPORT(tz0_calculate_encrypted_block_addr); -PONGO_EXPORT(tz_blackbird); +PONGO_EXPORT(tz_get); +PONGO_EXPORT(tz_set); +PONGO_EXPORT(tz_locked); +PONGO_EXPORT(tz_lock); PONGO_EXPORT(tz_lockdown); +PONGO_EXPORT(tz_blackbird); +PONGO_EXPORT(tz0_calculate_encrypted_block_offset); PONGO_EXPORT(vatophys); PONGO_EXPORT(vatophys_static); PONGO_EXPORT(lock_take); diff --git a/src/kernel/main_task.c b/src/kernel/main_task.c index 4787ae12..250abf7e 100644 --- a/src/kernel/main_task.c +++ b/src/kernel/main_task.c @@ -27,6 +27,7 @@ #include #include #include +#include void shell_main();