From 1f2365a5d1b47bf87a928d434dc26ceefe9b35b8 Mon Sep 17 00:00:00 2001 From: Gruzzberry Date: Sat, 31 Oct 2026 03:29:33 +0000 Subject: [PATCH] fix(libkernel): fail getsockopt and setsockopt with EFAULT on an unusable pointer Co-Authored-By: Claude Sonnet 5.5 --- .../libkernel/Socket/src/SocketOptions.cpp | 46 ++++----- core/libs/prx/libkernel/Socket/src/Udp.cpp | 26 ++--- core/libs/tests/GuestSocketOptions.cpp | 96 +++++++++++++++++++ 3 files changed, 132 insertions(+), 36 deletions(-) diff --git a/core/libs/prx/libkernel/Socket/src/SocketOptions.cpp b/core/libs/prx/libkernel/Socket/src/SocketOptions.cpp index 027d43d4c1..e5e046868a 100644 --- a/core/libs/prx/libkernel/Socket/src/SocketOptions.cpp +++ b/core/libs/prx/libkernel/Socket/src/SocketOptions.cpp @@ -13,7 +13,6 @@ #endif #endif #include "prx/libkernel/Socket/include/SocketError.hpp" -#include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/Socket/include/SocketOptions.hpp" #include "prx/libc/include/General.hpp" #include @@ -158,14 +157,14 @@ int IpReceiveBit(int option) { } } +int ReadValue(void* destination, const void* source, std::size_t bytes) { + return GuestCopyIn_nid_no_patch(destination, source, bytes) ? 0 : ErrorFault; +} + int Copy(void* output, std::uint32_t* length, const void* value, std::uint32_t size) { const std::uint32_t count = output ? std::min(*length, size) : 0; - const GuestArena::HostWrite destination(count == 0 ? nullptr : output, count); - if (!destination.Open()) return ErrorFault; - const GuestArena::HostWrite lengthOutput(length, sizeof(*length)); - if (!lengthOutput.Open()) return ErrorFault; - if (count != 0) std::memcpy(output, value, count); - *length = count; + if (count != 0 && !GuestCopyOut_nid_no_patch(output, value, count)) return ErrorFault; + if (!GuestCopyOut_nid_no_patch(length, &count, sizeof(count))) return ErrorFault; return 0; } @@ -182,7 +181,7 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int fa if (option == GuestLinger) { if (length < sizeof(GuestLingerValue)) return ErrorInvalid; GuestLingerValue guest; - std::memcpy(&guest, value, sizeof(guest)); + if (const int fault = ReadValue(&guest, value, sizeof(guest))) return fault; if (guest.seconds < 0 || guest.seconds > MaximumLinger) return ErrorDomain; #ifdef _WIN32 linger request{}; @@ -199,7 +198,7 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int fa if (option == GuestSendTimeout || option == GuestReceiveTimeout) { if (length < sizeof(GuestTimeval)) return ErrorInvalid; GuestTimeval time; - std::memcpy(&time, value, sizeof(time)); + if (const int fault = ReadValue(&time, value, sizeof(time))) return fault; if (time.seconds < 0 || time.microseconds < 0 || time.microseconds >= 1000000) return ErrorDomain; (option == GuestSendTimeout ? state.sendTimeout : state.receiveTimeout) = TimeoutOf(time); return 0; @@ -207,7 +206,7 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int fa if (option == GuestNoSigpipe || option == GuestNonBlocking) { if (length < sizeof(int)) return ErrorInvalid; int number; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; if (option == GuestNoSigpipe) { state.noSignal = number != 0; return 0; @@ -228,7 +227,7 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int fa if (native_boolean || buffer || lowWater) { if (length < sizeof(int)) return ErrorInvalid; int number; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; if (buffer || lowWater) { if (number < 1) return ErrorInvalid; EnsureBuffers(state, family, type); @@ -253,7 +252,7 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int fa if ((option & (KeptFlags | WriteOnlyFlags)) != 0 && (option & (option - 1)) == 0) { if (length < sizeof(int)) return ErrorInvalid; int number; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; if ((option & KeptFlags) == 0) return 0; const int flags = number != 0 ? state.keptFlags | option : state.keptFlags & ~option; if (option == GuestReuseAddress || option == GuestReusePort) { @@ -355,7 +354,7 @@ int SetTcpLevelOption(std::uintptr_t native, SocketOptionState& state, bool tcp, return ErrorNoProtocolOption; if (length < sizeof(int)) return ErrorInvalid; int number; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; if (const int bit = TcpFlagBit(option); bit != 0) { state.tcpFlags = number != 0 ? state.tcpFlags | bit : state.tcpFlags & ~bit; return 0; @@ -413,7 +412,7 @@ int SetIpLevelOption(std::uintptr_t native, SocketOptionState& state, bool ipv4, if (option == GuestIpAddMembership || option == GuestIpDropMembership) { if (length < sizeof(ip_mreq)) return ErrorInvalid; ip_mreq request{}; - std::memcpy(&request, value, sizeof(request)); + if (const int fault = ReadValue(&request, value, sizeof(request))) return fault; const auto* group = reinterpret_cast(&request.imr_multiaddr); if ((group[0] & 0xf0) != 0xe0) return ErrorInvalid; std::uint32_t groupBits; @@ -441,14 +440,15 @@ int SetIpLevelOption(std::uintptr_t native, SocketOptionState& state, bool ipv4, if (length < sizeof(in_addr)) return ErrorInvalid; in_addr address{}; if (length == MulticastRequestWithIndex) { + unsigned char request[MulticastRequestWithIndex]; + if (const int fault = ReadValue(request, value, sizeof(request))) return fault; std::int32_t index; - std::memcpy(&index, static_cast(value) + 8, sizeof(index)); + std::memcpy(&index, request + 8, sizeof(index)); if (index != 0) throw std::runtime_error("IP_MULTICAST_IF: an interface index is not supported"); - const auto* addresses = static_cast(value); - if (std::any_of(addresses, addresses + 8, [](unsigned char byte) { return byte != 0; })) + if (std::any_of(request, request + 8, [](unsigned char byte) { return byte != 0; })) throw std::runtime_error("IP_MULTICAST_IF: an ip_mreqn with a non-zero address is not supported"); } else { - std::memcpy(&address, value, sizeof(address)); + if (const int fault = ReadValue(&address, value, sizeof(address))) return fault; } if (::setsockopt(handle, IPPROTO_IP, IP_MULTICAST_IF, reinterpret_cast(&address), sizeof(address))) return HostError(); std::memcpy(&state.multicastInterface, &address, sizeof(address)); @@ -457,14 +457,14 @@ int SetIpLevelOption(std::uintptr_t native, SocketOptionState& state, bool ipv4, if (const int bit = IpReceiveBit(option); bit != 0) { if (length < sizeof(int)) return ErrorInvalid; int number; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; state.ipReceiveOptions = number != 0 ? state.ipReceiveOptions | bit : state.ipReceiveOptions & ~bit; return 0; } if (option == GuestIpTimeToLive || option == GuestIpTypeOfService) { if (length < sizeof(int)) return ErrorInvalid; int number; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; const int stored = number & 0xff; if (option == GuestIpTypeOfService) { state.ipTypeOfService = stored; @@ -478,11 +478,11 @@ int SetIpLevelOption(std::uintptr_t native, SocketOptionState& state, bool ipv4, unsigned int number; if (length == 1) { unsigned char byte; - std::memcpy(&byte, value, 1); + if (const int fault = ReadValue(&byte, value, 1)) return fault; number = byte; } else { if (length < sizeof(number)) return ErrorInvalid; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; if (option == GuestIpMulticastTimeToLive && number > 255) return ErrorInvalid; } const int host = option == GuestIpMulticastLoop ? ((number & 0xff) != 0 ? 1 : 0) : static_cast(number); @@ -521,7 +521,7 @@ int SetIpv6LevelOption(std::uintptr_t native, bool ipv6, int option, const void* if (option != GuestIpv6Only) return ErrorNoProtocolOption; if (length != sizeof(int)) return ErrorInvalid; int number; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; const int enabled = number != 0 ? 1 : 0; if (::setsockopt(static_cast(native), IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&enabled), sizeof(enabled))) return HostError(); return 0; diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index 71a3ada8b0..482cb9d6b6 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -1223,6 +1223,7 @@ int APS5_VABI _fcntl_nid_postfix(int descriptor, int command, ...) { int APS5_VABI setsockopt_nid_postfix(int descriptor, int level, int option, const void* value, std::uint32_t length) { if (!value && length != 0) return Fail(14); + if (static_cast(length) < 0) return Fail(22); int netError = 0; if (!GuestSockets::IsOpen(descriptor) && KernelSetSocketOption_nid_no_patch(descriptor, level, option, value, length, &netError)) { return netError == 0 ? 0 : Fail(netError); @@ -1240,26 +1241,25 @@ int APS5_VABI setsockopt_nid_postfix(int descriptor, int level, int option, } int APS5_VABI getsockopt_nid_postfix(int descriptor, int level, int option, void* value, std::uint32_t* length) { + std::uint32_t declared = 0; + if (value && !GuestCopyIn_nid_no_patch(&declared, length, sizeof(declared))) return Fail(14); + if (value && !GuestCopyOut_nid_no_patch(length, &declared, sizeof(declared))) return Fail(14); int netError = 0; - if (!GuestSockets::IsOpen(descriptor) && KernelGetSocketOption_nid_no_patch(descriptor, level, option, value, length, &netError)) { - return netError == 0 ? 0 : Fail(netError); + if (!GuestSockets::IsOpen(descriptor) && KernelGetSocketOption_nid_no_patch(descriptor, level, option, value, &declared, &netError)) { + if (netError != 0) return Fail(netError); + return GuestCopyOut_nid_no_patch(length, &declared, sizeof(declared)) ? 0 : Fail(14); } const auto socket = Lookup(descriptor); if (!socket) return -1; if (level != 0xffff && level != 0 && level != 6 && level != 41) return Fail(22); - if (!length) return Fail(22); - if (!value) { - const GuestArena::HostWrite lengthOutput(length, sizeof(*length)); - if (!lengthOutput.Open()) return Fail(14); - *length = 0; - } std::lock_guard lock(socket->modeMutex); const auto native = static_cast(socket->value); - const int error = level == 0 ? GuestSockets::GetIpLevelOption(socket->options, socket->family == 2, socket->type == 2, option, value, length) - : level == 6 ? GuestSockets::GetTcpLevelOption(native, socket->options, socket->family != GuestUnix && socket->type == 1, option, value, length) - : level == 41 ? GuestSockets::GetIpv6LevelOption(native, socket->family == 28, option, value, length) - : GuestSockets::GetSocketLevelOption(native, socket->options, socket->family, socket->type, option, value, length); - return error == 0 ? 0 : Fail(error); + const int error = level == 0 ? GuestSockets::GetIpLevelOption(socket->options, socket->family == 2, socket->type == 2, option, value, &declared) + : level == 6 ? GuestSockets::GetTcpLevelOption(native, socket->options, socket->family != GuestUnix && socket->type == 1, option, value, &declared) + : level == 41 ? GuestSockets::GetIpv6LevelOption(native, socket->family == 28, option, value, &declared) + : GuestSockets::GetSocketLevelOption(native, socket->options, socket->family, socket->type, option, value, &declared); + if (error != 0) return Fail(error); + return GuestCopyOut_nid_no_patch(length, &declared, sizeof(declared)) ? 0 : Fail(14); } int APS5_VABI socket_nid_postfix(int family, int type, int protocol) { const bool nonblocking = (type & GuestNonBlocking) != 0; diff --git a/core/libs/tests/GuestSocketOptions.cpp b/core/libs/tests/GuestSocketOptions.cpp index e33092f87d..0b700e20a5 100644 --- a/core/libs/tests/GuestSocketOptions.cpp +++ b/core/libs/tests/GuestSocketOptions.cpp @@ -7,6 +7,12 @@ #include #include +#ifdef _WIN32 +#include +#else +#include +#endif + namespace { constexpr int Stream = 1; @@ -404,6 +410,95 @@ void UnknownRequestsFail() { } +constexpr int ErrorFault = 14; +constexpr int SoReceiveTimeout = 0x1006; +constexpr int SoNoSigpipe2 = 0x800; + +void* MapPage(bool readable) { +#ifdef _WIN32 + void* page = VirtualAlloc(nullptr, 4096, MEM_COMMIT | MEM_RESERVE, readable ? PAGE_READONLY : PAGE_NOACCESS); + Require(page != nullptr, "VirtualAlloc failed"); +#else + void* page = mmap(nullptr, 4096, readable ? PROT_READ : PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(page != MAP_FAILED, "mmap failed"); +#endif + return page; +} + +void UnmapPage(void* page) { +#ifdef _WIN32 + VirtualFree(page, 0, MEM_RELEASE); +#else + munmap(page, 4096); +#endif +} + +bool GetFails(int descriptor, int level, int option, void* value, std::uint32_t* size, int error) { + return getsockopt_nid_postfix(descriptor, level, option, value, size) == -1 && *__error_nid_postfix() == error; +} + +void BadPointersFail() { + void* unreadable = MapPage(false); + void* readOnly = MapPage(true); + const int stream = socket_nid_postfix(2, Stream, 0); + const int datagram = socket_nid_postfix(2, Datagram, 0); + Require(stream >= 0 && datagram >= 0, "socket failed"); + for (void* bad : {reinterpret_cast(0x10), unreadable}) { + Require(SetFails(stream, SolSocket, SoKeepAlive, bad, sizeof(int), ErrorFault), "setsockopt with an unreadable value must fail with EFAULT (14)"); + Require(GetInt(stream, SoKeepAlive) == 0, "a faulting setsockopt must not change the option"); + Require(SetFails(stream, SolSocket, SoLinger, bad, sizeof(Linger), ErrorFault), "SO_LINGER with an unreadable value must fail with EFAULT"); + Require(SetFails(stream, SolSocket, SoReceiveTimeout, bad, 16, ErrorFault), "SO_RCVTIMEO with an unreadable value must fail with EFAULT"); + Require(SetFails(stream, SolSocket, SoNoSigpipe2, bad, sizeof(int), ErrorFault), "SO_NOSIGPIPE with an unreadable value must fail with EFAULT"); + Require(SetFails(stream, SolSocket, SoSendBuffer, bad, sizeof(int), ErrorFault), "SO_SNDBUF with an unreadable value must fail with EFAULT"); + Require(SetFails(stream, 6, 1, bad, sizeof(int), ErrorFault), "TCP_NODELAY with an unreadable value must fail with EFAULT"); + Require(SetFails(stream, 0, 4, bad, sizeof(int), ErrorFault), "IP_TTL with an unreadable value must fail with EFAULT"); + Require(SetFails(datagram, 0, 12, bad, 8, ErrorFault), "IP_ADD_MEMBERSHIP with an unreadable value must fail with EFAULT"); + Require(SetFails(datagram, 0, 9, bad, 4, ErrorFault), "IP_MULTICAST_IF with an unreadable value must fail with EFAULT"); + Require(SetFails(datagram, 0, 9, bad, 12, ErrorFault), "IP_MULTICAST_IF with an unreadable ip_mreqn must fail with EFAULT"); + Require(SetFails(datagram, 0, 10, bad, 1, ErrorFault), "IP_MULTICAST_TTL with an unreadable byte must fail with EFAULT"); + Require(SetFails(datagram, 0, 10, bad, 4, ErrorFault), "IP_MULTICAST_TTL with an unreadable int must fail with EFAULT"); + Require(SetFails(stream, SolSocket, SoKeepAlive, bad, 2, ErrorInvalid), "a short length is rejected before the value is read"); + Require(SetFails(stream, SolSocket, 0x7777, bad, sizeof(int), ErrorNoProtocolOption), "an unknown option is rejected before the value is read"); + Require(SetFails(stream, 99, SoKeepAlive, bad, sizeof(int), ErrorInvalid), "an unknown level is rejected before the value is read"); + Require(SetFails(-1, SolSocket, SoKeepAlive, bad, sizeof(int), ErrorBadDescriptor), "the descriptor is checked before the value is read"); + } + const int on = 1; + Require(SetFails(stream, SolSocket, SoKeepAlive, &on, 0x80000000u, ErrorInvalid), "a negative length must fail with EINVAL (22)"); + + for (void* bad : {reinterpret_cast(0x10), unreadable, readOnly}) { + std::uint32_t size = sizeof(int); + Require(GetFails(stream, SolSocket, SoType, bad, &size, ErrorFault) && size == sizeof(int), "getsockopt into an unwritable value must fail with EFAULT and keep the length"); + Require(GetFails(stream, SolSocket, SoLinger, bad, &size, ErrorFault), "SO_LINGER into an unwritable value must fail with EFAULT"); + size = 16; + Require(GetFails(stream, SolSocket, SoReceiveTimeout, bad, &size, ErrorFault), "SO_RCVTIMEO into an unwritable value must fail with EFAULT"); + size = sizeof(int); + Require(GetFails(stream, 6, 1, bad, &size, ErrorFault), "TCP_NODELAY into an unwritable value must fail with EFAULT"); + Require(GetFails(stream, 0, 4, bad, &size, ErrorFault), "IP_TTL into an unwritable value must fail with EFAULT"); + Require(GetFails(datagram, 0, 9, bad, &size, ErrorFault), "IP_MULTICAST_IF into an unwritable value must fail with EFAULT"); + Require(GetFails(stream, SolSocket, 0x7777, bad, &size, ErrorNoProtocolOption), "an unknown option is rejected before the value is written"); + Require(GetFails(-1, SolSocket, SoType, bad, &size, ErrorBadDescriptor), "the descriptor is checked before the value is written"); + std::uint32_t zero = 0; + Require(getsockopt_nid_postfix(stream, SolSocket, SoType, bad, &zero) == 0 && zero == 0, "a zero length never touches the value"); + } + + for (std::uint32_t* bad : {reinterpret_cast(0x10), static_cast(unreadable), static_cast(nullptr)}) { + int value = 0; + Require(GetFails(stream, SolSocket, SoType, &value, bad, ErrorFault), "getsockopt with an unreadable length must fail with EFAULT"); + Require(GetFails(-1, SolSocket, SoType, &value, bad, ErrorFault), "the length is read before the descriptor is checked"); + Require(GetFails(stream, SolSocket, 0x7777, &value, bad, ErrorFault), "the length is read before the option is checked"); + } + for (std::uint32_t* bad : {reinterpret_cast(0x10), static_cast(unreadable), static_cast(readOnly), static_cast(nullptr)}) { + Require(GetFails(stream, SolSocket, SoType, nullptr, bad, ErrorFault), "the final length store failing must give EFAULT"); + Require(GetFails(stream, SolSocket, 0x7777, nullptr, bad, ErrorNoProtocolOption), "an option error is reported before the length store"); + } + int value = 0; + std::uint32_t size = sizeof(int); + Require(getsockopt_nid_postfix(stream, SolSocket, SoType, &value, &size) == 0 && value == Stream && size == sizeof(int), "valid getsockopt still works"); + Require(close_nid_postfix(stream) == 0 && close_nid_postfix(datagram) == 0, "close failed"); + UnmapPage(unreadable); + UnmapPage(readOnly); +} + int main(int argc, char** argv) { if (argc > 1 && std::strcmp(argv[1], "multicast") == 0) { MulticastOnTheHost(); @@ -417,6 +512,7 @@ int main(int argc, char** argv) { LengthsFollowFreeBsd(); BuffersFollowThePs5(); NullValues(); + BadPointersFail(); IpLevelFollowsThePs5(); MulticastMembership(); UnknownRequestsFail();