mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-05 13:20:11 +00:00
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:
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user