Switch System Flaws: Difference between revisions

 
(11 intermediate revisions by the same user 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,047: Line 1,047:
| June 11, 2024
| June 11, 2024
| [[User:Yellows8|yellows8]] (sysupdate diff)
| [[User:Yellows8|yellows8]] (sysupdate diff)
|-
| [[SSL_services|ssl]] broken RNG
| [[SSL_services|ssl]] uses nn::os::GenerateRandomBytes, but not [[SPL_services|spl]] GenerateRandomBytes. See the RNG entries elsewhere. This is used to seed the NSS global RNG (drbg.c, RNG_GenerateGlobalRandomBytes etc).
If one could somehow determine the data which was returned by nn::os::GenerateRandomBytes during seeding (which is likely difficult), the global RNG would be broken.
With [19.0.0+] nn::os::GenerateRandomBytes usage was replaced with [[SPL_services|spl]] GenerateRandomBytes.
| Breaking [[SSL_services|ssl]] global RNG -> potentially predict RNG data (keys(?)) during TLS comms.
| [[19.0.0]]
| [[19.0.0]]
| December 14, 2021
| October 8, 2024
| [[User:Yellows8|yellows8]]
|-
| [[Audio_services|audren]] uncleared TransferMemory
| audren OpenAudioRenderer uses the input tmem as workmem. The IAudioRenderer dtor doesn't clear the workmem properly. Depending on input params, certain objects stored here have vtables - hence infoleak.
The exact location in the workmem will vary depending on the input params - these objects are dynamically allocated in the workmem.
The following will leak vtables: Sink, Effect.
If the initialization func fails, the tmem is unmapped without clearing it first. It's unknown whether there's a way to actually trigger an infoleak with this however. With [19.0.0+] it's now cleared on failure.
With [19.0.0+] the dtor now clears the workmem when needed.
| Reading leaked data/ptrs from TransferMemory -> defeating ASLR in [[Audio_services|audio]]-sysmodule.
| [[19.0.0]]
| [[19.0.0]]
| December 17, 2022
| October 13, 2024
| [[User:Yellows8|yellows8]]
|-
| [[Audio_services|audren]] UpdateMixes OOB mem-copy
| With nn::audio::server::InfoUpdater::UpdateMixes when nn::audio::server::BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() returns true (requires REV7, which is [7.0.0+]), the mix_id from user input is used without validation as input to <code><nn::audio::server::MixContext::GetInfo(int) const></code>, instead of the counter from the for-loop. This allows one to control the destination MixInfo index which the user-input data is written into. If too large, this will trigger OOB data-copy. Note that the u8 at dest_MixInfo+12 must be non-zero.
Also note that a field is loaded from dest_MixInfo which is used as a splitter_id, so splitters need to be initialized where count is large enough for that id.
With [19.0.0+] after getting the mix_id (loop-index/input) it now does: <code>if (mix_id < 0 || mix_id >= nn::audio::server::MixContext::GetCount()) continue;</code>
| OOB mem-copy in [[Audio_services|audio]]-sysmodule, which for example can be used to overwrite a vtable used immediately after UpdateMixes.
| [[19.0.0]]
| [[19.0.0]]
| December 19, 2022
| October 13, 2024
| [[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]]
|-
| [[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).
For more details see [https://gist.github.com/yellows8/16bb56343d085d2db2ab0adc5d4cef99 here].
| 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]].
| [[21.0.0]]
| [[21.0.0]]
| June ~13, 2025
| November 11, 2025
| [[User:Yellows8|yellows8]]
|-
| [[HID_services|hid:dbg]] AttachHdlsVirtualDevice unvalidated DeviceTypeInternal
| 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.
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.
This functionality is also used eventually by ApplyHdlsNpadAssignmentState and ApplyHdlsStateList.
It's unknown whether there's a way to exploit this. Also note that hid:dbg is not normally accessible to retail titles.
[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]]
|-
| [[Bluetooth_Driver_services|bluetooth]] BSA allowed ATT MTU is too large
| 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.
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.
[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.
| [[15.0.0]]
| [[20.0.0]]
| November 2021?
| November 26, 2025
| [[User:Yellows8|yellows8]]
|}
|}


Line 1,096: Line 1,208:
|
|
| Everyone
| 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.
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
|}
|}


Line 1,186: Line 1,312:
| 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.
| 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.
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.
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.


In fixed versions immediately after the StationIndex validation it now does: <code>if(statefield+0x10<input_size) return;</code>
In fixed versions immediately after the StationIndex validation it now does: <code>if(statefield+0x10<input_size) return;</code>
This is called from nn::pia::session::MeshProtocol::ParseConnectionReport().
| Heap buffer overflow triggered by a Pia MeshProtocol message sent to a host device.
| Heap buffer overflow triggered by a Pia MeshProtocol message sent to a host device.
| v5.9.3, see above.
| v5.9.3, see above.