Switch System Flaws
System Flaws are used to execute unofficial code (homebrew) on the Nintendo Switch. This page is a list of known and public Switch System Flaws.
List of Switch System Flaws
Hardware
Summary | Description | Fixed with hardware model/revision | Newest hardware model/revision this flaw was checked for | Timeframe this was discovered | Public disclosure timeframe | Discovered by |
---|---|---|---|---|---|---|
GMMU DMA attack | The Switch's GPU includes a separate MMU (GMMU) that is allowed to bypass the system's IOMMU (SMMU). By accessing the GPU's MMIO region and manipulating the page table entries in the GMMU, an attacker can read/write any portion of the DRAM (except memory carveouts). | None | HAC-001 | Summer 2017 | December 28, 2017 | hexkyz, SciresM and qlutoo |
Weak Security Engine context validation | The Tegra X1 supports a "deep sleep" feature, where everything but DRAM and the PMC registers lose their content (and the SoC loses power). Upon awaking, the bootrom re-executes, restoring system state. Among these stored states is the Security Engine's saved state, which uses AES-128-CBC with a random key and all-zeroes IV. However, the bootrom doesn't perform a MAC on this data, and only validates the last block. This allows one to control most of security engine's state upon wakeup, if one has a way to modify the encrypted state buffer.
With a way to modify the encrypted state buffer, one can thus dump keys from "write-only" keyslots, etc. |
None | HAC-001 | December 2017 | January 20, 2018 | SciresM and motezazer |
System software
Stage 1 Bootloader
Summary | Description | Successful exploitation result | Fixed in system version | Last system version this flaw was checked for | Timeframe this was discovered | Public disclosure timeframe | Discovered by |
---|---|---|---|---|---|---|---|
Null-dereference in panic() | The Switch's stage 1 bootloader, on panic(), clears the stack and then attempts to clear the Security Engine. However, it does so by dereferencing a pointer to the SE in .bss (initially NULL), and this pointer doesn't get initialized until partway into the bootloader's main() after several functions that might panic() are called. Thus, a panic() caused prior to SE initialization would result in the SE pointer still being NULL when dereferenced. This would cause a data abort, causing the bootloader to clear the stack and then try to clear the security engine...dereferencing NULL again, over and over in a loop.
In 3.0.0, this was fixed by moving the security engine initialization earlier in main(), before the first function that could potentially panic(). |
Infinite clear-the-stack-then-data-abort loop very early in boot, before SBK/other keyslots are cleared. Probably useless for anything more interesting. | 3.0.0 | 3.0.0 | Early July, 2017 | July 30, 2017 | Everyone who diff'd 2.3.0 and 3.0.0 Package1 |
FUSE_DIS_PGM not written by package1 | The switch's hardware fuse driver contains a write-once bit in a register called "FUSE_DIS_PGM", which disables burning fuses until the next reboot. While Nintendo's bootloader code for waking up from sleep writes this on all firmware, the actual package1 initial bootloader forgets to write to it on cold reboot.
This isn't too big of a problem because another fuse is burnt on retail devices (production mode), which prevents burning *all* fuses other than ODM_RESERVED ones in hardware. This was fixed in 3.0.0 by writing to the register on cold boot (although the write happens in TZ instead of package1 where it should take place, possibly to obfuscate the fact that they made this mistake). |
Burning arbitrary ODM reserved fuses with TZ code execution, which should never be possible for non-bootloader code.
Warning: one could irreparably brick one's console by playing with this. |
3.0.0 | 3.0.0 | Late summer/early fall 2017 | December 31, 2017 | SciresM, motezazer |
TrustZone
Summary | Description | Successful exploitation result | Fixed in system version | Last system version this flaw was checked for | Timeframe this was discovered | Public disclosure timeframe | Discovered by |
---|---|---|---|---|---|---|---|
Non-atomic mutexes | When an SMC is called, TrustZone sets a global variable to mark that an SMC is in progress, so that two SMCs using shared resources (like the security engine) do not trample on one another. On 1.0.0, this global variable was written using non-atomic writes, and thus a race condition is possible.
However, the SMC handler enforces that all SMCs must be called from core #3, unless the top-level handler ID is 1 (SMCs internal to the kernel). Thus, the only SMCs that can be run side-by-side are [any userland smc] and smcGetRandomBytesForKernel, and this turns out to not really be abusable. |
Mostly useless. Maybe some oob-write into unused (and thus useless) memory if running smcGetRandomBytesForKernel and smcGetRandomBytesForUser at the same time. | 2.0.0 | 2.0.0 | December 2017 (Probably earlier by others) | January 18, 2018 | SciresM, probably others. |
jamais vu (non-secure world access to PMC MMIO and pre-deep sleep firmware) | On 1.0.0, one could map in the PMC registers in userland. In addition, am ran a little-kernel based firmware on the BPMP at runtime. With code execution under am, one could modify the BPMP's little-kernel firmware to hook deep sleep entry, and modify TrustZone/Security engine state.
This was fixed in 2.0.0 by making the PMC secure-world only, blacklisting the BPMP's exception vectors from being mapped, and thoroughly checking for malicious behavior on deep sleep entry. |
Arbitrary TrustZone code execution. | 2.0.0 | 2.0.0 | December, 2017 | January 20, 2018 | SciresM and motezazer |
Missed BPMP Exception Vector Writes | Starting in 2.0.0, the BPMP is asleep at runtime, and is turned on by TrustZone during smcCpuSuspend in order to initiate the deep sleep process. When it does so, it is held in RESET, and TrustZone attempts to write to the BPMP exception vectors at 0x6000F200 to register EVP_RESET = lp0_entry_fw_crt0, and all other EVPs to a function that simply reboots. However, while they successfully write EVP_RESET, they miss all the other vectors, accidentally writing to the 0x6000F004-0x6000F020 region instead of the 0x6000F204-0x6000F220 region they want to write to. This results in all the exception vectors for the BPMP other than RESET being "undefined" (attacker controlled).
With some way of causing an exception vector to be taken at the right time, this would give pre-sleep code execution (and thus arbitrary TrustZone code execution, via the security engine flaw). However, none of the abort vectors are really triggerable, and interrupts are disabled for the BPMP when it is taken out of reset. Thus, this is useless in practice. This was fixed in 4.0.0 by writing to the correct registers. |
Theoretically: Arbitrary TrustZone code execution. In practice: Useless. | 4.0.0 | 4.0.0 | January, 2018 | February 23, 2018 | SciresM and motezazer, naehrwert, hexkyz, probably others, independently. |
Kernel
Summary | Description | Successful exploitation result | Fixed in system version | Last system version this flaw was checked for | Timeframe this was discovered | Public disclosure timeframe | Discovered by |
---|---|---|---|---|---|---|---|
Syscall Infoleaks | Many syscalls leaked kernel pointers on sad paths (for example svcSetHeapSize and svcQueryMemory), until they landed a bunch of fixes in 2.0.0. | Nothing really. | 2.0.0 | 2.0.0 | ? | ||
GetLastThreadInfo UAF | GetLastThreadInfo syscall gets last-scheduled-KThread pointer from KScheduler object. This pointer is not reference counted, and can be pointing to a freed KThread. | Nothing. There is a theoretical race that might leak from a KThread from a different process, but it's impossible to trigger practically. | Unfixed | 15 October | 17 October | qlutoo | |
Bad irq_id check in CreateInterruptEvent | CreateInterruptEvent syscall is designed to work only for irq_id >= 32. All irq_ids < 32 are "per-core" and reserved for kernel use (watchdog/scheduling/core communications).
On 1.0.0 you could supply irq_id < 32 and it would write outside the SharedIrqs table. |
You can register irq's in the Core3Irqs table, and thus register per-core irqs for core3, that are normally reserved for kernel. Useless. | 2.0.0 | 2.0.0 | ~October | 17 October | qlutoo |
Kernel .text mapped executable in usermode | Prior to 3.0.2 the kernel .text was mapped in usermode as executable. This can be used for usermode ROP for bypassing ASLR, but SVCs/IPC are not usable by running kernel .text in usermode. | Executing kernel .text in usermode | 3.0.2 | 3.0.2 | 34c3 (December 28, 2017) | qlutoo | |
Memory Controller not properly secured | The Switch OS originally had the memory controller not set to be accessible only by the secure-world, which was problematic because insecure access can compromise the kernel.
This was fixed partially in 2.0.0 by blacklisting the memory controller from being mapped by user-processes, and was fixed entirely in 4.0.0 by making the memory controller TZ-only and making all kernel accesses go through smcReadWriteRegister. |
With some way to access the memory controller MMIO, arbitrary kernel code execution. | 4.0.0 | 4.0.0 | January 2018 | January 2018 | SciresM, yellows8 |
FIRM-package System Modules
Summary | Description | Successful exploitation result | Fixed in system version | Last system version this flaw was checked for | Timeframe this was discovered | Public disclosure timeframe | Discovered by |
---|---|---|---|---|---|---|---|
Service access control bypass (sm:h, smhax, probably other names) | Prior to 3.0.1, the service manager (sm) built-in system module treats a user as though it has full permissions if the user creates a new "sm:" port session but bypasses initialization. This is due to the other sm commands skipping the service ACL check for Pids <= 7 (i.e. all kernel bundled modules) and that skipping the initialization command leaves the Pid field uninitialized.
In 3.0.1, sm returns error code 0x415 if Initialize has not been called yet. |
Acquiring, registering, and unregistering arbitrary services | 3.0.1 | 3.0.1 | May 2017 | August 17, 2017 | Everyone |
Overly permissive SPL service | The concept behind the switch's Secure Monitor is that all cryptographic keydata is located in userspace, but stored as "access keys" encrypted with "keks" that never leave TrustZone. The spl ("security processor liaison"?) service serves as an interface between the rest of the system and the secure monitor. Prior to 4.0.0, spl exposed only a single service "spl:", which provided all TrustZone wrapper functions to all sysmodules with access to it. Thus anyone with access to the spl: service (via smhax or by pwning a sysmodule with access) could do crypto with any access keys they knew.
This was fixed in 4.0.0 by splitting spl: into spl:, spl:mig, spl:ssl, spl:es, and spl:fs. |
Arbitrary spl: crypto with any access keys one knows. For example, one could use the SSL module's access keys to decrypt their console's SSL certificate private key without having to pwn the SSL sysmodule. | 4.0.0 | 4.0.0 | Summer 2017 (after smhax was discovered). | December 23, 2017 | Everyone |
Single session services not really single session | Several "critical" services (like fsp-ldr, fsp-pr, sm:m, etc) are meant to only ever hold a single session with a specific sysmodule. However, when a sysmodule dies, all its service session handles are released -- and thus killing the holder of a single session handle would allow one (via sm:hax etc) to get access to that service.
This was fixed in 4.0.0 by adding a semaphore to these critical single-session services, so that even if one gets access to them an error code will be returned when attempting to use any of their commands. |
With some way to access these services and kill their session holders: dumping sysmodule code, arbitrary service access, elevated filesystem permissions, etc. | 4.0.0 | 4.0.0 | May/June 2017 (basically immediately after smhax was discovered) | December 30, 2017 | Everyone |
System Modules
Summary | Description | Successful exploitation result | Fixed in system version | Last system version this flaw was checked for | Timeframe this was discovered | Public disclosure timeframe | Discovered by |
---|---|---|---|---|---|---|---|
Out-of-bounds array read for BCAT_Content_Container secret-data index | The BCAT_Content_Container secret-data index is not validated at all. This is handled before the RSA-signature(?) is ever used. Since the field is an u8, a total of 0x800-bytes relative to the array start can be accessed.
This is not useful since the string loaded from this array is only involved with key-generation. |
Unknown | 2.0.0 | August 4, 2017 | August 6, 2017 | Shiny Quagsire, yellows8 (independently) | |
OOB Read in NS system module (pl:utoohax, pl:utonium, maybe other names) | Prior to 3.0.0, pl:u (Shared Font services implemented in the NS sysmodule) service commands 1,2,3 took in a signed 32-bit index and returned that index of an array but did not check that index at all. This allowed for an arbitrary read within a 34-bit range (33-bit signed) from NS .bss. In 3.0.0, sending out of range indexes causes error code 0x60A to be returned. | Dumping full NS .text, .rodata and .data, infoleak, etc | 3.0.0 | 3.0.0 | April 2017 | On exploit's fix in 3.0.0 | qlutoo, Reswitched team (independently) |
Unchecked domain ID in common IPC code | Prior to 2.0.0, object IDs in domain messages are not bounds checked. This out-of-bounds read could be exploited to brute-force ASLR and get PC control in some services that support domain messages. | 2.0.0 | 2.0.0 | ~July 2017 | 20 July 2017 | hthh |