Introduction
A while ago, we discovered an interesting vulnerability in the GPU’s drivers of iPhones, iPads, and macOS computers with M-series chips. Dubbed ShadyShader, this flaw allows a specially crafted shader program to overwhelm Apple’s GPU, causing repeated freezes that ultimately lead to a system crash.
Apple issued CVE-2023-40441 and addressed this vulnerability last year, with improved input validation for the GPU drivers.
What’s a Shader and Why It Matters
First off, let’s break down what a shader actually is. A shader is a small program that runs on your device’s GPU and dictates how graphics are rendered. It controls everything from lighting in video games to textures in 3D applications. Unlike features like Bluetooth, the microphone, or the camera, which typically require user approval, GPU access happens silently without needing user consent. Browsers like Safari, Chromium, and Firefox use a system called ANGLE (Almost Native Graphics Layer Engine) to translate shaders written in GLSL (OpenGL Shading Language) into platform-specific languages, such as Metal Shading Language (MSL) on Apple devices. This allows consistent WebGL rendering across different hardware and operating systems.
On Apple devices, when WebGL content is loaded, GLSL shaders are converted to MSL behind the scenes. The ShadyShader vulnerability exploits this process by sending a specially crafted shader program that bypasses common GPU optimizations on Apple’s M-series chips, leading to GPU resource exhaustion and ultimately crashing the system.
The Exploit
ShadyShader is a deliberately crafted shader program that aims to overwhelm a GPU’s processing capabilities. Unlike infinite loops—which modern GPUs can typically detect and handle gracefully—this shader employs computational tasks that are difficult for the GPU to distinguish from legitimate, resource-intensive workloads.
In the shader code example below, deeply nested loops create an enormous computational burden. These loops iterate a vast number of times before finally assigning a value to gl_Position. This design forces the GPU to perform an immense number of calculations, significantly delaying the rendering process and consuming substantial processing time.
Finding the right balance between the number of iterations, the depth of nested loops, and the size of the canvas is crucial. If the shader’s workload exceeds a certain threshold, the GPU might fail to process it entirely.
While modern GPUs are proficient at detecting and stopping obvious infinite loops, ShadyShader avoids detection by using a finite but exceedingly large number of iterations. This creates a heavy yet legitimate workload that causes the GPU to exhaust its resources trying to meet the condition for setting gl_Position, which only occurs after an immense amount of calculations.
Because the shader is technically performing valid operations within these nested loops, GPU drivers cannot easily differentiate between this excessive workload and a normal, heavy rendering task. The drivers do not recognize that the shader is unnecessarily monopolizing resources. This overwhelms the GPU to the point where it can no longer manage other tasks, eventually crashing the system.
How It Affects Apple Devices
When the GPU is stuck processing ShadyShader, it causes the system’s display management service to wait for the GPU to finish its task. On macOS, this service is called WindowServer, while on iOS, it’s known as SpringBoard. Both are responsible for managing everything you see on your screen. If they can’t get updates from the GPU, the entire system becomes unresponsive.
Here’s the typical sequence of events when ShadyShader is executed, on both macOS and iOS:
- Shader Execution: The malicious shader runs on the GPU, initiating heavy computational work through nested loops. Whether on macOS or iOS, the GPU gets overwhelmed trying to process this task.
- Display Management Stalls: Since both WindowServer (macOS) and SpringBoard (iOS) depend on the GPU for rendering, they stall while waiting for the shader to finish. During this time, the system appears frozen.
- Watchdog Timer Activates: Both macOS and iOS have built-in watchdog timers that monitor critical processes like WindowServer and SpringBoard to ensure they don’t become unresponsive for too long.
On macOS, if WindowServer doesn’t respond in 120 seconds, the watchdog triggers a kernel panic, forcing the system to crash and reboot. On iOS, the watchdog timer typically reacts faster (within 30 seconds). If SpringBoard becomes unresponsive, iOS might first try to terminate the misbehaving app.
In our testing however, Macbooks experience a full reboot within 1-2 minutes, while iOS devices remain unresponsive anywhere from 3-6 minutes before presenting the lock screen, without performing a full reboot in most cases.
Recursive DoS Using Browser Recovery and Caching
While ShadyShader on its own can temporarily crash a device, the attack can be made far more persistent using a combination of browser recovery functionality and caching. Here’s how it works:
After the device reboots from the initial crash, the browser automatically tries to reopen previously accessed tabs, especially if it’s set to restore the last session. Since the malicious page with ShadyShader is in the cache, the GPU is again forced into an infinite loop as soon as the browser starts up, causing another crash. This creates a recursive DoS attack where the device keeps crashing each time the browser is launched.
In this scenario, unless the user disables JavaScript execution on their browser before opening it, their device would keep crashing every time the browser is opened.
How a Text Message Can Freeze Your Apple Device
The ShadyShader vulnerability can be exploited on a small scale through methods like text messages, emails, or QR codes that contain links to malicious webpages. When the user clicks the link, their browser loads WebGL content with the malicious shader, causing the device to freeze or crash as the GPU becomes overwhelmed.
In these attacks, the device may either reboot or remain unresponsive for several minutes. If the browser attempts to restore the last session after a crash, the user may be trapped in a crash loop, making the attack more persistent. Disabling JavaScript or avoiding suspicious links can help mitigate this vulnerability until the device is patched.
Using Captive Portals for Wide-Scale Impact
The vulnerability can also be exploited in public networks using captive portals. Captive portals are the login pages or redirects you often encounter when connecting to public Wi-Fi networks (like in airports, cafes, or hotels). An attacker can set up a Wi-Fi network with a captive portal that contains the malicious shader. When users connect to the network, they are automatically redirected to this page before gaining internet access without even opening the browser.
Impact on Other Devices
While Apple devices were uniquely affected by this attack, we observed interesting behaviors on other devices as well, notably on Google Pixel phones. Although these devices did not crash, the browser app became unusable until the user restarted the phone.
During an opportunistic test on a Tesla vehicle, we observed temporary unresponsiveness of the main screen, although critical driving functions remained unaffected. While we have not tested all possible impacts, we estimate that any system with a GPU and a browser could be similarly affected.
Final Thoughts
Users should update to the latest versions of iOS and macOS which address the ShadyShader exploit. However, the underlying issue of GPU resource exhaustion remains in our view and could be exploited in future attacks.
If your device is stuck in a crash loop from such an attack, disable JavaScript in the Settings app before reopening the browser. This should allow you to close the problematic tab.
Try Imperva for Free
Protect your business for 30 days on Imperva.