Table of Contents
- 1. Introduction
- 2. Userland to Kernel
- 3. Gaining Kernel Privileges
- 4. Recap: The Complete Exploit Chain
- 5. Conclusion
- 6. Webography and further reading
Introduction
Have you ever felt that a system was too limited considering the hardware it runs on ?
This frustration has led many people to investigate through the devices they own.
This process is commonly referred to as a Jailbreak, like an access to freedom.
Modern operating systems enforce separation between user applications and the kernel.
These applications execute their code in their own sandboxed environment meaning that they cannot directly access hardware or modify the operating system.
These operations are reserved for the kernel which has the highest privileges on the system.
Executing code inside the browser or an application is only the first step. To fully jailbreak the console, we must escape from this sandbox and gain kernel privileges through another vulnerability.
In this article, we will follow the privilege escalation path from a userland WebKit exploit to kernel code execution through the Lapse AIO vulnerability.
We first explore how arbitrary code execution is achieved in userland.
Prerequisites
This article is aimed for students, engineers with a basic understanding of operating systems and security concepts. To fully benefit from this article, it’s recommended to be familiar with the following concepts:
- JavaScript understanding
- Following C Language concepts :
- I/O operations
- Double Free
- Threads
Precisely, you know what is a Promise in JavaScript additionally to the understanding of the language syntax.
In C, you know how to use malloc and free functions, what is a double free and what is a thread and how it works.
Additionally, you can have a basic understanding of the Return Oriented Programming (ROP) technique which is not well explained in this article.
Userland to Kernel
When looking at the PS5 home screen, we can ask ourselves how can we even run code here. To allow code execution, we need to an entry point into one of the console’s applications.
In the console, the YouTube application is present by default and can be a great entry point to run our code. The fact is when you are in an old firmware version, it is not very convenient to install a new application (can be done with some USB restoration). Moreover, this application uses the V8 JavaScript engine through the app allowing us to run JavaScript code as explained bellow.
The Y2JB (YouTube to Jailbreak) is one implementation of the complete exploit chain where we can follow the path to understand how the exploit works.
The Entry Point: Running our code
It has been found that the application cache integrity is not properly checked allowing cached files to be modified.
By modifying the splash.html file, we can create a <script> tag to load our own JavaScript code, that represents our first entry point.
But that is not sufficient as executing code through the JavaScript engine of the application is very limited, native code execution within the process is still required.
TheHole
Achieving native code execution first requires escaping ourselves from this JavaScript sandbox.
This is achieved by exploiting an incoherent state of the V8 JavaScript engine.
The great article from Starlabs provides a snippet implementation around the CVE-2021-38003.
It exploits an issue in the JSON.stringify function with a large string that is repeated multiple times in an array.
This code shows how we can trigger a TheHole value by using this vulnerability (explication of it just after !):
|
|
The trigger function returns a TheHole object that is a sentinel value used by the V8 engine to represent a hole in an array, for example.
When we see an undefined value in JavaScript, it is actually a TheHole value in the engine.
The point here is that this TheHole value is not supposed to be leaked outside the engine but the vulnerability allows us to do so.
Y2JB uses a combination of CVE-2021-38003 and CVE-2022-4174 where the main difference is that the trigger function relies on a vulnerability in the Promise.any function which is faster than the previously seen one.
Here is the trigger function that is used in Y2JB:
|
|
By injecting the leaked TheHole value into a Map, it violates the engine invariant and corrupts the Map metadata, causing its size to become -1 :
|
|
It can be explained as follow :
- Set (1, 1) and (hole, 1) in the map. Now element count = 2, bucket count = 2.
- Delete (hole, 1). Now element count = 1, bucket count = 2.
- Delete (hole, 1) again. Now element count = 0, bucket count = 2. Since element count < bucket count / 2, it will shrink the map and remove the hole values.
- Now there’s no hole value in the map, so we can’t delete (hole, 1) anymore. However, there’s still (1, 1) in the map, so we delete that entry. This will decrease element count by 1, making element count ( = map.size ) equals -1.
Note that we can delete the hole value multiple times because in the delete implementation of the V8 engine, when it finds the entry to delete it will set the entry to TheHole value.
By having access to this value, we can corrupt the map’s size.
OOB read/write and primitives
The next steps involve creating an OOB (Out-Of-Bounds) read/write by manipulating the structure of the Map.
This will allow us to construct primitives to modify the native memory of the process itself.
In the following code snippet, we construct the addrof primitive, which allows us to get the address of an object in memory.
As we are not diving to explain how we perform to retrieve this primitive here, you can refer again to the Starlabs article for more details where they demonstrate how to execute a shell command in a vulnerable chrome version with some few primitives.
|
|
Libkernel and ROP
Now that those primitives provide us a complete visibility over the process’s address space, we need to locate the PS5’s libkernel library. You can think of libkernel as the PS5’s equivalent of the libc library on Linux as it is the userland library that wraps system calls. Locating libkernel is therefore mandatory for the next step, since every kernel interaction from the userland has to go through this layer.
Gaining the possibility to call those syscalls are mandatory to perform it.
To get the address of the libkernel, we must be careful about the Address Space Layout Randomization (ASLR) that prevent us to hardcode this address after finding it by reverse engineering the PS5 firmware.
Thus, we know the offset of each syscall based on the library base address depending of the YouTube application version linked to the firmware version.
By using leaked pointers, we can determine the base address of shared libraries like as follow :
|
|
The last step that will allow us to call those syscalls is to build a Return Oriented Programming (ROP) chain. This technique chains together assembly instructions that already exists in the memory to perform arbitrary operations without injecting code.
Userland Exploit Chain Schematic
The end of the userland exploit chain is expressed by the Remote JS Loader, which allows us to load our own JavaScript code in the current context from a remote server by this line : await load_localscript('remotejsloader.js');
We can mention the fork of the Y2JB project like Y2JB autoloader which runs automatically the kernel Lapse exploit or other ones if there is for the detected firmware. Those forks can also provide many features.
Here is a schematic representation of the userland exploit chain:
As explained before, the code execution through userland is not sufficient to jailbreak the console as we must gain kernel privileges. The understanding of the vulnerability that we are going to explain requires a basic understanding of the AIO subsystem.
Gaining Kernel Privileges
The AIO subsystem
Today’s operating systems constantly perform I/O operations, whether reading a file, writing to disk, etc. Those operations are blocking the calling thread until completion which have a significant impact on performance.
The AIO (Asynchronous input/output) subsystem is a solution that the PS5 kernel implements allowing a process to submit one or more requests to the kernel.
This asynchronous behavior allows the application to continue its execution without waiting for the I/O operation to complete.
But why do we need to understand the AIO subsystem to perform the kernel exploit ?
Because the exploit that has been found is located in the aio_multi_delete function.
This function is used to delete multiple requests as one operation.
This function allows us to dive in the heart of the exploit with the vulnerability that it contains. Here is a summary of the bug from the PS5 Dev Wiki:
|
|
Have you seen it ?
As the commentary explains, no lock is acquired when rem_ids != 1, meaning that every write operation on the obj structure is not protected against concurrent access from other threads.
The id_rlock function is used to acquire a read lock but the missed write protection allows two concurrents threads to free the same ao_entries[req_idx].
Race Condition
For a better understanding of the race condition, we can imagine two threads running concurrently as follow :
| Thread 1 | Thread 2 |
|---|---|
id_rlock(): read lock acquired |
- |
| - | id_rlock(): read lock acquired |
| rem_ids = obj->ao_rem_ids = 3 | - |
| - | rem_ids = obj->ao_rem_ids = 3 (same value) |
obj->ao_rem_ids = --rem_ids; (=2)free_queue_entry(); –> FIRST FREE obj->ao_entries[req_idx] = NULL; |
- |
| - | obj->ao_rem_ids = --rem_ids;free_queue_entry(); –> SECOND FREE |
If you have already done some C programming with malloc and free, you may have already core dumped your program by freeing the same pointer twice.
Let’s see why this AIO vulnerability is not doing the same thing.
Understanding Double Free Vulnerability
This proof of concept (PoC) that you can find on this public GitHub repository is a great example to understand the double free vulnerability and how it can be exploited.
|
|
By compiling and running the code we obtain the following output:
|
|
This example shows that by understanding how the allocation and deallocation of memory works, we can control the content of the memory that has been freed and allocated again.
Here we are overwriting the command variable with the content of the username variable, which will be executed by the program.
Explanations:
When a chunk of memory is freed, it is added to a free stack to keep track of available memory which is later used to quickly find memory chunks that can be allocated for other values. So here is the states of this stack after each main operation:
free(a)
Head -> a -> Tail
free(b)
Head -> b -> a -> Tail
strncpy(a, "text", 50)
This operation does not modify the free stack but corrupts the metadata of the a chunk to allow us to free it again.
free(a)
Head -> a -> b -> a -> Tail
Now that we have this stack arrangement, we know that that every allocation will return the first chunk of memory available in the stack.
char* command = malloc(50)
Head -> b -> a -> Tail
char* some_pointer = malloc(50)
This crucial and unused variable helps us to pop the b chunk from the stack.
Head -> a -> Tail
char* username = malloc(50)
Head -> Tail
This manipulation leads to an equality of pointer addresses between command and username.
Here we call username an aliased pointer because it points to the same memory location as command.
In the case of Lapse, the double free is of course more complex than this simple POC but the ideas are similar as the vulnerability allows us to free the same memory twice and then allocate it again to control the content of this memory.
Steps to Kernel R/W
The work after finding and understanding this main vulnerability requires some non-trivial steps to finally allow us to perform kernel read/write. To stay concise, we will not dive into the details of each step but the entire Lapse exploit is a ~1850 lines of code that you can find here : Y2JB lapse.js
As a summary, the code expresses the following 5 stages :
- Stage 1: Double Free
By using the previously explained aio_multi_delete double free vulnerability, it leaves a dangling kernel heap chunk which is used by some sockets options producing an aliased pair of sockets.
- Stage 2: Kernel address leak
The aliased sockets are used to confuse the kernel by treating the chunk as an event flag object and revealing raw kernel pointers by reading through them.
- Stage 3: Double Free on SceKernelAioRWRequest
A second double free is performed with aio_multi_delete where the dangling chunk lands inside an IPV6 structure.
This will create a new aliased socket pair at a known kernel address.
- Stage 4: Arbitrary kernel R/W
This second aliased socket pair is exploited through specific socket options to read and write at any kernel address giving us a full arbitrary kernel read/write primitive.
- Stage 5: Final one, PS5 Jailbreak
On this last one, we can clearly see that we are writing in the kernel space to give us permissions. The following Y2JB code shows that we are writing our root access, maximal Sony’s rights, etc:
|
|
Recap: The Complete Exploit Chain
After diving into the entire path from userland to kernel, we can locate the different steps that are performed, from the entry point with the YouTube cache and the splash.html file to the final kernel R/W by using two main vulnerabilities, the V8 Promise.any to leak a TheHole value and the aio_multi_delete double free vulnerability.
Here is a schematic representation:
Conclusion
This escalation privilege that we just saw is one of the exploit/issue that has been found. It is part of the PlayStation HackerOne Bug Bounty program where hackers can report vulnerabilities and exploits to Sony. This program allows Sony to fix those issues and reward hackers for their work. Once the vulnerability is reported, Sony releases a new firmware version to fix it and avoid huge losses by forcing users to update their consoles.
Indeed, every user that wants to use this kind of exploit must be on a firmware version that is vulnerable to it meaning having an old console or waiting for a new exploit to be found making the console almost useless until it is found.
Note that the path that we have followed is working up to the firmware version 10.01 as the aio_multi_delete vulnerability has been fixed in the next firmware version 10.20.
Moreover, when having kernel privileges, we can run any legally obtained application, having root access to the file system and many more but can’t run Linux for example as we need the hypervisor privileges.
You can look at the PS5 exploits states (06/30/2026) to see what is available for every firmware version.
About the legal status of jailbreaking: it is legal in France to modify a console that you own but it is illegal in the US for example. Policy around it is very different depending on the country and the laws that are applied.
We can add that this Jailbreak voids the warranty of the console, that you have not access to any PlayStation Network services or features and that some bad manipulations can lead to a bricked console. The risk to be banned from the PlayStation Network is also present although not big because of restrictions about online features.
At the end, this one on many others exploit chain is an interesting and great example to learn about privilege escalation and some specific vulnerabilities that can be found in modern operating systems. You can have a look at the list of vulnerabilities at PS5 Dev Wiki - Vulnerabilities. We can see it as a good way to learn by having fun manipulating a console as our own wish.
Webography and further reading
- Y2JB GitHub ❤️
- Starlabs CVE-2021-38003 explanation and example ⭐
- Understanding Double Free Vulnerability 👍
- Modded Warfare Channel - News in video about Sony Jailbreak ❤️]
- Y2JB Autoloader
- PS5 Exploit Chains
- PS5 exploits states (06/30/2026)
- PS5 Dev Wiki - Vulnerabilities
- TheHole leak with Promise.any
- Understanding ROP (French)
- Jailbreak Legal Status (French)
- Sony HackerOne
- CVE-2021-38003
- CVE-2022-4174