[fuchsia] Always allocate executable memory in W^X mode

In W^X mode (i.e., FLAG_write_protect_code), the Dart VM allocates
code memory with is_executable==false and then later makes it
executable with Protect(kReadExecute). However, on Fuchsia, this will
require a VMO with ZX_RIGHT_EXECUTE (which soon will not be the
default).

This CL takes the approach of always adding ZX_RIGHT_EXECUTE when in
W^X mode. This is suboptimal because it means all VM allocated memory
can be changed to executable, but currently the OS-independent
VirtualMemory::Allocate API doesn't provide us a way to identify
memory that doesn't need to be executable now but will in the future.
(Note that ZX_RIGHT_EXECUTE only *allows* mapping the memory as
executable; the memory is still initially mapped by
VirtualMemory::Allocate as non-executable.)

Extending the VirtualMemory::Allocate API is tracked in FL-172.

Bug: SEC-42
Change-Id: I20555c546c5ab1798c4c36b7394f6221d3993c9e
Reviewed-on: https://dart-review.googlesource.com/c/90882
Reviewed-by: Zach Anderson <zra@google.com>
Commit-Queue: Zach Anderson <zra@google.com>
diff --git a/runtime/vm/virtual_memory_fuchsia.cc b/runtime/vm/virtual_memory_fuchsia.cc
index bcbf3b0..217bfc0 100644
--- a/runtime/vm/virtual_memory_fuchsia.cc
+++ b/runtime/vm/virtual_memory_fuchsia.cc
@@ -35,6 +35,8 @@
 
 namespace dart {
 
+DECLARE_FLAG(bool, write_protect_code);
+
 uword VirtualMemory::page_size_ = 0;
 
 void VirtualMemory::Init() {
@@ -44,6 +46,14 @@
 VirtualMemory* VirtualMemory::Allocate(intptr_t size,
                                        bool is_executable,
                                        const char* name) {
+  // When FLAG_write_protect_code is active, the VM allocates code
+  // memory with !is_executable, and later changes to executable via
+  // VirtualMemory::Protect, which requires ZX_RIGHT_EXECUTE on the
+  // underlying VMO. Conservatively assume all memory needs to be
+  // executable in this mode.
+  // TODO(mdempsky): Make into parameter.
+  const bool can_prot_exec = FLAG_write_protect_code;
+
   ASSERT(Utils::IsAligned(size, page_size_));
   zx_handle_t vmo = ZX_HANDLE_INVALID;
   zx_status_t status = zx_vmo_create(size, 0u, &vmo);
@@ -57,8 +67,8 @@
     zx_object_set_property(vmo, ZX_PROP_NAME, name, strlen(name));
   }
 
-  if (is_executable) {
-    // Add ZX_PERM_EXECUTE permission to VMO, so it can be mapped
+  if (is_executable || can_prot_exec) {
+    // Add ZX_RIGHT_EXECUTE permission to VMO, so it can be mapped
     // into memory as executable.
     status = zx_vmo_replace_as_executable(vmo, ZX_HANDLE_INVALID, &vmo);
     if (status != ZX_OK) {
@@ -88,6 +98,9 @@
                                               intptr_t alignment,
                                               bool is_executable,
                                               const char* name) {
+  // See explanation in VirtualMemory::Allocate above.
+  const bool can_prot_exec = FLAG_write_protect_code;
+
   ASSERT(Utils::IsAligned(size, page_size_));
   ASSERT(Utils::IsAligned(alignment, page_size_));
   intptr_t allocated_size = size + alignment;
@@ -105,8 +118,8 @@
     zx_object_set_property(vmo, ZX_PROP_NAME, name, strlen(name));
   }
 
-  if (is_executable) {
-    // Add ZX_PERM_EXECUTE permission to VMO, so it can be mapped
+  if (is_executable || can_prot_exec) {
+    // Add ZX_RIGHT_EXECUTE permission to VMO, so it can be mapped
     // into memory as executable.
     status = zx_vmo_replace_as_executable(vmo, ZX_HANDLE_INVALID, &vmo);
     if (status != ZX_OK) {