Syscalls

From Vita Development Wiki
Jump to navigation Jump to search

See also SVC.

The normal syscall table is allocated at the start of SceSysmem with enough space for theoretical maximum of 3840 syscalls. Because of randomization, many slots are unoccupied and will not be used. Normal syscalls are numbered from 0x100 to 0xFFF. "Fast" syscalls (run with interrupts disabled) are reserved in slots 0 to 0xFF. However, as of System Software 1.69, there has been no observed usage of fast syscalls. If a syscall is not imported by any running processes on the system, it is not callable regardless of if it is exported by the Kernel.

Syscalls allocation

Syscall slots are assigned to imported functions of usermode modules when the usermode modules are started. The few kernel modules started before SceKernelModulemgr have their slots assigned after that SceKernelModulemgr starts, hence why SceSysmem#SceSysmem#SceSysrootForDriver callbacks exist. However, the syscall virtual table itself is not written to with pointers until a first usermode module imports this syscall. After the syscall virtual table is written to, however, a syscall virtual address is not erased from the table until after the module is unloaded.

On SceKernelModulemgr startup, the syscall table is allocated and every syscall slot points to a placeholder function that returns SCE_KERNEL_ERROR_NOSYS. When an entry is added, the function pointer is overwritten with the address of the exported function. When an entry is removed, the address of the placeholder function comes back. This way, upon syscalls, the kernel does not need to check if the slot is pointing to a valid function.

There is a global counter keeping track of the "top" of the list of syscall slots. When a new module is started (after it is loaded), this counter is first incremented for a random value between 1 and 5 (unless incrementing it will overflow). Next, for each exporting entry, the syscall number is assigned by looking at the slot pointed to by the "top" pointer. If it points to an allocated slot, it will increment (with wraparound) until it finds an unallocated slot. If all slots are allocated, it returns error SCE_KERNEL_ERROR_MODULEMGR_SYSCALL_REG. Once it finds an unallocated slot, it writes the slot number to a buffer in the module information structure in kernel memory and increments the top pointer.

When a user application/library imports a kernel syscall, the resolver looks to see if the slot assigned to that function is written to yet. If it is not, the kernel will fill the assigned slot with the exporting function. (This is done at the time the user library starts, not when it is loaded to memory).

When a library is unloaded, any module with exporting functions will have all the slots erased (replaced with the placeholder function). It knows what to erase based on the slot assignment information.

Syscall ID estimation

Syscall numbering depends on both the random increment per each module load and also the order in which modules are loaded. The actual order of module loads is dependent of the order in which the user decides to open applications. Therefore, lower syscall numbers are more likely to be close upon each reboot. Because of the way syscall slots are allocated, for a given module the syscalls are arranged in NID order. Estimation of syscalls that are not imported by the current running module only requires knowledge of the relative position of that exported function and the location of any other syscall from that module.

To estimate syscalls, another useful way is to note the error code returned by syscalls. Many functions return specific error codes that other functions do not. In case of ambiguity, you can abuse the fact that syscalls next to each other do not change position, so some set of inputs to each syscall in order should return the same sequence of error codes. With good inputs, you can predict with high probability the syscalls for a certain module.

Bugs and Vulnerabilities

See syscalls related bugs in Bugs#Kernel and Vulnerabilities#Kernel.