Merge pull request #13752 from Gruzzberry/fix/sockopt-efault

fix(libkernel): fail getsockopt and setsockopt with EFAULT on an unusable pointer
This commit is contained in:
Dean authored and GitHub committed 2026-11-03 06:31:52 +13:00
commit a4eca2a056
3 files changed
+132 -36

No files matched your search

@@ -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 <algorithm>
@@ -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<const unsigned char*>(&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<const unsigned char*>(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<const unsigned char*>(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<NativeOptionValue>(&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<int>(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<NativeHandle>(native), IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<NativeOptionValue>(&enabled), sizeof(enabled))) return HostError();
return 0;
+13 -13
View File
@@ -1300,6 +1300,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<std::int32_t>(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);
@@ -1317,26 +1318,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<std::uintptr_t>(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;
+96
View File
@@ -7,6 +7,12 @@
#include <cstdlib>
#include <cstring>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/mman.h>
#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<void*>(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<void*>(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<std::uint32_t*>(0x10), static_cast<std::uint32_t*>(unreadable), static_cast<std::uint32_t*>(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<std::uint32_t*>(0x10), static_cast<std::uint32_t*>(unreadable), static_cast<std::uint32_t*>(readOnly), static_cast<std::uint32_t*>(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();