summaryrefslogtreecommitdiff
path: root/Userland/test_efault.cpp
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 /Userland/test_efault.cpp
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 'Userland/test_efault.cpp')
-rw-r--r--Userland/test_efault.cpp69
1 files changed, 69 insertions, 0 deletions
diff --git a/Userland/test_efault.cpp b/Userland/test_efault.cpp
new file mode 100644
index 0000000000..ad5f004074
--- /dev/null
+++ b/Userland/test_efault.cpp
@@ -0,0 +1,69 @@
+#include <AK/Assertions.h>
+#include <AK/Types.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#define EXPECT_OK(syscall, address, size) \
+ do { \
+ rc = syscall(fd, (void*)(address), (size_t)(size)); \
+ if (rc < 0) { \
+ fprintf(stderr, "Expected success: " #syscall "(%p, %zu), got rc=%d, errno=%d\n", (void*)(address), (size_t)(size), rc, errno); \
+ } \
+ } while(0)
+
+#define EXPECT_EFAULT(syscall, address, size) \
+ do { \
+ rc = syscall(fd, (void*)(address), (size_t)(size)); \
+ if (rc >= 0 || errno != EFAULT) { \
+ fprintf(stderr, "Expected EFAULT: " #syscall "(%p, %zu), got rc=%d, errno=%d\n", (void*)(address), (size_t)(size), rc, errno); \
+ } \
+ } while(0)
+
+
+int main(int, char**)
+{
+ int fd = open("/dev/zero", O_RDONLY);
+ int rc;
+
+ // Test a one-page mapping (4KB)
+ u8* one_page = (u8*)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
+ ASSERT(one_page);
+ EXPECT_OK(read, one_page, 4096);
+ EXPECT_EFAULT(read, one_page, 4097);
+ EXPECT_EFAULT(read, one_page - 1, 4096);
+
+ // Test a two-page mapping (8KB)
+ u8* two_page = (u8*)mmap(nullptr, 8192, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
+ ASSERT(two_page);
+
+ EXPECT_OK(read, two_page, 4096);
+ EXPECT_OK(read, two_page + 4096, 4096);
+ EXPECT_OK(read, two_page, 8192);
+ EXPECT_OK(read, two_page + 4095, 4097);
+ EXPECT_OK(read, two_page + 1, 8191);
+ EXPECT_EFAULT(read, two_page, 8193);
+ EXPECT_EFAULT(read, two_page - 1, 1);
+
+ // Check validation of pages between the first and last address.
+ ptrdiff_t distance = two_page - one_page;
+ EXPECT_EFAULT(read, one_page, (u32)distance + 1024);
+
+ // Test every kernel page just because.
+ for (u64 kernel_address = 0xc0000000; kernel_address <= 0xffffffff; kernel_address += PAGE_SIZE) {
+ EXPECT_EFAULT(read, (void*)kernel_address, 1);
+ }
+
+ // Test the page just below where the kernel VM begins.
+ u8* jerk_page = (u8*)mmap((void*)(0xc0000000 - PAGE_SIZE), PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, 0, 0);
+ ASSERT(jerk_page == (void*)(0xc0000000 - PAGE_SIZE));
+
+ EXPECT_OK(read, jerk_page, 4096);
+ EXPECT_EFAULT(read, jerk_page, 4097);
+
+ // Test something that would wrap around the 2^32 mark.
+ EXPECT_EFAULT(read, jerk_page, 0x50000000);
+
+ return 0;
+}