Jelajahi Sumber

Kernel+Userland: Reject W->X prot region transition after a prctl call

We add a prctl option which would be called once after the dynamic
loader has finished to do text relocations before calling the actual
program entry point.

This change makes it much more obvious when we are allowed to change
a region protection access from being writable to executable.
The dynamic loader should be able to do this, but after a certain point
it is obvious that such mechanism should be disabled.
Liav A. 1 tahun lalu
induk
melakukan
15ddc1f17a

+ 1 - 0
Kernel/API/prctl_numbers.h

@@ -15,3 +15,4 @@
 #define PR_GET_PROCESS_NAME 7
 #define PR_SET_THREAD_NAME 8
 #define PR_GET_THREAD_NAME 9
+#define PR_SET_NO_TRANSITION_TO_EXECUTABLE_FROM_WRITABLE_PROT 10

+ 2 - 54
Kernel/Syscalls/mmap.cpp

@@ -28,55 +28,8 @@
 
 namespace Kernel {
 
-static bool should_make_executable_exception_for_dynamic_loader(bool make_readable, bool make_writable, bool make_executable, Memory::Region const& region)
-{
-    // Normally we don't allow W -> X transitions, but we have to make an exception
-    // for the dynamic loader, which needs to do this after performing text relocations.
-
-    // FIXME: Investigate whether we could get rid of all text relocations entirely.
-
-    // The exception is only made if all the following criteria is fulfilled:
-
-    // The region must be RW
-    if (!(region.is_readable() && region.is_writable() && !region.is_executable()))
-        return false;
-
-    // The region wants to become RX
-    if (!(make_readable && !make_writable && make_executable))
-        return false;
-
-    // The region is backed by a file
-    if (!region.vmobject().is_inode())
-        return false;
-
-    // The file mapping is private, not shared (no relocations in a shared mapping!)
-    if (!region.vmobject().is_private_inode())
-        return false;
-
-    auto const& inode_vm = static_cast<Memory::InodeVMObject const&>(region.vmobject());
-    auto const& inode = inode_vm.inode();
-
-    Elf_Ehdr header;
-    auto buffer = UserOrKernelBuffer::for_kernel_buffer((u8*)&header);
-    auto result = inode.read_bytes(0, sizeof(header), buffer, nullptr);
-    if (result.is_error() || result.value() != sizeof(header))
-        return false;
-
-    // The file is a valid ELF binary
-    if (!ELF::validate_elf_header(header, inode.size()))
-        return false;
-
-    // The file is an ELF shared object
-    if (header.e_type != ET_DYN)
-        return false;
-
-    // FIXME: Are there any additional checks/validations we could do here?
-    return true;
-}
-
 ErrorOr<void> Process::validate_mmap_prot(int prot, bool map_stack, bool map_anonymous, Memory::Region const* region) const
 {
-    bool make_readable = prot & PROT_READ;
     bool make_writable = prot & PROT_WRITE;
     bool make_executable = prot & PROT_EXEC;
 
@@ -96,13 +49,8 @@ ErrorOr<void> Process::validate_mmap_prot(int prot, bool map_stack, bool map_ano
         if (make_writable && region->has_been_executable())
             return EINVAL;
 
-        if (make_executable && region->has_been_writable()) {
-            if (should_make_executable_exception_for_dynamic_loader(make_readable, make_writable, make_executable, *region)) {
-                return {};
-            } else {
-                return EINVAL;
-            };
-        }
+        if (make_executable && region->has_been_writable() && should_reject_transition_to_executable_from_writable_prot())
+            return EINVAL;
     }
 
     return {};

+ 7 - 0
Kernel/Syscalls/prctl.cpp

@@ -92,6 +92,13 @@ ErrorOr<FlatPtr> Process::sys$prctl(int option, FlatPtr arg1, FlatPtr arg2, Flat
             }));
             return 0;
         }
+        case PR_SET_NO_TRANSITION_TO_EXECUTABLE_FROM_WRITABLE_PROT: {
+            TRY(require_promise(Pledge::prot_exec));
+            with_mutable_protected_data([](auto& protected_data) {
+                return protected_data.reject_transition_to_executable_from_writable_prot.set();
+            });
+            return 0;
+        }
         }
 
         return EINVAL;

+ 8 - 0
Kernel/Tasks/Process.h

@@ -143,6 +143,7 @@ class Process final
         Atomic<u32> thread_count { 0 };
         u8 termination_status { 0 };
         u8 termination_signal { 0 };
+        SetOnce reject_transition_to_executable_from_writable_prot;
     };
 
 public:
@@ -624,6 +625,13 @@ public:
     ErrorOr<void> require_promise(Pledge);
     ErrorOr<void> require_no_promises() const;
 
+    bool should_reject_transition_to_executable_from_writable_prot() const
+    {
+        return with_protected_data([](auto& protected_data) {
+            return protected_data.reject_transition_to_executable_from_writable_prot.was_set();
+        });
+    }
+
     ErrorOr<void> validate_mmap_prot(int prot, bool map_stack, bool map_anonymous, Memory::Region const* region = nullptr) const;
     ErrorOr<void> validate_inode_mmap_prot(int prot, bool description_readable, bool description_writable, bool map_shared) const;
 

+ 5 - 0
Userland/Libraries/LibELF/DynamicLinker.cpp

@@ -744,6 +744,11 @@ Examples of static-pie ELF objects are ELF packers, and the system dynamic loade
         VERIFY_NOT_REACHED();
     }
 
+    rc = syscall(SC_prctl, PR_SET_NO_TRANSITION_TO_EXECUTABLE_FROM_WRITABLE_PROT, 0, 0, nullptr);
+    if (rc < 0) {
+        VERIFY_NOT_REACHED();
+    }
+
     dbgln_if(DYNAMIC_LOAD_DEBUG, "Jumping to entry point: {:p}", entry_point_function);
     if (s_do_breakpoint_trap_before_entry) {
 #if ARCH(AARCH64)