Bugs

From Vita Development Wiki
Revision as of 23:16, 18 February 2023 by CelesteBlue (talk | contribs) (→‎Kernel)
Jump to navigation Jump to search

The PS Vita has bugs. Some bugs can lead to Vulnerabilities. Others lead to nothing useful (yet) but can serve as examples of what not to do.

Exploitable bugs

See Vulnerabilities.

Non-exploitable bugs

Kernel

Syscall table collision between modules

Because of the time between a slot is allocated and the time it is written to, there could be collisions. For example, assume there is one empty syscall slot left. Two modules each exporting syscalls are loaded and both of them are assigned the final free slot. One user library is loaded that imports from the first module. Then it imports from the second module. At this point, the function pointer exported by the first module is replaced with the second one.

It is unlikely this would lead to any security vulnerabilities, but it could create system instability. However, if the system has so many (let us assume more than 3000) syscalls loaded, it may be already in an unstable state.

Kernel heap pointer leak in sceKernelGetLibraryInfoByNID

Discovered on 2019-12-17 by Princess of Sleeping.

Leaks a kernel heap pointer, but probably not useful for kernel exploitation.

See SceKernelModulemgr#sceKernelGetLibraryInfoByNID.

SceIofilemgr misses internal NULL pointer checks

SceIofilemgr's syscalls wrappers do various checks in usermode for the sanity of usermode arguments, but some internal functions that the syscalls call do not do proper checks.

For example, you can simply trigger a Kernel DABT by running the following code:

sceIoDevctl(NULL, 0, NULL, 0, NULL, 0);

Confirmed in FW 2.10. FWs >=3.60 have proper checks.

sceAppMgrDestroyAppByAppId triggers kernel panic

Triggering a usermode exception immediately after calling sceAppMgrDestroyAppByAppId causes ?SceKernelThreadMgr? to get confused and trigger a kernel exception.

sceKernelCreateThread in thumb mode

SceKernelThreadMgr#sceKernelCreateThreadForUser checks the memory attributes to see if the entry point is executable, but in thumb mode, the function pointer always has bit 0 as 1, so if the entry point is the last 4-bytes of a memory page, then the next check fails and returns 0x80020006.

res = sceKernelProcIsPAWithinSameSectionForDriver(pid, memory_attr, entry, 4);

sceNetRecvfromForDriver 0xC0022005 error on kernel call

This is because the internal function always sets the is_user flag in the parameter, so setting the kernel memory pointer to data in sceNetRecvForDriver will result in an error in SceSysmem#sceKernelCopyToUserDomainForKernel or SceSysmem#sceKernelCopyToUserTextDomainForKernel.

// Offsets are for FW 3.60

// Patch by function hook
SceUID target = -1;
tai_hook_ref_t FUN_8100d5a8_ref;
int FUN_8100d5a8_patch(void *a1, void *a2, void *a3, int a4, void *a5, void *a6) {
	if (target == sceKernelGetThreadIdForDriver())
		*(int *)(a3 + 5 * 4) = 1; // 0:user 1:kernel 2~:kpanic
	return TAI_CONTINUE(int, FUN_8100d5a8_ref, a1, a2, a3, a4, a5, a6);
}


// Patch by code injection (recommended)
int patch_netrecv_0xC0022005(void) {
/*
        810067b2 c0 ef 10 00     vmov.i32   d16,#0              -> DD F8 30 C0   ldr ip, [sp, #0x30]
        810067b6 19 68           ldr        r1, [r3]
        810067b8 a2 60           str        r2, [r4, #8]
        810067ba da f8 0c 30     ldr.w      r3, [sl, #0xc]

        810067be c4 e9 07 55     strd       r5, r5, [r4,#0x1c]  -> C4 E9 07 C5   strd ip, r5, [r4,#0x1c]
        810067c2 a5 61           str        r5, [r4, #0x18]

        810067c4 e3 60           str        r3, [r4, #0xc]
        810067c6 61 62           str        r1, [r4, #0x24]

        810067c8 c4 ed 04 0b     vstr.64    d16, [r4,#0x10]     -> C4 E9 04 55   strd r5, r5, [r4,#0x10]
*/
	SceUID module_id;
	void *patch_point;
	char inst[0x20];
	module_id = sceKernelSearchModuleByNameForDriver("SceNetPs");
	module_get_offset(0x10005, module_id, 0, 0x67b2, (uintptr_t *)&patch_point);
	memcpy(inst, patch_point, 0x1E);
	memcpy(&(inst[0x0]), (const char[4]){0xDD, 0xF8, 0x30, 0xC0}, 4);
	memcpy(&(inst[0xC]), (const char[4]){0xC4, 0xE9, 0x07, 0xC5}, 4);
	memcpy(&(inst[0x16]), (const char[4]){0xC4, 0xE9, 0x04, 0x55}, 4);
	taiInjectDataForKernel(0x10005, module_id, 0, 0x67B2, inst, 0x1E);
	return 0;
}

Illegal alignment check of kernel allocator

Discovered on 2021-08-30 by Princess of Sleeping.

For example, if 0x880 is passed as the alignment argument of kernel malloc, the function will not return NULL.

This affects at least SceNetPs malloc and system malloc internal/external.

Ignored sceGUIDGetNameCore error propagation

Discovered on 2022-03-10 by Princess of Sleeping.

sceGUIDGetNameCore, which is called internally by sceGUIDGetName or sceGUIDGetName2, always returns 0 even if an error occurs in the function.

void unsafe_calling_example_1(void) {
    int res;
    const char *name;

    // Use some tricks to reach sceGUIDGetNameCore with invalid guid.
    res = sceGUIDGetName((invalid_guid | 1) & ~0xC0000000, &name);

    // res is always 0 even failed internally.
    // And sceGUIDGetNameCore initializes name with NULL, but if the internal check fails too early, name is not initialized and is undefined.
}

void unsafe_calling_example_2(void) {
    const char *name;

    // Use some tricks to reach sceGUIDGetNameCore with invalid guid.
    name = sceGUIDGetName2((invalid_guid | 1) & ~0xC0000000);

    // res is always 0 even failed internally.
    // And sceGUIDGetNameCore initializes name with NULL, but if the internal check fails too early, name is not initialized and is undefined.
    // If sceGUIDGetNameCore failed internally, name value is *(uint32_t *)(unsafe_calling_example_2_current_sp - 0x10)
}

void safe_calling_example_1(void) {
    int res;
    const char *name = NULL; // Initialize with NULL in advance

    // Use some tricks to reach sceGUIDGetNameCore with invalid guid.
    res = sceGUIDGetName((invalid_guid | 1) & ~0xC0000000, &name);

    // res is always 0 even failed internally.

    if(NULL == name){
        sceKernelPrintf("Failed %s\n", "sceGUIDGetName");
    }
}

void safe_calling_example_2(void) {
    int res;
    const char *name;

    // Add guid valid check
    res = some_guid_valid_check(invalid_guid);
    if (res < 0)
        return; // If invalid guid it, do not call sceGUIDGetName2.

    // Use some tricks to reach sceGUIDGetNameCore with invalid guid.
    name = sceGUIDGetName2((invalid_guid | 1) & ~0xC0000000);

    // name is always not NULL.
}

Kernel Boot Loader

Out of range access in SKBL

Discovered on 2022-01-20 by Princess of Sleeping.

To decode ARZL encoded Tzs SceSysmem, SKBL maps Compati SRAM (PA 0x1C000000) to Tzs VA with a size of 2MiB. It then calls SKBL#sceArlzDecode with an improper argument, thus using glitches during decoding to exceed 2MiB will pass the size check and access outside the range of the device, so it can trigger a Data abort exception.

Moreover, even if SKBL#sceArlzDecode returns an error code, it will be passed to the argument of SKBL#sceArlzArmFilter without being checked, so access for up to 0x80560201-bytes will occur.

if (sceKernelCpuId() == 0) {
  sceKernelMMUMapSections(*(void **)(param_1 + 0x60), 0x1061D007, 0xC, 0x1C000000, 0x200000 /* mapping size */, 0x1C000000);
  res = sceArlzDecode(0x1C000000 /* dst */, 0x1000000 /* dst max size */, &ARZL_encoded_SceSysmem[4] /* src */, NULL);
  size = sceArlzArmFilter(0x1C000000, res, 0);
  g_Tzs_SceSysmem_start_address = 0x1C000000;
  g_Tzs_SceSysmem_end_address = 0x1C000000 + size;
}

It is currently just a bug as no glitching has been tried and as a Data abort exception is not useful.

Non-Secure Kernel Boot Loader (NSKBL)

Null dereference in the NSKBL kernel panic handler

(2021/06/19 by Princess of Sleeping) The kernel panic handler accesses the SceSysroot pointer, but since that pointer is set to NULL during early boot, NULL access to SceSysroot occurs.

CelesteBlue: If I understood correctly, this means that as long as NSKBL is running, a non-secure Kernel panic from any cause will end up in a DABT exception at NSKBL level.

Present in FW 3.600.011.