Switch 2 Jailbreak: The Exploit Chain That Beat Secure Boot
The Switch 2 jailbreak chained a BASIC interpreter overflow, an fs path bug, and a loader race. What it teaches about exploit chains and secure boot.

A fresh console ships with a hardened microkernel, pointer authentication on every return address, execute-only memory, control-flow integrity, and a signed boot chain. Two weeks later, a hobbyist is running arbitrary native code as the filesystem service — the most trusted process on the box. That's the story of the first public Switch 2 jailbreak, and it should make every engineer who builds secure systems a little uncomfortable. Not because the mitigations failed — most of them worked exactly as designed — but because the exploit never needed to beat them head-on. It went around them, one logic bug at a time.
I've spent years on the defensive side of infrastructure security, and the pattern here is one I've seen in production incident reviews more times than I'd like: a chain of individually unimpressive bugs, each one bought just enough privilege to reach the next. The initial foothold wasn't even in the Switch 2 itself. It was a memory corruption bug in a Switch 1 game — the SmileBASIC interpreter — running in backward-compatibility mode. From there the chain walked up through the filesystem service, abused a race in the program loader, and ended with the ability to run native code under any sysmodule's identity. Every link deserves a look, because each one maps to a mistake I've watched teams make in server-side systems too.
Why the Browser Door Was Closed — and the Game Door Wasn't
WebKit is the classic first target for console jailbreaks, and the researcher did get a foothold there via a captive portal and an n-day. But it dead-ended fast. The Switch 2's browser stack is wrapped in three mitigations that compose brutally well: execute-only memory (XOM) means even with arbitrary read/write you can't locate useful code gadgets; pointer authentication (PAC) means you can't forge a code pointer without a signing oracle; and CFI means even a validly signed pointer won't pass type checks at most call sites. Breaking any one of those is a research project. Breaking all three, chained, from a JavaScript sandbox, is a career.
So the attack pivoted to the weakest tenant on the machine: third-party Switch 1 games. The reasoning was cold and correct. Game binaries are built by studios, not by Nintendo's security team, so they're far less likely to be compiled with the full mitigation stack — and the Switch 1 never had PAC at all, so anything running in its compatibility environment inherits that softness. Better still, the Switch 1's root of trust was compromised years ago, which means its game code can be decrypted and audited offline. SmileBASIC, a giant C++ BASIC interpreter that had already been exploited on the 3DS, was the obvious soft target.
There's a lesson here that generalizes beyond consoles: your security posture is the security posture of the least-hardened code you agree to run. Backward compatibility is an attack surface you inherit, not one you choose. The Xbox's security model learned this the hard way in the 2000s — saved-game exploits in unhardened titles were the wedge that cracked a much tighter kernel, a story we covered in what the original Xbox hack teaches about hardware security models. Twenty years later, the same move still works.
The Integer Overflow That Opened the First Door
The SmileBASIC bug is a textbook integer overflow in bounds checking — the kind of thing every secure-code review checklist says to catch, and the kind that keeps shipping anyway. The interpreter's array-removal command validated its arguments like this:
// intent: ensure the removed range is inside the array
if (position + count <= array.length) {
memmove(backing + position * 8, // start of the gap
backing + (position + count) * 8, // elements after the gap
(length - position) * 8); // bytes to copy
}
Call REMOVE R, &H60000000, &H3FFFFFFF on a four-element array and position + count wraps to 0x9FFFFFFF, which as a signed 32-bit value is deeply negative — comfortably "less than" the length. The multiplications by 8 wrap too, and you end up executing memmove(backing, backing - 8, 32). The command prints an out-of-memory error afterward, but the copy already happened. And here's the lovely part: the 8 bytes immediately before any SmileBASIC array hold a pointer to the array itself. After the removal, R[0] is interpreted as a string whose length field is actually that pointer — an astronomically long string you can read and write through. Arbitrary read/write, gift-wrapped by the interpreter's own memory layout.
If you write C or C++ for a living, stare at that check for a minute. position + count <= length is the single most common bounds-check idiom in systems code, and it's wrong whenever the sum can overflow. The correct form is position <= length && count <= length - position, or checked arithmetic. I've found variants of this bug in a packet parser at a previous job and in an open-source storage engine. It survives code review because it looks obviously right. This is the specification-implementation gap in miniature — the spec says "validate the range," the implementation validates something adjacent to the range, and the difference is exploitable. We've written about how the specification-implementation gap is where bugs live; console interpreters are just where they get found by motivated teenagers instead of fuzzers.
Escalating to the Filesystem Service With a Path-Handling Overflow
Userspace read/write inside a game process buys you nothing on its own — the Switch's microkernel design means every service is a separate process with its own IPC surface. The valuable target was fs, the built-in filesystem sysmodule, for a deliciously recursive reason: fs is the component that serves decrypted code to the loader. Own fs and you can dump every other sysmodule's code for offline analysis. The fortress's own quartermaster hands you the blueprints.
The bug was a heap overflow in path normalization. The GetEntryType IPC command normalizes its path argument, and paths containing a parent-directory component with a backslash — /..\ — route into a helper that allocates a fixed 0x301-byte buffer and then copies the path into it without an adequate length check. A sufficiently long path overflows the buffer into adjacent heap objects. The researcher's own commentary on discovering this was one word: "lol."
That "lol" should sting, because path normalization is exactly the sort of unglamorous plumbing code that never gets the security scrutiny the crypto gets. The exploit turned the overflow into full arbitrary read/write by corrupting a neighboring cache object whose dirty bit redirects file I/O through an attacker-controlled pointer — a clean heap-grooming primitive. Then, with memory scanning, the exploit found its own process's connection object in fs's tables and rewrote the stored process ID to 1, granting the game process "FullPermission" over the filesystem. Note what just happened: not a single cryptographic check was broken. The permission model was enforced by data in memory, and the attacker could write memory.

Beating the Signature Checks by Swapping the Filesystem Mid-Flight
The cleverest link in the chain is the one that defeats code signing without touching a single key. The program loader (ldr) verifies two signatures before running anything: Nintendo's signature over the process-permission metadata and developer key, and the developer's signature over the content archive's header hash. In theory, that hash transitively fixes every byte of the executable. In practice, there's a gap: it's fs's job to make sure the filesystem it serves actually matches the hash it already reported — and the attacker had arbitrary read/write inside fs.
So the exploit waits until the loader has requested the executable filesystem and fs has already handed over the valid hash and signature, then swaps the filesystem object behind the loader's IPC handle for a crafted one that serves the genuine signed metadata file but an arbitrary replacement executable. The signature checks pass because they were checks on bytes the attacker never needed to modify. The timing is enforced by rewriting the handle fs waits on for the loader's connection to an event the attacker controls from the game process — turning a race condition into a deterministic interposition.
The cryptographic chain was sound from header hash to executable contents — on paper. The one link that existed only as a runtime assumption, 'the process serving the files is honest,' was the link that broke.
This is a time-of-check/time-of-use bug wearing a trench coat, and it's the most transferable lesson in the whole write-up. Anytime component A validates data and component B later acts on it through a mutable reference, you have a swap window. I reviewed an internal deployment system once that validated a container image digest, then pulled by tag from the registry at rollout time — same bug, different hat. The fix in both cases is the same: validate at the point of use, or hand the consumer an immutable, verified object rather than a handle to something it has to trust a third party to keep honest. Defensive design that assumes every intermediary is compromised isn't paranoia; on a machine the attacker can already partially write, it's just accurate modeling. That's the core argument in defensive engineering for systems that resist catastrophe, and this exploit is a clean case study.
Borrowing a Kernel Handle and Patching fs From the Inside
The final trick is small and beautiful. To patch the fs process's own code, the exploit needed a kernel handle to it. It got one by overwriting a 4-byte handle field in memory with the magic value 0xffff8001, which the kernel interprets as "the current process's handle." The game process called a benign API, received its own process handle back, shipped it to a rogue process running with the code-loading sysmodule's permissions, and that process used legitimate kernel calls to map and patch fs's code. Every individual operation was authorized. The composition was a full compromise of the most trusted process on the device.
If that sounds familiar, it's because confused-deputy problems are everywhere once you start looking: sudo rules that permit a benign binary with a writable config, cloud IAM roles that can pass themselves to a more powerful service, build systems that sign whatever the CI job hands them. We covered the privilege-escalation side of this pattern in the history of Unix privilege escalation — the mechanisms change, the shape of the bug doesn't. Handles, capabilities, and tokens are only as strong as the integrity of the memory that stores them.
What Secure-Boot Designers Keep Getting Wrong
Step back and the pattern across the whole chain is consistent, and it's the same pattern behind most serious enterprise breaches I've reviewed: the verified core held, and everything around it didn't. Nintendo's boot ROM, signature scheme, and kernel mitigations were fine. What fell was an interpreter bounds check, a path-normalization buffer, a permission field stored in writable memory, a TOCTOU window between two trusted services, and a handle table that took kernel pointers from userspace-writable structures. None of those are exotic. All of them are the sort of thing that survives review because each looks locally reasonable.
- Audit the boring code. Path normalizers, length checks, and handle tables are where exploit chains actually start, not the crypto. Budget review time proportional to attacker reachability, not algorithmic sophistication.
- Treat every mitigation gap as a roadmap. If one process on the system lacks PAC/CFI — a compatibility layer, a third-party binary, a legacy interpreter — assume it's the entry point and model what it can reach.
- Eliminate trust-by-position. A component that validates data and hands a mutable handle downstream has created a swap window. Pass immutable, self-verifying objects or re-verify at use.
- Assume writable memory anywhere is writable memory everywhere. Permission flags, PID fields, and handle values stored in attacker-reachable memory are part of your attack surface, not your enforcement mechanism.
The other uncomfortable takeaway is the tooling. The exploit was developed with an AI agent given physical access to the hardware through KVM-over-IP devices — video in, controller input over USB, and a Bluetooth lever gadget to press the power button after each crash. Multiple agent instances worked different attack surfaces in parallel, around the clock, for two weeks. Whatever you think of AI-assisted security research, the economics of vulnerability discovery on closed hardware just changed. Bugs that needed a patient expert now need a patient expert who can supervise three tireless assistants. If your threat model assumed "nobody will bother reverse-engineering this sysmodule," that assumption expired this month.
The Chain Is the Product
It's tempting to read a jailbreak write-up as a list of Nintendo's mistakes. Read it instead as a mirror. Every system I've ever helped defend had an equivalent of SmileBASIC (the legacy component nobody hardened), an equivalent of the path buffer (the plumbing code that outgrew its assumptions), and an equivalent of the loader race (the validation that trusted a mutable intermediary). The attackers' advantage isn't brilliance at any single step — it's that they think in chains while defenders think in components.
So the exercise for your team this week isn't "could someone forge our signatures." It's: pick your most trusted internal service, assume the attacker already has arbitrary write in the least-hardened process that talks to it, and walk the chain forward. Where does the first escalation land? What does that identity let them reach? You'll find your own fs sysmodule soon enough. Better you than someone with a KVM, a Bluetooth lever, and two weeks of patience.


