fix(libkernel): fail the signal action, mask, stack and pending calls with EFAULT on an unusable pointer

FreeBSD 11.4's sys_sigaction, sys_sigprocmask and sys_sigaltstack copy
their input in before validating it and copy the old value out after
the change, and sys_sigpending copies out; a bad pointer gives EFAULT.
The guest pointers were dereferenced directly.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
GruzzberryandClaude Sonnet 5.5 committed 2026-10-26 09:28:04 +00:00
1 parent 4e43321bd6
commit 1fbf3a42d2
4 files changed
+81 -10

No files matched your search

+1
View File
@@ -31,6 +31,7 @@ extern "C" int CopyGuestPath_nid_no_patch(const char* source, std::string& desti
extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path);
extern "C" bool GuestCopyOut_nid_no_patch(void* destination, const void* source, std::size_t bytes);
extern "C" bool GuestCopyIn_nid_no_patch(void* destination, const void* source, std::size_t bytes);
extern "C" std::filesystem::path ResolvePathOrRelinkedModule_nid_no_patch(const char* path);
extern "C" std::filesystem::path ResolveArchivedPath_nid_no_patch(const char* path, bool module = false);
extern "C" std::filesystem::path ResolveDiskPath_nid_no_patch(const char* path);
+7
View File
@@ -837,6 +837,13 @@ extern "C" bool GuestCopyOut_nid_no_patch(void* destination, const void* source,
#endif
}
extern "C" bool GuestCopyIn_nid_no_patch(void* destination, const void* source, std::size_t bytes) {
if (bytes == 0) return true;
const auto start = reinterpret_cast<std::uintptr_t>(source);
if (start == 0 || bytes > UINTPTR_MAX - start) return false;
return CopyGuestBytes(destination, source, bytes);
}
extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path) {
auto resolved = ResolveLocked(path, false, true);
AppArchiveRefuseOnly_nid_no_patch(resolved, path, "reading");
+32 -10
View File
@@ -542,7 +542,7 @@ thread_local GuestStack alternateStack{nullptr, 0, SsDisable};
namespace {
int ChangeBlockedMask(int how, const GuestSignalSet* set, GuestSignalSet* previousSet, bool releaseHolds, bool* caught = nullptr) {
int ChangeBlockedMask(int how, const GuestSignalSet* set, GuestSignalSet* previousSet, bool releaseHolds, bool* caught = nullptr, GuestSignalSet* guestPrevious = nullptr) {
if (set != nullptr && (how < 1 || how > 3)) { *__error_nid_postfix() = 22; return -1; }
{
std::lock_guard lock(registration);
@@ -576,8 +576,10 @@ int ChangeBlockedMask(int how, const GuestSignalSet* set, GuestSignalSet* previo
}
}
}
const bool faulted = guestPrevious != nullptr && !GuestCopyOut_nid_no_patch(guestPrevious, previousSet, sizeof(*previousSet));
std::atomic_thread_fence(std::memory_order_seq_cst);
if (DeliverUnblockedPending() && caught != nullptr) *caught = true;
if (faulted) { *__error_nid_postfix() = 14; return -1; }
return 0;
}
@@ -603,6 +605,11 @@ GuestHandler APS5_VABI signal_nid_postfix(int guest, GuestHandler handler) {
return previous;
}
int APS5_VABI sigaction_nid_postfix(int guest, const GuestSigaction* action, GuestSigaction* previous) {
GuestSigaction requested{};
if (action != nullptr) {
if (!GuestCopyIn_nid_no_patch(&requested, action, sizeof(requested))) { *__error_nid_postfix() = 14; return -1; }
action = &requested;
}
if (guest < 1 || guest > MaxSignal) { *__error_nid_postfix() = 22; return -1; }
if (action && action->handler > 1 && (action->flags & ~SaDefinedFlags) != 0) { *__error_nid_postfix() = 22; return -1; }
if (action && (guest == GuestSigkill || guest == GuestSigstop) && action->handler != 0) { *__error_nid_postfix() = 22; return -1; }
@@ -614,7 +621,7 @@ int APS5_VABI sigaction_nid_postfix(int guest, const GuestSigaction* action, Gue
dispositions[guest] = *action;
DiscardPendingIfIgnored(guest, *action);
}
if (previous) *previous = old;
if (previous && !GuestCopyOut_nid_no_patch(previous, &old, sizeof(old))) { *__error_nid_postfix() = 14; return -1; }
return 0;
}
int APS5_VABI _sigaction_nid_postfix(int guest, const GuestSigaction* action, GuestSigaction* previous) {
@@ -664,18 +671,27 @@ int APS5_VABI pthread_kill_nid_postfix(Pthread thread, int guest) {
}
int APS5_VABI sigaltstack_nid_postfix(const GuestStack* stack, GuestStack* previous) {
GuestStack replacement = alternateStack;
GuestStack requested{};
if (stack != nullptr) {
if (!GuestCopyIn_nid_no_patch(&requested, stack, sizeof(requested))) { *__error_nid_postfix() = 14; return -1; }
stack = &requested;
if ((stack->flags & ~SsDisable) != 0) { *__error_nid_postfix() = 22; return -1; }
if (stack->flags & SsDisable) replacement.flags = SsDisable;
else if (stack->size < MinSignalStackSize) { *__error_nid_postfix() = 12; return -1; }
else replacement = {stack->sp, stack->size, 0};
}
if (previous != nullptr) *previous = alternateStack;
const GuestStack old = alternateStack;
alternateStack = replacement;
if (previous != nullptr && !GuestCopyOut_nid_no_patch(previous, &old, sizeof(old))) { *__error_nid_postfix() = 14; return -1; }
return 0;
}
int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet) {
return ChangeBlockedMask(how, set, previousSet, true);
GuestSignalSet requested{}, old{};
if (set != nullptr) {
if (!GuestCopyIn_nid_no_patch(&requested, set, sizeof(requested))) { *__error_nid_postfix() = 14; return -1; }
set = &requested;
}
return ChangeBlockedMask(how, set, previousSet != nullptr ? &old : nullptr, true, nullptr, previousSet);
}
int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSet) {
@@ -684,8 +700,10 @@ int APS5_VABI sigprocmask_nid_postfix(int how, const void* set, void* previousSe
}
int APS5_VABI pthread_sigmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet) {
if (set != nullptr && (how < 1 || how > 3)) return 22;
return _sigprocmask_nid_postfix(how, set, previousSet);
const int saved = *__error_nid_postfix();
const int error = _sigprocmask_nid_postfix(how, set, previousSet) == 0 ? 0 : *__error_nid_postfix();
*__error_nid_postfix() = saved;
return error;
}
int APS5_VABI sigtimedwait_nid_postfix(const GuestSignalSet* set, void* info, const KernelTimespec* timeout) {
@@ -745,10 +763,14 @@ int APS5_VABI _sigsuspend_nid_postfix(const GuestSignalSet* set) {
int APS5_VABI sigpending_nid_postfix(GuestSignalSet* set) {
if (set == nullptr) { *__error_nid_postfix() = 14; return -1; }
auto& pending = Pending();
std::lock_guard lock(pending.lock);
for (int word = 0; word < 4; ++word)
set->bits[word] = pending.words[word].load(std::memory_order_relaxed) & BlockedWord(word);
GuestSignalSet result{};
{
auto& pending = Pending();
std::lock_guard lock(pending.lock);
for (int word = 0; word < 4; ++word)
result.bits[word] = pending.words[word].load(std::memory_order_relaxed) & BlockedWord(word);
}
if (!GuestCopyOut_nid_no_patch(set, &result, sizeof(result))) { *__error_nid_postfix() = 14; return -1; }
return 0;
}
}
+41
View File
@@ -66,8 +66,49 @@ void APS5_VABI MaskCallback(int value) {
}
static void Require(bool value) { if (!value) std::abort(); }
static void CheckSignalPointerFaults() {
constexpr std::size_t page = 0x4000;
void* memory = nullptr;
Require(sceKernelMapFlexibleMemory(&memory, page, 3, 0) == 0 && memory != nullptr);
Require(sceKernelMprotect(memory, page, 1) == 0);
const auto readOnly = memory;
const auto unreadable = reinterpret_cast<void*>(std::uintptr_t{0x10});
const auto fails = [](int result) { return result == -1 && *__error_nid_postfix() == 14; };
constexpr int user = 30;
GuestSigaction previous{};
Require(fails(sigaction_nid_postfix(user, static_cast<const GuestSigaction*>(unreadable), nullptr)));
Require(fails(sigaction_nid_postfix(0, static_cast<const GuestSigaction*>(unreadable), nullptr)));
Require(fails(sigaction_nid_postfix(user, nullptr, static_cast<GuestSigaction*>(unreadable))));
Require(fails(sigaction_nid_postfix(user, nullptr, static_cast<GuestSigaction*>(readOnly))));
Require(sigaction_nid_postfix(0, nullptr, &previous) == -1 && *__error_nid_postfix() == 22);
Require(sigaction_nid_postfix(user, nullptr, &previous) == 0);
GuestSignalSet blocked{}, set{{1u << (user - 1), 0, 0, 0}}, none{};
Require(sigprocmask_nid_postfix(3, nullptr, &blocked) == 0);
Require(fails(sigprocmask_nid_postfix(1, unreadable, nullptr)));
Require(fails(sigprocmask_nid_postfix(9, unreadable, nullptr)));
Require(sigprocmask_nid_postfix(9, &set, nullptr) == -1 && *__error_nid_postfix() == 22);
Require(fails(sigprocmask_nid_postfix(1, nullptr, unreadable)));
Require(fails(sigprocmask_nid_postfix(1, nullptr, readOnly)));
Require(sigprocmask_nid_postfix(1, nullptr, &none) == 0 && std::memcmp(&none, &blocked, sizeof(none)) == 0);
Require(fails(sigprocmask_nid_postfix(1, &set, readOnly)));
Require(sigprocmask_nid_postfix(1, nullptr, &none) == 0 && (none.bits[0] & set.bits[0]) != 0);
Require(sigprocmask_nid_postfix(3, &blocked, nullptr) == 0);
Require(pthread_sigmask_nid_postfix(1, static_cast<const GuestSignalSet*>(unreadable), nullptr) == 14);
Require(pthread_sigmask_nid_postfix(1, nullptr, static_cast<GuestSignalSet*>(readOnly)) == 14);
Require(pthread_sigmask_nid_postfix(9, static_cast<const GuestSignalSet*>(unreadable), nullptr) == 14);
Require(pthread_sigmask_nid_postfix(9, &set, nullptr) == 22);
Require(sigprocmask_nid_postfix(3, &blocked, nullptr) == 0);
Require(fails(sigaltstack_nid_postfix(static_cast<const GuestStack*>(unreadable), nullptr)));
Require(fails(sigaltstack_nid_postfix(nullptr, static_cast<GuestStack*>(unreadable))));
Require(fails(sigaltstack_nid_postfix(nullptr, static_cast<GuestStack*>(readOnly))));
Require(fails(sigpending_nid_postfix(static_cast<GuestSignalSet*>(unreadable))));
Require(fails(sigpending_nid_postfix(static_cast<GuestSignalSet*>(readOnly))));
Require(fails(sigpending_nid_postfix(nullptr)));
}
int main() {
const auto invalid = reinterpret_cast<Handler>(static_cast<std::uintptr_t>(-1));
CheckSignalPointerFaults();
const auto ignore = reinterpret_cast<Handler>(std::uintptr_t{1});
Require(signal_nid_postfix(9, Callback) == invalid);
Require(*__error_nid_postfix() == 22);