Switch System Flaws: Difference between revisions

 
(16 intermediate revisions by 3 users not shown)
Line 1: Line 1:
This page is a list of publicly known Switch flaws.
This page is a list of publicly known Switch / Switch 2 (S2) flaws.


= Hardware =
= Hardware =
Line 1,087: Line 1,087:
| October 13, 2024
| October 13, 2024
| [[User:Yellows8|yellows8]]
| [[User:Yellows8|yellows8]]
|}
|-
| [[Bus_services|sasbus]] StartPeriodicReceiveMode infoleak
| StartPeriodicReceiveMode writes a vtable ptr into the mapped tmem at +0. The tmem is mapped RW in the user-process. There is no clearing of tmem during tmem cleanup. Hence, the user-process can read the tmem to obtain a Bus-sysmodule codebin-region infoleak. This vtable-ptr seems to be unused - it's also empty after the first two entries (stubbed incref/decref).
[20.0.0+] Removed the vtable ptr, with data intended for the user-process being moved from tmem+0x8 to +0x0. Also, instead of calling memset, funcs are called for manually clearing tmem.
| Bus-sysmodule infoleak, which allows defeating ASLR.
| [[20.0.0]]
| [[20.0.0]]
| February 22, 2022
| May 3, 2025
| [[User:Yellows8|yellows8]]
|-
| [[NFC_services|nfc]] SendCommandByPassThrough buffer overflow
| SendCommandByPassThrough eventually copies the input buffer into a fixed-size heap buffer, without size validation.
This was fixed with [20.0.0+] by clamping the size.
| nfc-sysmodule heap buffer overflow.
| [[20.0.0]]
| [[20.0.0]]
| Late November 2021
| May 3, 2025
| [[User:Yellows8|yellows8]] (maybe others?)
|-
| [[HID_services|hidbus]] EnableJoyPollingReceiveMode infoleak
| The tmem initialized by hidbus EnableJoyPollingReceiveMode contains a vtable ptr (tmem+0x10), hence infoleak. With [20.0.0+] the vtable ptr write was removed, and tmem is now memset starting at tmem+0x10 instead of +0x20.
| hid-sysmodule infoleak, which allows defeating ASLR.
| [[20.0.0]]
| [[20.0.0]]
| March 2020
| May 4, 2025
| [[User:Yellows8|yellows8]]
|-
| [[SSL_services|ssl]] Certificate verification bypass
| The ssl sysmodule keeps a list of trusted certificates, that are imported by an app with ImportServerPki. During certificate verification, if the certificate that is provided by the server has the same subject key id as a trusted certificate, the certificate is accepted, even if self-signed. A blog post about this vulnerability can be found [https://reversing.live/sslbypass.html here].
| Man-in-the-middle for any connection that uses ImportServerPki.
| [[20.2.0]]
| [[20.2.0]]
| June 6, 2025
| August 8, 2025
| [https://github.com/kinnay Yannik]
|-
| [[LDN_services|ldn]] AdvertiseData OOB-memcpy with EncryptionType3 (AES-128-GCM) actionframes (ldnhax)
| The ldn action-frame parser object for AES-128-GCM (used with [[LDN_services|EncryptionType3]]), when it does validation once finished, only verifies that the sizes are within bounds of the input buffer. There's no validation against constants, which the other EncryptionType objects have. The caller code doesn't validate the size either.
 
[21.0.0+] Now validates the advert-size with sizeof(NetworkInfo.AdvertiseData).


== Internet Browser ==
For more details see [https://gist.github.com/yellows8/16bb56343d085d2db2ab0adc5d4cef99 here].
{| class="wikitable" border="1"
| Compromise of ldn starting from OOB-memcpy, even on S2: stack infoleak (ASLR defeat), arbitrary memory read/write (which also allows handle-leak), vfunc-calls with arbitrary [[Security_Mitigations|vtable]].
!  Summary
| [[21.0.0]]
!  Description
| [[21.0.0]]
!  Successful exploitation result
| June ~13, 2025
!  Fixed in system version
| November 11, 2025
!  Last system version this flaw was checked for
| [[User:Yellows8|yellows8]]
!  Timeframe this was discovered
!  Public disclosure timeframe
!  Discovered by
|-
|-
| CVE-2016-4657
| [[HID_services|hid:dbg]] AttachHdlsVirtualDevice unvalidated DeviceTypeInternal
| WebKit vuln discovered around August 2016. Most notably used in the iOS 9.3.X exploit. A simple PoC can be found [https://github.com/LiveOverflow/lo_nintendoswitch/blob/master/poc1.html here]. This was later exploited by [https://twitter.com/qwertyoruiopz Qwertyoruiop] using an adjusted version of his iOS 9.3 webkit exploit (others exploited this prior to then).
| hid:dbg AttachHdlsVirtualDevice eventually passes the input from HdlsDeviceInfo into a func without any validation. The DeviceTypeInternal field is used as the index for loading a ptr from a global array. The only validation occurs when the loaded ptr is NULL - this is just for initializing the ptr in the array when it's not already set.
|
 
| [[2.1.0]]
Since the highest DeviceTypeInternal is value 30, using >=31 will load an OOB ptr. This ptr is written to state, and also immediately passed to a called func. As long as ptr is valid it should be fine with this func.
| [[2.0.0]]
 
| Original: August 2016
This functionality is also used eventually by ApplyHdlsNpadAssignmentState and ApplyHdlsStateList.
Switch: March 3rd-4th 2017
 
|
It's unknown whether there's a way to exploit this. Also note that hid:dbg is not normally accessible to retail titles.
| Everyone
 
[21.0.0+] Arrayindex=0 is now used when the input is invalid.
| Likely useless, even if reachable?
| [[21.0.0]]
| [[21.0.0]]
| June 3, 2024 (possibly eariler(?))
| November 14, 2025
| [[User:Yellows8|yellows8]]
|-
|-
| CVE-2017-7005
| [[Bluetooth_Driver_services|bluetooth]] BSA allowed ATT MTU is too large
| WebKit type confusion.
| GATT-handler stack buffer overflows with a large input size are only possible if the payload_size (MTU) field in state is large enough. gatt_client_handle_server_rsp/gatt_server_handle_client_req will drop messages where the size is >= payload_size (though unless the request opcode matches certain values it will also send an error-response for invalid-PDU). Both of these handle updating this field when needed, however that's handled properly.
|
 
| [[3.0.1]]
With bluetooth-classic via L2CAP, a hard-coded MTU of 0x205 is sent in the configure request. However the code handling received configure requests will set payload_size to 0x2A0 if no MTU is specified, or the input MTU if it's within range 0x30..0x2A0. Hence, sending data large enough for buffer overflows requires bluetooth-classic via L2CAP + manually sending large ACL data.
| [[3.0.1]]
 
|
[15.0.0+] gatt_l2cif_config_ind_cback which handles the received configure-requests with bluetooth-classic mentioned above, now uses MTU range 0x30..0x205 with the default MTU being 0x205. It is therefore no longer possible to trigger the previously mentioned buffer-overflows with bluetooth-classic.
|  
| Stack buffer overflows in bluetooth-sysmodule due to the allowed MTU for ATT being larger than the stack data.
| Everyone
| [[15.0.0]]
| [[20.0.0]]
| November 2021?
| November 26, 2025
| [[User:Yellows8|yellows8]]
|-
|-
| CVE-2016-4622
| [[Bluetooth_Driver_services|bluetooth]] BSA gatt_process_prep_write_rsp stack buffer overflow
| WebKit memory corruption bug. This bug was incorrectly re-introduced in [[4.0.0]]. See [http://www.phrack.org/papers/attacking_javascript_engines.html here] for a detailed write-up from the author.
| BSA gatt_process_prep_write_rsp memcpys to stack without size validation (the input len param which is subtracted to determine the copy-size is also unvalidated). Triggering this is only possible if the system sent ATT_PREPARE_WRITE_REQ, and then received ATT_PREPARE_WRITE_RSP with a large size.
|
 
| [[6.1.0]]
The size used with memcpy is (u16)(insize-4), so when insize is less than 4 the copy size will be {negative value masked to u16}. This will therefore eventually crash when the stacktop is reached during memcpy.
| [[6.1.0]]
 
|
[15.0.0+] Paritially fixed due to corrected MTU handling (doesn't apply to negative-copysize). [21.0.0+] Fully fixed with proper size validation.
|  
| Stack buffer overflow in bluetooth-sysmodule when the required ATT messages are sent/received.
| Everyone
| [[21.0.0]]
| [[21.0.0]]
| November 2021?
| January 19, 2026
| [[User:Yellows8|yellows8]]
|-
|-
| CVE-2018-4441
| [[NFC_services|nfc]] Initialize buffer overflow
| WebKit memory corruption bug. See [https://bugs.chromium.org/p/project-zero/issues/detail?id=1685&desc=2 here].
| All Initialize* cmds for nn::nfc::detail::IUser (nfc:user), nn::nfc::detail::ISystem (nfc:sys), nn::nfp::detail::IUser (nfp:user), nn::nfp::detail::ISystem (nfp:sys), nn::nfp::detail::IDebug (nfp:dbg), nn::nfc::mifare::detail::IUser (nfc:mf:u): these copy the input array into _this, without validating the array count.
|
The data is copied to obj_impl+0x8+0x28, with each entry being 0x20-bytes. The event handle returned by AttachAvailabilityChangeEvent is at obj_impl+0x8+0xB8+0x14 (Same with nfc/nfp interfaces). This therefore means +0xA4 in the input buffer will overwrite the handle returned by that cmd, allowing one to leak any handle with the specified value. This can be done with count=0x6. The object is large enough that this count will only overwrite data within the current object. However during the dtor it will use ptrs which were corrupted with this (located before the event), so one must avoid closing the session unless the input data included valid ptrs.
| [[7.0.0]]
 
| [[7.0.0]]
This can be exploited by just using a 0xC0-byte (array_count=0x6) input buffer with Initialize where each u32 is the target nfc handle value, then using cmd GetAvailabilityChangeEventHandle to leak the handle.
|  
|
| Everyone
|-
| Web-applets OpenSSL broken RNG
| [[SPL_services|csrng]] access was added to web-applets with [12.1.0+]. Prior to that, csrng and nn::os::GenerateRandomBytes were not used (besides sdk heap code).
nn::os::GetSystemTick is used to seed the OpenSSL RNG, among other data. Hence, it's probably (?) possible to bruteforce the RNG initial state, allowing predicting RNG output.


The RNG code is wkcRandomNumbersPeer (peer_wkc nro), with the initialization code using GetSystemTick located in the func immediately before wkcGetTickCountPeer. The former is called from wkcOsslRandFilefReadPeer. wkcOsslRandFilefReadPeer is called for seeding the OpenSSL RNG.
[22.0.0+] This was fixed by clamping the count to a maximum of 0x4.
 
| OOB datacopy into object state. Allows leaking arbitary [[NFC_services|handles]], including on [S2] (such as process-handle, sm, fsp-srv (remaining services can also be used via sm)).
With [12.1.0+], wkcRandomNumberPeer/wkcRandomNumbersPeer wrap nn::os::GenerateRandomBytes. wkcCryptographicallyRandomValuesPeer was added which wraps nn::crypto::GenerateCryptographicallyRandomBytes. wkcOsslRandFilefReadPeer now calls nn::crypto::GenerateCryptographicallyRandomBytes instead of wkcRandomNumbersPeer.
| [[22.0.0]]
| Breaking web-applets OpenSSL RNG -> potentially predict RNG data (keys(?)) during TLS comms.
| [[22.0.0]]
| [[12.1.0]]
| November 2021
| [[12.1.0]]
| March 17, 2026
| January 28, 2022
| [[User:Yellows8|yellows8]]
| October 8, 2024
|}
| [[User:Yellows8|yellows8]], likely (?) others
|}
 
=== Whitelist ===
This section documents [[Internet_Browser|WebApplet]] whitelist issues in applications. These can be used to load your own browser content over plain HTTP, which then for example could be used for web-applet exploitation.


== Internet Browser ==
{| class="wikitable" border="1"
{| class="wikitable" border="1"
Application
Summary
!  Description
!  Description
!  Fixed with app version
!  Successful exploitation result
Newest app version this flaw was checked for
!  Fixed in system version
Last system version this flaw was checked for
!  Timeframe this was discovered
!  Timeframe this was discovered
!  Public disclosure timeframe
!  Public disclosure timeframe
!  Discovered by
!  Discovered by
|-
|-
| Sonic Mania
| CVE-2016-4657
| Originally this game launched web-applet with a plain-http URL for displaying the manual, this was later changed to https. Originally the whitelist only had 1 entry for a http URL, this was later replaced with various https-only URLs.
| WebKit vuln discovered around August 2016. Most notably used in the iOS 9.3.X exploit. A simple PoC can be found [https://github.com/LiveOverflow/lo_nintendoswitch/blob/master/poc1.html here]. This was later exploited by [https://twitter.com/qwertyoruiopz Qwertyoruiop] using an adjusted version of his iOS 9.3 webkit exploit (others exploited this prior to then).
| 1.04, unknown if fixed with an earlier update
|
| 1.04
| [[2.1.0]]
| January (?) 2022
| [[2.0.0]]
| February 23, 2022
| Original: August 2016
| [[User:Yellows8|yellows8]]
Switch: March 3rd-4th 2017
|}
|
 
| Everyone
== NintendoSDK ==
This section documents vulnerabilities for NSOs in NintendoSDK.
 
=== nnSdk ===
This section documents vulnerabilities for nnSdk (sdknso).
 
{| class="wikitable" border="1"
|-
|-
!  Summary
| CVE-2017-7005
!  Description
| WebKit type confusion.
!  Successful exploitation result
|
!  Fixed in SDK [[System_Versions|version]]
| [[3.0.1]]
!  Last SDK version this flaw was checked for
| [[3.0.1]]
!  Timeframe this was discovered
|
!  Public disclosure timeframe
|
!  Discovered by
| Everyone
|-
| CVE-2016-4622
| WebKit memory corruption bug. This bug was incorrectly re-introduced in [[4.0.0]]. See [http://www.phrack.org/papers/attacking_javascript_engines.html here] for a detailed write-up from the author.
|
| [[6.1.0]]
| [[6.1.0]]
|
|
| Everyone
|-
|-
| [[HID_services|hidbus]] GetJoyPollingReceivedData buffer overflow
| CVE-2018-4441
| hidbus GetJoyPollingReceivedData doesn't validate the u8 size used for memcpy, when copying the data to the output JoyPollingReceivedData. With 11.x, the size is now clamped to a maximum of 0x2C (regardless of polling-mode). Note that 0x2C is the data-size for JoyButtonOnlyPollingDataAccessor, the other polling-modes have a smaller size.
| WebKit memory corruption bug. See [https://bugs.chromium.org/p/project-zero/issues/detail?id=1685&desc=2 here].
 
|
The hid-sysmodule code which writes data here does handle it properly: size is clamped to a max size, and the data-read uses a fixed-size anyway (hence there's no way to trigger this sdknso vuln with the hid-sysmodule tmem writing code).
| [[7.0.0]]
 
| [[7.0.0]]
This could only be exploited if one directly writes to the tmem when one has previously compromised hid-sysmodule, without using the normal tmem-writing func for this.
|  
 
|
There are only a few [[HID_services#ExternalDevices|apps]] which use hidbus.
| Everyone
| Triggering a buffer overflow in an application which uses hidbus GetJoyPollingReceivedData, from a previously compromised hid-sysmodule.
| 11.x.0
| 11.4.0
| March 2020
| December 3, 2020
| [[User:Yellows8|yellows8]]
|-
|-
| [[Profile_Selector|Profile Selector]] uninitialized input data
| Web-applets OpenSSL broken RNG
| Originally unused regions of [[Profile_Selector]] UiSettings/UserSelectionSettings were not cleared prior to being sent to the applet. With 1.x.x these are now properly memset().
| [[SPL_services|csrng]] access was added to web-applets with [12.1.0+]. Prior to that, csrng and nn::os::GenerateRandomBytes were not used (besides sdk heap code).
| Stack infoleak from user-process, sent to the applet.
nn::os::GetSystemTick is used to seed the OpenSSL RNG, among other data. Hence, it's probably (?) possible to bruteforce the RNG initial state, allowing predicting RNG output.
| 1.x.x
| 11.4.0
| November-December 2019
| December 31, 2020
| [[User:Yellows8|yellows8]]
|}


=== Pia ===
The RNG code is wkcRandomNumbersPeer (peer_wkc nro), with the initialization code using GetSystemTick located in the func immediately before wkcGetTickCountPeer. The former is called from wkcOsslRandFilefReadPeer. wkcOsslRandFilefReadPeer is called for seeding the OpenSSL RNG.
This section documents vulnerabilities for [https://github.com/Kinnay/NintendoClients/wiki/Pia-Overview Pia].


In v5.11.3 (exact starting version unknown) the fixes aren't present for the below vulns which were fixed in v5.9.3, while in v5.18.98 these are present (exact starting version unknown). This probably indicates that the vuln fixes were backported from a newer Pia version to v5.9.3.
With [12.1.0+], wkcRandomNumberPeer/wkcRandomNumbersPeer wrap nn::os::GenerateRandomBytes. wkcCryptographicallyRandomValuesPeer was added which wraps nn::crypto::GenerateCryptographicallyRandomBytes. wkcOsslRandFilefReadPeer now calls nn::crypto::GenerateCryptographicallyRandomBytes instead of wkcRandomNumbersPeer.
| Breaking web-applets OpenSSL RNG -> potentially predict RNG data (keys(?)) during TLS comms.
| [[12.1.0]]
| [[12.1.0]]
| January 28, 2022
| October 8, 2024
| [[User:Yellows8|yellows8]], likely (?) others
|}


The Pia packet handlers are only active when the game is using multiplayer. LanProtocol is only active in the games which are actively using the LAN-mode option (not Ldn) - only certain games support LAN-mode. The LanProtocol Pia packet handler can be reached while in a lobby or searching for one.
=== Whitelist ===
 
This section documents [[Internet_Browser|WebApplet]] whitelist issues in applications. These can be used to load your own browser content over plain HTTP, which then for example could be used for web-applet exploitation.
Most Pia packets require an active StationProtocol connection to be active with {InetAddr which the packet was received from}, otherwise the packet is filtered out. The only protocols which don't use filtering are the following: NatTraversalProtocol, LanProtocol, StationProtocol, LocalProtocol.
 
Note that broadcast IP-dest Pia packets are accepted - this can be used to target every device on the network which is using Pia (which is really only useful with {above protocols} due to the filtering mentioned above, unless one also handles StationProtocol).


{| class="wikitable" border="1"
{| class="wikitable" border="1"
|-
Application
Summary
!  Description
!  Description
!  Successful exploitation result
!  Fixed with app version
!  Fixed in Pia version
Newest app version this flaw was checked for
Last Pia version this flaw was checked for
!  Timeframe this was discovered
!  Timeframe this was discovered
!  Public disclosure timeframe
!  Public disclosure timeframe
!  Discovered by
!  Discovered by
|-
|-
| nn::pia::session::RelayRouteManageJob::UpdateConnectionReport buffer overflow
| Sonic Mania
| nn::pia::session::RelayRouteManageJob::UpdateConnectionReport() checks that the input size is at least {value}, but there's no max size check. This is used to memcpy from the input to elsewhere - hence buf-overflow if size is too large. The dst buffer is allocated on the pead heap - this buffer is probably small.
| Originally this game launched web-applet with a plain-http URL for displaying the manual, this was later changed to https. Originally the whitelist only had 1 entry for a http URL, this was later replaced with various https-only URLs.
Note that there's various requirements before it would actually reach the memcpy, such as <code><nn::pia::session::Mesh::IsHost() const></code> must return true.
| 1.04, unknown if fixed with an earlier update
| 1.04
| January (?) 2022
| February 23, 2022
| [[User:Yellows8|yellows8]]
|}


In fixed versions immediately after the StationIndex validation it now does: <code>if(statefield+0x10<input_size) return;</code>
== NintendoSDK ==
This section documents vulnerabilities for NSOs in NintendoSDK.


This is called from nn::pia::session::MeshProtocol::ParseConnectionReport().
=== nnSdk ===
| Heap buffer overflow triggered by a Pia MeshProtocol message sent to a host device.
This section documents vulnerabilities for nnSdk (sdknso).
| v5.9.3, see above.
| v5.9.1/v5.9.2/v5.9.3
| November 11, 2022
| November 15, 2022
| [[User:Yellows8|yellows8]]
|-
| nn::pia::lan::LanProtocol::ParseSessionMessage buffer overflow
| nn::pia::lan::LanProtocol::ParseSessionMessage() calls nn::pia::lan::LanSessionMessage::Deserialize() to deserialize the message payload data buffer into the LanSessionMessage object on stack. LanSessionMessage::Deserialize (among other things) memcpys data from the input buffer to the object, using an u32 from the input buffer - there is no size validation in Deserialize itself.
There is a size check immediately after calling Deserialize() to verify <code>payloadsize=={u32val}+{constant}</code>, returning on fail - but this doesn't matter for too-large-size.


In fixed versions Deserialize now does bounds checking, both for the minimum message size and clamping the memcpy size to a constant. An error is thrown if the clamped memcpy size is larger than the message size. The caller now checks the ret properly, previously it was ignored.
{| class="wikitable" border="1"
|-
!  Summary
!  Description
!  Successful exploitation result
!  Fixed in SDK [[System_Versions|version]]
!  Last SDK version this flaw was checked for
!  Timeframe this was discovered
!  Public disclosure timeframe
!  Discovered by
|-
| [[HID_services|hidbus]] GetJoyPollingReceivedData buffer overflow
| hidbus GetJoyPollingReceivedData doesn't validate the u8 size used for memcpy, when copying the data to the output JoyPollingReceivedData. With 11.x, the size is now clamped to a maximum of 0x2C (regardless of polling-mode). Note that 0x2C is the data-size for JoyButtonOnlyPollingDataAccessor, the other polling-modes have a smaller size.


Following the size check in ParseSessionMessage() it calls <code><nn::pia::session::Mesh::IsProcessingLeaveMesh() const></code>, returning if ret is false.
The hid-sysmodule code which writes data here does handle it properly: size is clamped to a max size, and the data-read uses a fixed-size anyway (hence there's no way to trigger this sdknso vuln with the hid-sysmodule tmem writing code).


Then it calls nn::pia::lan::LanProtocol::ReceivedFragmentData::Receive(), with the memcpy'd buffer/size from the above LanSessionMessage, and other fields from LanSessionMessage. This eventually memcpys the input buffer to object+{offset}+{chunksize_field}*inputu8, there is no validation for size or inputu8 (except for the above size check). Hence, if the u8 is large enough, this would result in a heap buffer overflow.
This could only be exploited if one directly writes to the tmem when one has previously compromised hid-sysmodule, without using the normal tmem-writing func for this.


In fixed versions ReceivedFragmentData::Receive added a bunch of validation before the memcpy.
There are only a few [[HID_services#ExternalDevices|apps]] which use hidbus.
| Stack/heap buffer overflow triggered by a Pia LanProtocol message.
| Triggering a buffer overflow in an application which uses hidbus GetJoyPollingReceivedData, from a previously compromised hid-sysmodule.
| v5.9.3, see above.
| 11.x.0
| v5.9.1/v5.9.2/v5.9.3
| 11.4.0
| November 14, 2022
| March 2020
| November 15, 2022
| December 3, 2020
| [[User:Yellows8|yellows8]]
| [[User:Yellows8|yellows8]]
|-
|-
| nn::pia::session::SessionProtocol::ParseLeaveMeshInvitation buffer overflow
| [[Profile_Selector|Profile Selector]] uninitialized input data
| <code><nn::pia::session::SessionProtocol::ParseLeaveMeshInvitation(nn::pia::transport::ReceivedMessageAccessor const&)></code> This immediately returns if *(ReceivedMessageAccessor+16) is 0. Then the input data is deserialized. The input u64 array is deserialized to stack, the u8 arraycount field from input is not validated.
| Originally unused regions of [[Profile_Selector]] UiSettings/UserSelectionSettings were not cleared prior to being sent to the applet. With 1.x.x these are now properly memset().
| Stack infoleak from user-process, sent to the applet.
| 1.x.x
| 11.4.0
| November-December 2019
| December 31, 2020
| [[User:Yellows8|yellows8]]
|}


Hence, stack buffer overflow. Note that there's similar loop code in nearby funcs, which do validate the count properly.
=== NEX ===
This section documents client-side vulnerabilities for [https://github.com/Kinnay/NintendoClients/wiki/NEX-Overview-(Game-Servers) NEX].


In fixed versions the arraycount field is now validated.
{| class="wikitable" border="1"
 
SessionProtocol uses ReliableSlidingWindow MessageHeader, with a maximum message size of 0x100. The allocated size used for the above u64 array is also 0x100-bytes. Hence, when triggering a buf overflow the data after the buffer is uncontrolled data from the SessionProtocol object.
| Stack buffer overflow triggered by a Pia SessionProtocol message.
| v5.9.3, see above.
| v5.9.1/v5.9.2/v5.9.3
| November 14, 2022
| November 15, 2022
| [[User:Yellows8|yellows8]]
|-
|-
| Optional Pia packet encryption
!  Summary
| Pia packet encryption is optional. If the encryption flag is disabled, the packet handler will accept it and skip crypto.
!  Description
In fixed versions immediately after grabbing a packet, it now checks the crypto flag. If it's plaintext the packet is dropped.
!  Successful exploitation result
 
!  Fixed in version
This can be used to send a plaintext Pia packet without needing to handle encryption, especially useful if the session-key can't be obtained (online-play matchmaking). This could be combined with other vulns if wanted.
!  Timeframe this was discovered
| Sending a plaintext Pia packet without needing to handle encryption.
!  Public disclosure timeframe
| v5.9.3, see above.
!  Discovered by
| v5.9.3 (and later versions)
|
| November 19, 2022
|
|-
|-
| nn::pia::session::{JoinMeshJob/ProcessUpdateMeshJob}::SetStationDataList OOB read/write/vfunc-call
| Buffer overflow in StringConversion::T2Char8
| <code>nn::pia::session::JoinMeshJob::SetStationDataList</code>is called by <code>nn::pia::session::MeshProtocol::ParseJoinResponse(nn::pia::transport::ReceivedMessageAccessor const&)></code> with the ReceivedMessageAccessor buffer.
| StringConversion::T2Char8 is used to convert IP addresses from a platform-specific encoding to UTF-8. On the 3DS and Switch, the implementation is simply a strcpy. By sending a long IP address string, a buffer overflow can be triggered on the stack. The vulnerability can be triggered through the NAT traversal protocol. A blog post about this vulnerable can be found [https://reversing.live/hacking-hundreds-of-wii-us-at-once.html here].
SetStationDataList will update state and immediately return if the join was denied. It will also validate the num_mesh_stations field against state. ParseJoinResponse also essentially verifies that the message was received from the host device.
| Stack overflow in any game that uses NEX for matchmaking
| Fixed server-side
| December, 2022
| May, 2024
| [https://github.com/kinnay Yannik]
|}


The input buffer size is ignored.
=== Pia ===
This section documents vulnerabilities for [https://github.com/Kinnay/NintendoClients/wiki/Pia-Overview Pia].


The num_fragments field must be value 1 or <=3 otherwise it will return, there's two seperate code blocks handling these.
In v5.11.3 (exact starting version unknown) the fixes aren't present for the below vulns which were fixed in v5.9.3, while in v5.18.98 these are present (exact starting version unknown). This probably indicates that the vuln fixes were backported from a newer Pia version to v5.9.3.


Other than the checks at the start, there's no validation for the index fields. So large enough values could result in OOB-reads.
The Pia packet handlers are only active when the game is using multiplayer. LanProtocol is only active in the games which are actively using the LAN-mode option (not Ldn) - only certain games support LAN-mode. The LanProtocol Pia packet handler can be reached while in a lobby or searching for one.


When handling multiple fragments, it will loop through the stationinfo list. There is no validation for the u8 count field or the baseindex field. It calls a vfunc from obj baseptr+index*{entrysize} with data from the buffer, where index starts with the above baseindex field. Afterwards, an u8 is copied into an u32 array (with certain versions an u16 is deserialized into an u16 array).
Most Pia packets require an active StationProtocol connection to be active with {InetAddr which the packet was received from}, otherwise the packet is filtered out. The only protocols which don't use filtering are the following: NatTraversalProtocol, LanProtocol, StationProtocol, LocalProtocol.


<code>nn::pia::session::ProcessUpdateMeshJob::UpdateStationDataList</code> is (eventually) called from <code>nn::pia::session::MeshProtocol::ParseUpdateMesh</code>, which has similar issues to the above.
Note that broadcast IP-dest Pia packets are accepted - this can be used to target every device on the network which is using Pia (which is really only useful with {above protocols} due to the filtering mentioned above, unless one also handles StationProtocol).


Note that ParseJoinResponse/ParseUpdateMesh essentially require the message to be received from the host device.
{| class="wikitable" border="1"
 
|-
With fixed versions (v5.18.98, exact version unknown) various validation was added. Additional/updated validation was added in a later version (v5.31.0, exact version unknown).
!  Summary
| OOB read/write / vfunc call where the object is selected by an OOB index, triggered by a Pia MeshProtocol message.
!  Description
| v5.18.98 and v5.31.0 (exact versions unknown).
!  Successful exploitation result
| v5.31.0
!  Fixed in Pia version
| November 18, 2022
!  Last Pia version this flaw was checked for
| November 21, 2022
!  Timeframe this was discovered
| [[User:Yellows8|yellows8]]
!  Public disclosure timeframe
!  Discovered by
|-
|-
| Insecure encryption
| nn::pia::session::RelayRouteManageJob::UpdateConnectionReport buffer overflow
| Originally Pia packets used AES-ECB encryption. As documented [https://github.com/Kinnay/NintendoClients/wiki/Pia-Overview here] it was later changed with v5.7.0 to AES-GCM. Each 0x10-byte block would have the same encrypted block output where the plaintext 0x10-byte data is the same.
| nn::pia::session::RelayRouteManageJob::UpdateConnectionReport() checks that the input size is at least {value}, but there's no max size check. This is used to memcpy from the input to elsewhere - hence buf-overflow if size is too large. The dst buffer is allocated on the pead heap - this buffer is probably small.
The mechanism for generating the Pia SessionKey for LAN has also changed over time.
Note that there's various requirements before it would actually reach the memcpy, such as <code><nn::pia::session::Mesh::IsHost() const></code> must return true.
 
This is called from nn::pia::session::MeshProtocol::ParseConnectionReport().
 
ParseConnectionReport uses a state ptr for object nn::pia::session::RelayRouteManageJob, it will return if not set. nn::pia::session::Mesh::Initialize handles setup for this, depending on an input field from nn::pia::session::Mesh::Setting. These settings originate from <code><nn::pia::session::Session::CreateInstance(nn::pia::session::Session::Setting const&)></code>, which is called by user-code with the needed settings.
ParseConnectionReport is therefore only usable if the game explicitly enables the Relay functionality.


The [https://github.com/Kinnay/NintendoClients/wiki/LAN-Protocol LAN] non-Pia-encapsulated packets were also originally sent in plaintext, however at some point it was changed to mostly encrypted.
In fixed versions immediately after the StationIndex validation it now does: <code>if(statefield+0x10<input_size) return;</code>
|  
| Heap buffer overflow triggered by a Pia MeshProtocol message sent to a host device.
| AES-GCM fix: v5.7.0
| v5.9.3, see above.
|  
| v5.9.1/v5.9.2/v5.9.3
|  
| November 11, 2022
|  
| November 15, 2022
|  
| [[User:Yellows8|yellows8]]
|-
|-
| nn::pia::transport::UnreliableProtocol::Dispatch buffer overflow
| nn::pia::lan::LanProtocol::ParseSessionMessage buffer overflow
| nn::pia::lan::LanProtocol::ParseSessionMessage() calls nn::pia::lan::LanSessionMessage::Deserialize() to deserialize the message payload data buffer into the LanSessionMessage object on stack. LanSessionMessage::Deserialize (among other things) memcpys data from the input buffer to the object, using an u32 from the input buffer - there is no size validation in Deserialize itself.
There is a size check immediately after calling Deserialize() to verify <code>payloadsize=={u32val}+{constant}</code>, returning on fail - but this doesn't matter for too-large-size.
 
In fixed versions Deserialize now does bounds checking, both for the minimum message size and clamping the memcpy size to a constant. An error is thrown if the clamped memcpy size is larger than the message size. The caller now checks the ret properly, previously it was ignored.
 
Following the size check in ParseSessionMessage() it calls <code><nn::pia::session::Mesh::IsProcessingLeaveMesh() const></code>, returning if ret is false.
 
Then it calls nn::pia::lan::LanProtocol::ReceivedFragmentData::Receive(), with the memcpy'd buffer/size from the above LanSessionMessage, and other fields from LanSessionMessage. This eventually memcpys the input buffer to object+{offset}+{chunksize_field}*inputu8, there is no validation for size or inputu8 (except for the above size check). Hence, if the u8 is large enough, this would result in a heap buffer overflow.
 
In fixed versions ReceivedFragmentData::Receive added a bunch of validation before the memcpy.
| Stack/heap buffer overflow triggered by a Pia LanProtocol message.
| v5.9.3, see above.
| v5.9.1/v5.9.2/v5.9.3
| November 14, 2022
| November 15, 2022
| [[User:Yellows8|yellows8]]
|-
| nn::pia::session::SessionProtocol::ParseLeaveMeshInvitation buffer overflow
| <code><nn::pia::session::SessionProtocol::ParseLeaveMeshInvitation(nn::pia::transport::ReceivedMessageAccessor const&)></code> This immediately returns if *(ReceivedMessageAccessor+16) is 0. Then the input data is deserialized. The input u64 array is deserialized to stack, the u8 arraycount field from input is not validated.
 
Hence, stack buffer overflow. Note that there's similar loop code in nearby funcs, which do validate the count properly.
 
In fixed versions the arraycount field is now validated.
 
SessionProtocol uses ReliableSlidingWindow MessageHeader, with a maximum message size of 0x100. The allocated size used for the above u64 array is also 0x100-bytes. Hence, when triggering a buf overflow the data after the buffer is uncontrolled data from the SessionProtocol object.
| Stack buffer overflow triggered by a Pia SessionProtocol message.
| v5.9.3, see above.
| v5.9.1/v5.9.2/v5.9.3
| November 14, 2022
| November 15, 2022
| [[User:Yellows8|yellows8]]
|-
| Optional Pia packet encryption
| Pia packet encryption is optional. If the encryption flag is disabled, the packet handler will accept it and skip crypto.
In fixed versions immediately after grabbing a packet, it now checks the crypto flag. If it's plaintext the packet is dropped.
 
This can be used to send a plaintext Pia packet without needing to handle encryption, especially useful if the session-key can't be obtained (online-play matchmaking). This could be combined with other vulns if wanted.
| Sending a plaintext Pia packet without needing to handle encryption.
| v5.9.3, see above.
| v5.9.3 (and later versions)
|
| November 19, 2022
|
|-
| nn::pia::session::{JoinMeshJob/ProcessUpdateMeshJob}::SetStationDataList OOB read/write/vfunc-call
| <code>nn::pia::session::JoinMeshJob::SetStationDataList</code>is called by <code>nn::pia::session::MeshProtocol::ParseJoinResponse(nn::pia::transport::ReceivedMessageAccessor const&)></code> with the ReceivedMessageAccessor buffer.
SetStationDataList will update state and immediately return if the join was denied. It will also validate the num_mesh_stations field against state. ParseJoinResponse also essentially verifies that the message was received from the host device.
 
The input buffer size is ignored.
 
The num_fragments field must be value 1 or <=3 otherwise it will return, there's two seperate code blocks handling these.
 
Other than the checks at the start, there's no validation for the index fields. So large enough values could result in OOB-reads.
 
When handling multiple fragments, it will loop through the stationinfo list. There is no validation for the u8 count field or the baseindex field. It calls a vfunc from obj baseptr+index*{entrysize} with data from the buffer, where index starts with the above baseindex field. Afterwards, an u8 is copied into an u32 array (with certain versions an u16 is deserialized into an u16 array).
 
<code>nn::pia::session::ProcessUpdateMeshJob::UpdateStationDataList</code> is (eventually) called from <code>nn::pia::session::MeshProtocol::ParseUpdateMesh</code>, which has similar issues to the above.
 
Note that ParseJoinResponse/ParseUpdateMesh essentially require the message to be received from the host device.
 
With fixed versions (v5.18.98, exact version unknown) various validation was added. Additional/updated validation was added in a later version (v5.31.0, exact version unknown).
| OOB read/write / vfunc call where the object is selected by an OOB index, triggered by a Pia MeshProtocol message.
| v5.18.98 and v5.31.0 (exact versions unknown).
| v5.31.0
| November 18, 2022
| November 21, 2022
| [[User:Yellows8|yellows8]]
|-
| Insecure encryption
| Originally Pia packets used AES-ECB encryption. As documented [https://github.com/Kinnay/NintendoClients/wiki/Pia-Overview here] it was later changed with v5.7.0 to AES-GCM. Each 0x10-byte block would have the same encrypted block output where the plaintext 0x10-byte data is the same.
The mechanism for generating the Pia SessionKey for LAN has also changed over time.
 
The [https://github.com/Kinnay/NintendoClients/wiki/LAN-Protocol LAN] non-Pia-encapsulated packets were also originally sent in plaintext, however at some point it was changed to mostly encrypted.
|
| AES-GCM fix: v5.7.0
|
|
|
|
|-
| nn::pia::transport::UnreliableProtocol::Dispatch buffer overflow
| <code>nn::pia::transport::UnreliableProtocol::Dispatch</code> memcpys data from the message into a list entry, without size validation. If the pia packet is the max size, it will only overwrite the 0xC-bytes which were written to immediately before the memcpy: the u32 size and the 8-byte StationAddress (depending on the version there can also be 4-byte padding after the size for alignment).
| <code>nn::pia::transport::UnreliableProtocol::Dispatch</code> memcpys data from the message into a list entry, without size validation. If the pia packet is the max size, it will only overwrite the 0xC-bytes which were written to immediately before the memcpy: the u32 size and the 8-byte StationAddress (depending on the version there can also be 4-byte padding after the size for alignment).
However, nn::pia::transport::UnreliableProtocol::Receive will clamp the size from the list entry to the outbuf size when doing the memcpy. So this is probably useless.
However, nn::pia::transport::UnreliableProtocol::Receive will clamp the size from the list entry to the outbuf size when doing the memcpy. So this is probably useless.
 
 
It's unknown whether there's a version where more data could be overwritten, and whether that would be useful.
It's unknown whether there's a version where more data could be overwritten, and whether that would be useful.
 
 
This is fixed in v5.31.0, exact version unknown. The message is dropped if too large in Dispatch.
This is fixed in v5.31.0, exact version unknown. The message is dropped if too large in Dispatch.
| Small buffer overflow triggered by a Pia UnreliableProtocol message.
| Small buffer overflow triggered by a Pia UnreliableProtocol message.
| v5.31.0, exact version unknown.
| v5.31.0, exact version unknown.
| v5.18.98/v5.31.0
| v5.18.98/v5.31.0
| November 2022
| November 2022
| November 29, 2022
| November 29, 2022
| [[User:Yellows8|yellows8]]
| [[User:Yellows8|yellows8]]
|-
| Uncleared input structs for [[LDN_services|LDN]]
| The Pia code using ldn CreateNetwork*/ConnectNetwork*/Scan doesn't properly memset the input data for SecurityConfig/ScanFilter (when keysize is less than 0x40 for the former). Hence, infoleak from games is sent to ldn (structs are located on stack, so stack data is leaked). This requires ldn compromise/mitm to obtain the leaked data - these are not sent over the network.
With v6.20.1 (exact version unknown - fix isn't present in v5.32.0), the code using Scan* now clears the input ScanFilter properly. With v6.25.1 (exact version unknown - fix isn't present in v6.23.3), the code using CreateNetwork*/ConnectNetwork* now clears the input SecurityConfig properly.
| Infoleak from games with LDN cmds, requires compromised sysmodule/mitm.
| v6.20.1 and v6.25.1, exact versions unknown.
| v5.32.0/v6.20.1/v6.23.3/v6.25.1
|
| December 7, 2022
|
|}
 
=== ENL ===
This section documents vulnerabilities for [https://github.com/kinnay/NintendoClients/wiki/ENL-Protocol ENL].
A framework used by Nintendo games including Mario Kart 8 Deluxe, Splatoon 2 / 3, Mario Maker 2, and more.
 
Fun fact, this library appears to re-use network code and concepts from older Nintendo titles such as Mario Kart 7 and some Wii multiplayer games.
 
 
{| class="wikitable" border="1"
|-
!  Summary
!  Description
!  Successful exploitation result
!  Fixed in Enl version
!  Last Enl version this flaw was checked for
!  Timeframe this was discovered
!  Public disclosure timeframe
!  Discovered by
|-
| enl::TransportManager::updateReceiveBuffer_() nullptr deref
| enl::TransportManager::updateReceiveBuffer_() is called when the ENL framework receives a PIA packet from a client, it will fully trust the ENL header which includes a "ContentTransporter" type (ID) and a length.
The function will try to fetch the content transporter by ID using <code>enl::TransportManager::getContentTransporter(unsigned char const &)</code>, it returns NULL if there's no content transporter with the same ID
 
*NOTE: The function may be inlined
 
Then it will try to call a virtual method: <code>virtual size_t readyReceiveStream(enl::RamReadStream&, enl::Buffer*, size_t)</code>, dereferencing the pointer to fetch the vtable ptr
 
[https://gist.github.com/Rambo6Glaz/c088e2ed7a12db08f6322e9f7a3c4911 Pseudocode of the function before it was fixed]
 
| nullptr dereference triggered by an invalid content transporter type in the ENL header (it will crash the game/process)
| Unknown
| Depends on the game
| Early April 2022
| November 16, 2022
| [[User:Rambo6Glaz|Rambo6Glaz]], [https://github.com/kinnay Yannik] (massive RE help)
|}
 
There's another one more interesting but it will have to wait a bit :)
 
== Games ==
{| class="wikitable" border="1"
|-
! Game
!  Summary
!  Description
!  Impact
!  Fixed in version
!  Timeframe this was discovered
!  Public disclosure timeframe
!  Discovered by
|-
|-
| Uncleared input structs for [[LDN_services|LDN]]
| Mario Kart World
| The Pia code using ldn CreateNetwork*/ConnectNetwork*/Scan doesn't properly memset the input data for SecurityConfig/ScanFilter (when keysize is less than 0x40 for the former). Hence, infoleak from games is sent to ldn (structs are located on stack, so stack data is leaked). This requires ldn compromise/mitm to obtain the leaked data - these are not sent over the network.
| ASLR leak in application data
With v6.20.1 (exact version unknown - fix isn't present in v5.32.0), the code using Scan* now clears the input ScanFilter properly. With v6.25.1 (exact version unknown - fix isn't present in v6.23.3), the code using CreateNetwork*/ConnectNetwork* now clears the input SecurityConfig properly.
| A memory address can be leaked by changing your username to something short, and hosting a network session in LAN mode (press L + R + Left Stick on the main menu to enable this). The memory address can be found in bytes 12 - 19 of the application data that is transmitted in response to a browse request.
| Infoleak from games with LDN cmds, requires compromised sysmodule/mitm.
| v6.20.1 and v6.25.1, exact versions unknown.
| v5.32.0/v6.20.1/v6.23.3/v6.25.1
|
| December 7, 2022
|
|}
 
=== ENL ===
This section documents vulnerabilities for [https://github.com/kinnay/NintendoClients/wiki/ENL-Protocol ENL].
A framework used by Nintendo games including Mario Kart 8 Deluxe, Splatoon 2 / 3, Mario Maker 2, and more.


Fun fact, this library appears to re-use network code and concepts from older Nintendo titles such as Mario Kart 7 and some Wii multiplayer games.
'''Note:''' there is more uninitialized data in the packet, but the memory address is probably the most interesting part. The vulnerability was fixed by clearing the application data with zeros, before filling in the information.


[https://hackerone.com/reports/3463719 HackerOne report]


{| class="wikitable" border="1"
This stack infoleak was also present in the [[LDN_services|ldn]] AdvertiseData.
| A memory address can leaked (this is a requirement for many types of attacks).
| 1.5.0
| December 12, 2025
| February 19, 2026
| [https://github.com/kinnay Yannik], yellows8 (ldn)
|-
|-
!  Summary
| Splatoon 3
!  Description
| Anticheat Seed Randomization Weakness
!  Successful exploitation result
| This oversight of seed generation would allow an attacker to quickly compute all code hashes, and modify game code, while still producing a valid ch1 hash.
!  Fixed in Enl version
!  Last Enl version this flaw was checked for
!  Timeframe this was discovered
!  Public disclosure timeframe
!  Discovered by
|-
| enl::TransportManager::updateReceiveBuffer_() nullptr deref
| enl::TransportManager::updateReceiveBuffer_() is called when the ENL framework receives a PIA packet from a client, it will fully trust the ENL header which includes a "ContentTransporter" type (ID) and a length.
The function will try to fetch the content transporter by ID using <code>enl::TransportManager::getContentTransporter(unsigned char const &)</code>, it returns NULL if there's no content transporter with the same ID
 
*NOTE: The function may be inlined
 
Then it will try to call a virtual method: <code>virtual size_t readyReceiveStream(enl::RamReadStream&, enl::Buffer*, size_t)</code>, dereferencing the pointer to fetch the vtable ptr


[https://gist.github.com/Rambo6Glaz/c088e2ed7a12db08f6322e9f7a3c4911 Pseudocode of the function before it was fixed]
[https://hackerone.com/reports/3042475 HackerOne report]
 
| Allows an attacker to bypass the ch1 anti-cheat hashing mechanism.
| nullptr dereference triggered by an invalid content transporter type in the ENL header (it will crash the game/process)
| 10.0.0
| Unknown
| March 17, 2025
| Depends on the game
| February 19, 2026
| Early April 2022
| hana2736
| November 16, 2022
| [[User:Rambo6Glaz|Rambo6Glaz]], Kinnay (massive RE help)
|}
|}
There's another one more interesting but it will have to wait a bit :)