summaryrefslogtreecommitdiff
path: root/Kernel
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2021-01-21 00:06:19 +0100
committerAndreas Kling <kling@serenityos.org>2021-01-21 00:14:56 +0100
commit928ee2c791b0789ad4e845062a8212f50fe299e3 (patch)
tree8abab1650b870d04b58afe8f3250e4619e563667 /Kernel
parent1f53dd0943c02baded5f10cdeb1cd8aea8cf6fa5 (diff)
downloadserenity-928ee2c791b0789ad4e845062a8212f50fe299e3.zip
Kernel: Don't let signals unblock threads while handling a page fault
It was possible to signal a process while it was paging in an inode backed VM object. This would cause the inode read to EINTR, and the page fault handler would assert. Solve this by simply not unblocking threads due to signals if they are currently busy handling a page fault. This is probably not the best way to solve this issue, so I've added a FIXME to that effect.
Diffstat (limited to 'Kernel')
-rw-r--r--Kernel/Arch/i386/CPU.cpp9
-rw-r--r--Kernel/Thread.cpp8
-rw-r--r--Kernel/Thread.h4
3 files changed, 21 insertions, 0 deletions
diff --git a/Kernel/Arch/i386/CPU.cpp b/Kernel/Arch/i386/CPU.cpp
index e0aa077c3b..20be34443f 100644
--- a/Kernel/Arch/i386/CPU.cpp
+++ b/Kernel/Arch/i386/CPU.cpp
@@ -25,6 +25,7 @@
*/
#include <AK/Assertions.h>
+#include <AK/ScopeGuard.h>
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <AK/Types.h>
@@ -257,6 +258,14 @@ void page_fault_handler(TrapFrame* trap)
}
auto current_thread = Thread::current();
+
+ if (current_thread)
+ current_thread->set_handling_page_fault(true);
+ ScopeGuard guard = [current_thread] {
+ if (current_thread)
+ current_thread->set_handling_page_fault(false);
+ };
+
if (!faulted_in_kernel && !MM.validate_user_stack(current_thread->process(), VirtualAddress(regs.userspace_esp))) {
dbgln("Invalid stack pointer: {}", VirtualAddress(regs.userspace_esp));
handle_crash(regs, "Bad stack on page fault", SIGSTKFLT);
diff --git a/Kernel/Thread.cpp b/Kernel/Thread.cpp
index 209baa35bb..5e7a89eda5 100644
--- a/Kernel/Thread.cpp
+++ b/Kernel/Thread.cpp
@@ -166,6 +166,12 @@ void Thread::unblock(u8 signal)
return;
ASSERT(m_blocker);
if (signal != 0) {
+ if (is_handling_page_fault()) {
+ // Don't let signals unblock threads that are blocked inside a page fault handler.
+ // This prevents threads from EINTR'ing the inode read in an inode page fault.
+ // FIXME: There's probably a better way to solve this.
+ return;
+ }
if (!m_blocker->can_be_interrupted() && !m_should_die)
return;
m_blocker->set_interrupted_by_signal(signal);
@@ -461,6 +467,8 @@ u32 Thread::pending_signals_for_state() const
{
ASSERT(g_scheduler_lock.own_lock());
constexpr u32 stopped_signal_mask = (1 << (SIGCONT - 1)) | (1 << (SIGKILL - 1)) | (1 << (SIGTRAP - 1));
+ if (is_handling_page_fault())
+ return 0;
return m_state != Stopped ? m_pending_signals : m_pending_signals & stopped_signal_mask;
}
diff --git a/Kernel/Thread.h b/Kernel/Thread.h
index 761fcb33a8..f1096420e2 100644
--- a/Kernel/Thread.h
+++ b/Kernel/Thread.h
@@ -1155,6 +1155,9 @@ public:
return m_kernel_stack_region;
}
+ bool is_handling_page_fault() const { return m_handling_page_fault; }
+ void set_handling_page_fault(bool b) { m_handling_page_fault = b; }
+
private:
IntrusiveListNode m_runnable_list_node;
@@ -1258,6 +1261,7 @@ private:
JoinBlockCondition m_join_condition;
Atomic<bool> m_is_active { false };
bool m_is_joinable { true };
+ bool m_handling_page_fault { false };
unsigned m_syscall_count { 0 };
unsigned m_inode_faults { 0 };