summaryrefslogtreecommitdiff
path: root/Kernel
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2020-01-02 02:09:25 +0100
committerAndreas Kling <awesomekling@gmail.com>2020-01-02 02:17:12 +0100
commit3dcec260ed0455a1de9ff5ebbdd6480caf1bd6b4 (patch)
tree21fff2596bf8a1147173e06535901fabf32b54e9 /Kernel
parente5ffa960d7b585f4aba0eb18b89a14dee9e7e2b5 (diff)
downloadserenity-3dcec260ed0455a1de9ff5ebbdd6480caf1bd6b4.zip
Kernel: Validate the full range of user memory passed to syscalls
We now validate the full range of userspace memory passed into syscalls instead of just checking that the first and last byte of the memory are in process-owned regions. This fixes an issue where it was possible to avoid rejection of invalid addresses that sat between two valid ones, simply by passing a valid address and a size large enough to put the end of the range at another valid address. I added a little test utility that tries to provoke EFAULT in various ways to help verify this. I'm sure we can think of more ways to test this but it's at least a start. :^) Thanks to mozjag for pointing out that this code was still lacking! Incidentally this also makes backtraces work again. Fixes #989.
Diffstat (limited to 'Kernel')
-rw-r--r--Kernel/Process.cpp18
-rw-r--r--Kernel/VM/MemoryManager.cpp44
-rw-r--r--Kernel/VM/MemoryManager.h11
3 files changed, 50 insertions, 23 deletions
diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp
index 7c5a834fc9..f5532c696a 100644
--- a/Kernel/Process.cpp
+++ b/Kernel/Process.cpp
@@ -1958,7 +1958,7 @@ bool Process::validate_read_from_kernel(VirtualAddress vaddr, ssize_t size) cons
return false;
if (is_kmalloc_address(vaddr.as_ptr()))
return true;
- return validate_read(vaddr.as_ptr(), size);
+ return MM.validate_kernel_read(*this, vaddr, size);
}
bool Process::validate_read_str(const char* str)
@@ -1984,23 +1984,15 @@ bool Process::validate_read(const void* address, ssize_t size) const
if (is_kmalloc_address(address))
return true;
}
- ASSERT(size);
if (!size)
return false;
- if (first_address.page_base() != last_address.page_base()) {
- if (!MM.validate_user_read(*this, last_address))
- return false;
- }
- return MM.validate_user_read(*this, first_address);
+ return MM.validate_user_read(*this, first_address, size);
}
bool Process::validate_write(void* address, ssize_t size) const
{
ASSERT(size >= 0);
VirtualAddress first_address((u32)address);
- VirtualAddress last_address = first_address.offset(size - 1);
- if (last_address < first_address)
- return false;
if (is_ring0()) {
if (is_kmalloc_address(address))
return true;
@@ -2012,11 +2004,7 @@ bool Process::validate_write(void* address, ssize_t size) const
}
if (!size)
return false;
- if (first_address.page_base() != last_address.page_base()) {
- if (!MM.validate_user_write(*this, last_address))
- return false;
- }
- return MM.validate_user_write(*this, first_address);
+ return MM.validate_user_write(*this, first_address, size);
}
pid_t Process::sys$getsid(pid_t pid)
diff --git a/Kernel/VM/MemoryManager.cpp b/Kernel/VM/MemoryManager.cpp
index 0b67633d52..e3728b4ae9 100644
--- a/Kernel/VM/MemoryManager.cpp
+++ b/Kernel/VM/MemoryManager.cpp
@@ -606,6 +606,35 @@ static inline bool is_user_address(VirtualAddress vaddr)
return vaddr.get() >= (8 * MB) && vaddr.get() < 0xc0000000;
}
+template<MemoryManager::AccessSpace space, MemoryManager::AccessType access_type>
+bool MemoryManager::validate_range(const Process& process, VirtualAddress base_vaddr, size_t size) const
+{
+ ASSERT(size);
+ VirtualAddress vaddr = base_vaddr.page_base();
+ VirtualAddress end_vaddr = base_vaddr.offset(size - 1).page_base();
+ if (end_vaddr < vaddr) {
+ dbg() << *current << " Shenanigans! Asked to validate " << base_vaddr << " size=" << size;
+ return false;
+ }
+ const Region* region = nullptr;
+ while (vaddr <= end_vaddr) {
+ if (!region || !region->contains(vaddr)) {
+ if (space == AccessSpace::Kernel)
+ region = kernel_region_from_vaddr(vaddr);
+ if (!region || !region->contains(vaddr))
+ region = user_region_from_vaddr(const_cast<Process&>(process), vaddr);
+ if (!region
+ || (space == AccessSpace::User && !region->is_user_accessible())
+ || (access_type == AccessType::Read && !region->is_readable())
+ || (access_type == AccessType::Write && !region->is_writable())) {
+ return false;
+ }
+ }
+ vaddr = vaddr.offset(PAGE_SIZE);
+ }
+ return true;
+}
+
bool MemoryManager::validate_user_stack(const Process& process, VirtualAddress vaddr) const
{
if (!is_user_address(vaddr))
@@ -614,20 +643,23 @@ bool MemoryManager::validate_user_stack(const Process& process, VirtualAddress v
return region && region->is_user_accessible() && region->is_stack();
}
-bool MemoryManager::validate_user_read(const Process& process, VirtualAddress vaddr) const
+bool MemoryManager::validate_kernel_read(const Process& process, VirtualAddress vaddr, size_t size) const
+{
+ return validate_range<AccessSpace::Kernel, AccessType::Read>(process, vaddr, size);
+}
+
+bool MemoryManager::validate_user_read(const Process& process, VirtualAddress vaddr, size_t size) const
{
if (!is_user_address(vaddr))
return false;
- auto* region = user_region_from_vaddr(const_cast<Process&>(process), vaddr);
- return region && region->is_user_accessible() && region->is_readable();
+ return validate_range<AccessSpace::User, AccessType::Read>(process, vaddr, size);
}
-bool MemoryManager::validate_user_write(const Process& process, VirtualAddress vaddr) const
+bool MemoryManager::validate_user_write(const Process& process, VirtualAddress vaddr, size_t size) const
{
if (!is_user_address(vaddr))
return false;
- auto* region = user_region_from_vaddr(const_cast<Process&>(process), vaddr);
- return region && region->is_user_accessible() && region->is_writable();
+ return validate_range<AccessSpace::User, AccessType::Write>(process, vaddr, size);
}
void MemoryManager::register_vmobject(VMObject& vmobject)
diff --git a/Kernel/VM/MemoryManager.h b/Kernel/VM/MemoryManager.h
index 967e681a2c..517106cad9 100644
--- a/Kernel/VM/MemoryManager.h
+++ b/Kernel/VM/MemoryManager.h
@@ -45,8 +45,10 @@ public:
void enter_process_paging_scope(Process&);
bool validate_user_stack(const Process&, VirtualAddress) const;
- bool validate_user_read(const Process&, VirtualAddress) const;
- bool validate_user_write(const Process&, VirtualAddress) const;
+ bool validate_user_read(const Process&, VirtualAddress, size_t) const;
+ bool validate_user_write(const Process&, VirtualAddress, size_t) const;
+
+ bool validate_kernel_read(const Process&, VirtualAddress, size_t) const;
enum class ShouldZeroFill {
No,
@@ -85,6 +87,11 @@ private:
MemoryManager(u32 physical_address_for_kernel_page_tables);
~MemoryManager();
+ enum class AccessSpace { Kernel, User };
+ enum class AccessType { Read, Write };
+ template<AccessSpace, AccessType>
+ bool validate_range(const Process&, VirtualAddress, size_t) const;
+
void register_vmobject(VMObject&);
void unregister_vmobject(VMObject&);
void register_region(Region&);