mirror of
https://github.com/EchoStretch/kstuff-lite.git
synced 2026-10-19 02:09:50 -05:00
Fix plaintext PPR key cleanup on unmount
This commit is contained in:
1 parent
5766aafce0
commit
35472f79ff
4 files changed
+242
-19
No files matched your search
@@ -160,7 +160,9 @@ The added metrics cover:
|
||||
snapshots;
|
||||
- CR0 fast-entry/deferred-restore arms, failures, hook-cache use,
|
||||
and arm cost;
|
||||
- FPU enter/exit, XSAVE/XSAVEC/XRSTOR counts, failures, and cycle cost.
|
||||
- FPU enter/exit, XSAVE/XSAVEC/XRSTOR counts, failures, and cycle cost;
|
||||
- PLAINTEXT_NOAUTH FE/FF index acquisition and exact transient-context cleanup
|
||||
before `ppfs_put_{cmac,xts}_index` (`PPRCLN01` word-log records).
|
||||
|
||||
`ps5-kstuff/debug-reader.c` prints these counters from the shared observation
|
||||
area. Metrics are intended for comparison runs; they add measurement overhead
|
||||
|
||||
+231
-15
@@ -26,6 +26,14 @@ enum {
|
||||
PPR_PFS_940_GET_CMAC_INDEX_FROM_MAILBOX = 0x5b2ce0,
|
||||
PPR_PFS_940_GET_XTS_RETURN_BEFORE_MAILBOX = 0x208902,
|
||||
PPR_PFS_940_GET_CMAC_RETURN_BEFORE_MAILBOX = 0x2088b4,
|
||||
/* cleanup_a53io_pkg_keys entry 0xffffffff954b7d60. */
|
||||
PPR_PFS_940_CLEANUP_KEYS_BEFORE_MAILBOX = 0x2109d0,
|
||||
/*
|
||||
* sceSblPfsClearKey's tree-miss status instruction at
|
||||
* 0xffffffff953e0409. The private FD/FC pair is deliberately absent
|
||||
* from that tree, so complete only that exact miss as success.
|
||||
*/
|
||||
PPR_PFS_940_CLEAR_KEY_MISSING_BEFORE_MAILBOX = 0x2e8327,
|
||||
/*
|
||||
* The PPR verifyImage mailbox return at 0xffffffff953defa0 is
|
||||
* 0x1d00 bytes before the existing verifySuperBlock return at
|
||||
@@ -79,6 +87,81 @@ static uint64_t ppr_pfs_plaintext_get_cmac_index(void)
|
||||
+ PPR_PFS_940_GET_CMAC_INDEX_FROM_MAILBOX;
|
||||
}
|
||||
|
||||
static uint64_t ppr_pfs_plaintext_cleanup_keys(void)
|
||||
{
|
||||
if(FWVER != 0x940)
|
||||
return 0;
|
||||
return (uint64_t)sceSblServiceMailbox
|
||||
- PPR_PFS_940_CLEANUP_KEYS_BEFORE_MAILBOX;
|
||||
}
|
||||
|
||||
static uint64_t ppr_pfs_plaintext_clear_key_missing(void)
|
||||
{
|
||||
if(FWVER != 0x940)
|
||||
return 0;
|
||||
return (uint64_t)sceSblServiceMailbox
|
||||
- PPR_PFS_940_CLEAR_KEY_MISSING_BEFORE_MAILBOX;
|
||||
}
|
||||
|
||||
static uint64_t* ppr_plaintext_index_counter(int cmac)
|
||||
{
|
||||
return cmac ? &shared_area.ppr_plaintext_cmac_indices_outstanding
|
||||
: &shared_area.ppr_plaintext_xts_indices_outstanding;
|
||||
}
|
||||
|
||||
static void retain_ppr_plaintext_key_pair(void)
|
||||
{
|
||||
__atomic_fetch_add(ppr_plaintext_index_counter(0), 1, __ATOMIC_RELEASE);
|
||||
__atomic_fetch_add(ppr_plaintext_index_counter(1), 1, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
static int has_ppr_plaintext_key_index(int cmac)
|
||||
{
|
||||
return __atomic_load_n(ppr_plaintext_index_counter(cmac),
|
||||
__ATOMIC_ACQUIRE) != 0;
|
||||
}
|
||||
|
||||
static int has_ppr_plaintext_key_pair(void)
|
||||
{
|
||||
return has_ppr_plaintext_key_index(0)
|
||||
|| has_ppr_plaintext_key_index(1);
|
||||
}
|
||||
|
||||
static int release_ppr_plaintext_key_index(int cmac, uint64_t* remaining)
|
||||
{
|
||||
uint64_t* counter = ppr_plaintext_index_counter(cmac);
|
||||
uint64_t old = __atomic_load_n(
|
||||
counter,
|
||||
__ATOMIC_ACQUIRE);
|
||||
while(old)
|
||||
{
|
||||
if(__atomic_compare_exchange_n(
|
||||
counter,
|
||||
&old, old - 1, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE))
|
||||
{
|
||||
if(remaining)
|
||||
*remaining = old - 1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int release_ppr_plaintext_key_pair(uint64_t* xts_remaining,
|
||||
uint64_t* cmac_remaining)
|
||||
{
|
||||
if(!release_ppr_plaintext_key_index(0, xts_remaining))
|
||||
return 0;
|
||||
if(!release_ppr_plaintext_key_index(1, cmac_remaining))
|
||||
{
|
||||
/* Keep both lifetimes paired if an unexpected concurrent clear won. */
|
||||
__atomic_fetch_add(ppr_plaintext_index_counter(0), 1,
|
||||
__ATOMIC_RELEASE);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint64_t ppr_pfs_verify_image_lr(void)
|
||||
{
|
||||
if(FWVER != 0x940)
|
||||
@@ -498,6 +581,114 @@ static void try_emulate_plaintext_key_index(uint64_t* regs, int cmac)
|
||||
#endif
|
||||
}
|
||||
|
||||
static void try_prepare_plaintext_key_cleanup(uint64_t* regs)
|
||||
{
|
||||
uint64_t cleanup_context = 0;
|
||||
uint64_t key_indices = 0;
|
||||
uint64_t xts_retained = 0;
|
||||
uint64_t cmac_retained = 0;
|
||||
const uint64_t synthetic_indices = 0x000000fe000000ffull;
|
||||
|
||||
/*
|
||||
* The 9.40 entry has a five-byte no-op frame shim followed by a jump to
|
||||
* entry+0x10. Emulate that shim for every hit so an unrelated native
|
||||
* cleanup can continue even while a plaintext mount is outstanding.
|
||||
*/
|
||||
canonicalize_debug_gprs(regs);
|
||||
regs[RIP] = ppr_pfs_plaintext_cleanup_keys() + 0x10;
|
||||
|
||||
/*
|
||||
* a1[271] points at the transient ppfs cleanup context. XTS and CMAC
|
||||
* indices are adjacent dwords at +56/+60. They were never reserved in the
|
||||
* stock allocator, so convert only the exact FF/FE pair to its normal
|
||||
* "not installed" representation before the original cleanup tests it.
|
||||
*/
|
||||
if(!has_ppr_plaintext_key_index(0)
|
||||
|| !has_ppr_plaintext_key_index(1)
|
||||
|| (regs[RDI] >> 48) != 0xffff
|
||||
|| copy_u64_from_kernel(&cleanup_context,
|
||||
regs[RDI] + 271 * sizeof(uint64_t)))
|
||||
return;
|
||||
cleanup_context = canonicalize_debug_kernel_pointer(cleanup_context);
|
||||
if((cleanup_context >> 48) != 0xffff
|
||||
|| copy_u64_from_kernel(&key_indices, cleanup_context + 56)
|
||||
|| key_indices != synthetic_indices)
|
||||
return;
|
||||
if(copy_u64_to_kernel(cleanup_context + 56, UINT64_MAX))
|
||||
{
|
||||
METRIC_INC(ppr_plaintext_g6_copy_failures);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The original body now skips both ppfs_put_* calls and performs the rest
|
||||
* of its context/VFS teardown unchanged. Do not release the retained
|
||||
* handles here: ppr_pfs_unmount subsequently calls sceSblPfsClearKey for
|
||||
* the FD/FC pair. Its stock tree lookup must miss because the private
|
||||
* pair was never registered; the exact miss trap is the lifetime end of
|
||||
* both synthetic keys.
|
||||
*/
|
||||
xts_retained = __atomic_load_n(ppr_plaintext_index_counter(0),
|
||||
__ATOMIC_ACQUIRE);
|
||||
cmac_retained = __atomic_load_n(ppr_plaintext_index_counter(1),
|
||||
__ATOMIC_ACQUIRE);
|
||||
observe_current_syscall_emulated();
|
||||
#if KSTUFF_OBS
|
||||
log_word(0x505052434c4e3031ull); /* "PPRCLN01" */
|
||||
log_word(cleanup_context);
|
||||
log_word(key_indices);
|
||||
log_word(xts_retained);
|
||||
log_word(cmac_retained);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void try_emulate_plaintext_clear_key_missing(uint64_t* regs)
|
||||
{
|
||||
const uint64_t expected_pair =
|
||||
((uint64_t)PPR_PFS_PLAINTEXT_XTS_HANDLE << 32)
|
||||
| PPR_PFS_PLAINTEXT_CMAC_HANDLE;
|
||||
uint64_t xts_remaining = 0;
|
||||
uint64_t cmac_remaining = 0;
|
||||
uint64_t trap = ppr_pfs_plaintext_clear_key_missing();
|
||||
int expected_args = regs[R13] == expected_pair
|
||||
|| (regs[R14] == PPR_PFS_PLAINTEXT_XTS_HANDLE
|
||||
&& regs[R15] == PPR_PFS_PLAINTEXT_CMAC_HANDLE);
|
||||
|
||||
/*
|
||||
* The trapped instruction is `mov ebx, 0xfffffffe`. Reproduce it for
|
||||
* every unrelated tree miss. At this point sceSblPfsClearKey has already
|
||||
* normally built (a1 << 32) | a2 in R13 and still owns its normal
|
||||
* lock/frame. If the tree root itself is null, the branch arrives before
|
||||
* that construction and the original zero-extended arguments remain in
|
||||
* R14/R15, so accept that equivalent exact shape too.
|
||||
*/
|
||||
if(!expected_args
|
||||
|| !has_ppr_plaintext_key_index(0)
|
||||
|| !has_ppr_plaintext_key_index(1)
|
||||
|| !release_ppr_plaintext_key_pair(&xts_remaining, &cmac_remaining))
|
||||
{
|
||||
regs[RBX] = UINT32_MAX - 1u;
|
||||
regs[RIP] = trap + 5;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Skip the diagnostic call but retain sceSblPfsClearKey's stock unlock,
|
||||
* stack-canary check and epilogue at loc_ffffffff953e041f.
|
||||
*/
|
||||
regs[RBX] = 0;
|
||||
regs[RIP] = trap + 0x16;
|
||||
METRIC_INC(clear_key_emulated);
|
||||
observe_current_syscall_emulated();
|
||||
#if KSTUFF_OBS
|
||||
log_word(0x505052434c523031ull); /* "PPRCLR01" */
|
||||
log_word(expected_pair);
|
||||
log_word(xts_remaining);
|
||||
log_word(cmac_remaining);
|
||||
log_word(regs[RIP]);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#define IDX_TO_HANDLE(x) (0x13374100 | ((uint8_t)((x)+1)))
|
||||
#define HANDLE_TO_IDX(x) ((((x) & 0xffffff00) == 0x13374100 ? ((int)(uint8_t)(x)) : (int)0) - 1)
|
||||
@@ -795,7 +986,9 @@ int is_fpkg_trap_rip(uint64_t rip)
|
||||
{
|
||||
return rip == (uint64_t)sceSblServiceCryptAsync_deref_singleton
|
||||
|| rip == ppr_pfs_plaintext_get_xts_index()
|
||||
|| rip == ppr_pfs_plaintext_get_cmac_index();
|
||||
|| rip == ppr_pfs_plaintext_get_cmac_index()
|
||||
|| rip == ppr_pfs_plaintext_cleanup_keys()
|
||||
|| rip == ppr_pfs_plaintext_clear_key_missing();
|
||||
}
|
||||
|
||||
int try_handle_fpkg_trap(uint64_t* regs)
|
||||
@@ -818,6 +1011,14 @@ int try_handle_fpkg_trap(uint64_t* regs)
|
||||
{
|
||||
try_emulate_plaintext_key_index(regs, 1);
|
||||
}
|
||||
else if(regs[RIP] == ppr_pfs_plaintext_cleanup_keys())
|
||||
{
|
||||
try_prepare_plaintext_key_cleanup(regs);
|
||||
}
|
||||
else if(regs[RIP] == ppr_pfs_plaintext_clear_key_missing())
|
||||
{
|
||||
try_emulate_plaintext_clear_key_missing(regs);
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
return 1;
|
||||
@@ -906,9 +1107,11 @@ int try_handle_fpkg_mailbox(uint64_t* regs, uint64_t lr)
|
||||
* debug exception is unsafe, so protocol v7 snapshots both file ranges
|
||||
* before nmount and this trap supplies the same output buffers.
|
||||
* registerMountKey indexes a global RB tree by handle alone. The
|
||||
* emulated result therefore resumes at the exact 9.40 success block
|
||||
* after both key registrations and the pair-tree insertion. The
|
||||
* sentinels cannot leak into those global tables after a failed mount.
|
||||
* emulated result therefore jumps past both key registrations and the
|
||||
* pair-tree insertion: the sentinels never enter either global table.
|
||||
* A retained-pair counter lets the later unmount trap complete the
|
||||
* matching sceSblPfsClearKey tree miss without touching unrelated
|
||||
* pairs.
|
||||
* The kernel-side verify_ppr_sblock_100 continuation still checks the
|
||||
* returned read sizes and parses the superblock metadata. Later in the
|
||||
* same nmount, exact ppfs_get_{xts,cmac}_index entry traps return the
|
||||
@@ -1014,8 +1217,9 @@ int try_handle_fpkg_mailbox(uint64_t* regs, uint64_t lr)
|
||||
/*
|
||||
* Recreate the two values that the skipped 9.40 wrapper code
|
||||
* would have prepared, then continue at loc_953df2dc. That block
|
||||
* combines the pair and publishes both completed read sizes, but
|
||||
* is after registerMountKey() and the pair RB-tree insertion.
|
||||
* combines the pair and publishes both completed read sizes. We
|
||||
* enter it after (and deliberately without executing)
|
||||
* registerMountKey() or the pair RB-tree insertion.
|
||||
*/
|
||||
if(copy_u64_to_kernel(regs[RBP] - 0x150,
|
||||
PPR_PFS_PLAINTEXT_XTS_HANDLE))
|
||||
@@ -1028,6 +1232,7 @@ int try_handle_fpkg_mailbox(uint64_t* regs, uint64_t lr)
|
||||
if(copy_to_kernel(regs[RDX], &fake_resp, sizeof(fake_resp)))
|
||||
return 0;
|
||||
regs[R13] = PPR_PFS_PLAINTEXT_CMAC_HANDLE;
|
||||
retain_ppr_plaintext_key_pair();
|
||||
release_ppr_plaintext_staging(latch_td);
|
||||
regs[RIP] = lr
|
||||
+ PPR_PFS_940_VERIFY_IMAGE_NO_KEY_SUCCESS_FROM_LR;
|
||||
@@ -1146,9 +1351,9 @@ void handle_fpkg_trap(uint64_t* regs, uint32_t trapno)
|
||||
}
|
||||
}
|
||||
|
||||
void handle_fpkg_syscall(uint64_t* regs, int enable_ppr_plaintext_traps)
|
||||
void handle_fpkg_syscall(uint64_t* regs, int is_nmount)
|
||||
{
|
||||
uint64_t dbgregs_for_nmount[6] = {
|
||||
uint64_t dbgregs_for_fpkg[6] = {
|
||||
(uint64_t)sceSblServiceMailbox, 0, 0, 0,
|
||||
0, 0x401
|
||||
};
|
||||
@@ -1158,17 +1363,28 @@ void handle_fpkg_syscall(uint64_t* regs, int enable_ppr_plaintext_traps)
|
||||
* explicitly owns an ARMED/ACTIVE PLAINTEXT_NOAUTH session; verifyImage
|
||||
* changes ARMED to ACTIVE later during this syscall.
|
||||
*/
|
||||
enable_ppr_plaintext_traps = enable_ppr_plaintext_traps
|
||||
&& current_ppr_plaintext_session_pending();
|
||||
int enable_ppr_plaintext_traps = is_nmount
|
||||
&& current_ppr_plaintext_session_pending();
|
||||
uint64_t get_xts = enable_ppr_plaintext_traps
|
||||
? ppr_pfs_plaintext_get_xts_index() : 0;
|
||||
uint64_t get_cmac = enable_ppr_plaintext_traps
|
||||
? ppr_pfs_plaintext_get_cmac_index() : 0;
|
||||
if(get_xts && get_cmac)
|
||||
uint64_t cleanup_keys = ppr_pfs_plaintext_cleanup_keys();
|
||||
uint64_t clear_key_missing = ppr_pfs_plaintext_clear_key_missing();
|
||||
if(get_xts && get_cmac && cleanup_keys)
|
||||
{
|
||||
dbgregs_for_nmount[2] = get_xts;
|
||||
dbgregs_for_nmount[3] = get_cmac;
|
||||
dbgregs_for_nmount[5] = 0x451; /* local DR0, DR2, DR3 + reserved bit 10 */
|
||||
dbgregs_for_fpkg[1] = cleanup_keys;
|
||||
dbgregs_for_fpkg[2] = get_xts;
|
||||
dbgregs_for_fpkg[3] = get_cmac;
|
||||
/* A later mount failure can clean the pair inside this same nmount. */
|
||||
dbgregs_for_fpkg[5] = 0x455; /* local DR0..DR3 + reserved bit 10 */
|
||||
}
|
||||
start_syscall_with_dbgregs(regs, dbgregs_for_nmount);
|
||||
else if(!is_nmount && cleanup_keys && clear_key_missing
|
||||
&& has_ppr_plaintext_key_pair())
|
||||
{
|
||||
dbgregs_for_fpkg[1] = cleanup_keys;
|
||||
dbgregs_for_fpkg[2] = clear_key_missing;
|
||||
dbgregs_for_fpkg[5] = 0x415; /* local DR0..DR2 + reserved bit 10 */
|
||||
}
|
||||
start_syscall_with_dbgregs(regs, dbgregs_for_fpkg);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
void handle_fpkg_syscall(uint64_t* regs, int enable_ppr_plaintext_traps);
|
||||
void handle_fpkg_syscall(uint64_t* regs, int is_nmount);
|
||||
void handle_fpkg_trap(uint64_t* regs, uint32_t trapno);
|
||||
int is_fpkg_trap_rip(uint64_t rip);
|
||||
int try_handle_fpkg_trap(uint64_t* regs);
|
||||
|
||||
@@ -408,6 +408,11 @@ struct shared_area_layout
|
||||
struct kstuff_ppr_plaintext_latch
|
||||
ppr_plaintext_latches[SHARED_PPR_PLAINTEXT_LATCH_SLOTS];
|
||||
struct kstuff_ppr_plaintext_staging ppr_plaintext_staging;
|
||||
/* Synthetic FE/FF indices identify retained FD/FC handles. They survive
|
||||
* cleanup_a53io_pkg_keys and end at the matching sceSblPfsClearKey pair
|
||||
* tree-miss in a later unmount syscall. */
|
||||
uint64_t ppr_plaintext_xts_indices_outstanding;
|
||||
uint64_t ppr_plaintext_cmac_indices_outstanding;
|
||||
#if KSTUFF_OBS
|
||||
struct kstuff_metrics metrics;
|
||||
struct kstuff_word_log word_log;
|
||||
@@ -440,8 +445,8 @@ _Static_assert(sizeof(struct kstuff_ioctl_com_table) == 3088, "unexpected ioctl
|
||||
_Static_assert(sizeof(struct kstuff_msg_log) == 504, "unexpected message log size");
|
||||
_Static_assert(sizeof(struct kstuff_snapshot) == 6088, "unexpected snapshot size");
|
||||
#if KSTUFF_OBS
|
||||
_Static_assert(sizeof(struct shared_area_layout) == 13816, "unexpected shared_area size");
|
||||
_Static_assert(sizeof(struct shared_area_layout) == 13832, "unexpected shared_area size");
|
||||
#else
|
||||
_Static_assert(sizeof(struct shared_area_layout) == 7744, "unexpected non-OBS shared_area size");
|
||||
_Static_assert(sizeof(struct shared_area_layout) == 7760, "unexpected non-OBS shared_area size");
|
||||
#endif
|
||||
_Static_assert(sizeof(struct shared_area_layout) <= SHARED_AREA_SIZE, "shared_area must fit in configured mapping");
|
||||
Reference in new issue
Block a user