orccodemem: Add support for Universal Windows Platform apps
authorNirbheek Chauhan <nirbheek@centricular.com>
Tue, 30 Jun 2020 10:46:39 +0000 (16:16 +0530)
committerNirbheek Chauhan <nirbheek@centricular.com>
Wed, 1 Jul 2020 01:56:16 +0000 (07:26 +0530)
VirtualAlloc is VirtualAllocFromApp when targeting UWP, and you can
only allocate executable pages if you have the codeGeneration
capability set in the app manifest. Check for that capability in
_orc_compiler_init() and switch to backup code or emulation if it
isn't available instead of crashing when VirtualAllocFromApp
returns NULL.

Also you cannot allocate pages that are both READWRITE and EXECUTE, so
we allocate as read-write first, then set the memory as execute-only
after the code has been compiled and copied over.

Part-of: <https://gitlab.freedesktop.org/gstreamer/orc/-/merge_requests/42>

meson.build
orc/orccodemem.c
orc/orccompiler.c

index c4d955b..eeb3f17 100644 (file)
@@ -94,6 +94,16 @@ if host_os == 'windows'
   cdata.set('HAVE_CODEMEM_VIRTUALALLOC', true)
   cdata.set('HAVE_OS_WIN32', true)
   cdata.set('HAVE_THREAD_WIN32', true)
+
+  code = '''
+  #include <windows.h>
+  #if !(WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP))
+  #error "Not building for UWP"
+  #endif'''
+  if cc.compiles(code, name : 'building for UWP')
+    cdata.set('ORC_WINAPI_ONLY_APP', true)
+  endif
+
   pc_conf.set('EXEEXT', '.exe')
   pc_conf.set('PTHREAD_LIBS', '')
 else
index 14aadbd..0081835 100644 (file)
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
+
 #ifdef HAVE_CODEMEM_MMAP
 #include <sys/mman.h>
 #endif
+
 #ifdef HAVE_CODEMEM_VIRTUALALLOC
 #include <windows.h>
+  #ifdef ORC_WINAPI_ONLY_APP
+    #define _virtualalloc VirtualAllocFromApp
+  #else
+    #define _virtualalloc VirtualAlloc
+  #endif
 #endif
 
 #include <orc/orcinternal.h>
@@ -299,7 +306,10 @@ orc_code_region_allocate_codemem (OrcCodeRegion *region)
 void
 orc_code_region_allocate_codemem (OrcCodeRegion *region)
 {
-  region->write_ptr = VirtualAlloc(NULL, SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
+  /* On UWP, we can't allocate memory as executable from the start. We can only
+   * set that later after compiling and copying the code over. This is a good
+   * idea in general to avoid security issues, so we do it on win32 too. */
+  region->write_ptr = _virtualalloc (NULL, SIZE, MEM_COMMIT, PAGE_READWRITE);
   region->exec_ptr = region->write_ptr;
   region->size = SIZE;
 }
index de2555d..600e66b 100644 (file)
@@ -6,6 +6,15 @@
 #include <stdlib.h>
 #include <stdarg.h>
 
+#if defined(HAVE_CODEMEM_VIRTUALALLOC)
+#include <windows.h>
+  #ifdef ORC_WINAPI_ONLY_APP
+    #define _virtualprotect VirtualProtectFromApp
+  #else
+    #define _virtualprotect VirtualProtect
+  #endif
+#endif
+
 #include <orc/orcprogram.h>
 #include <orc/orcdebug.h>
 #include <orc/orcinternal.h>
@@ -63,6 +72,38 @@ _orc_compiler_init (void)
   _orc_compiler_flag_emulate = orc_compiler_flag_check ("emulate");
   _orc_compiler_flag_debug = orc_compiler_flag_check ("debug");
   _orc_compiler_flag_randomize = orc_compiler_flag_check ("randomize");
+
+#if defined(HAVE_CODEMEM_VIRTUALALLOC) && defined(ORC_WINAPI_ONLY_APP)
+  if (!_orc_compiler_flag_backup && !_orc_compiler_flag_emulate) {
+    int can_jit = FALSE;
+    size_t page_size;
+    SYSTEM_INFO info;
+    GetNativeSystemInfo(&info);
+
+    page_size = info.dwPageSize;
+    /* If backup code is not enabled and emulation is not enabled, that means
+     * we will do JIT compilation and call orc_code_region_allocate_codemem().
+     * When targeting Windows Store apps, the codeGeneration capability must
+     * be enabled in the app manifest, or passing PAGE_EXECUTE to
+     * VirtualProtectFromApp will return NULL. In this case, we must force
+     * backup C code, and if that's not available, we must emulate. */
+    void *mem = VirtualAllocFromApp (NULL, page_size, MEM_COMMIT,
+        PAGE_READWRITE);
+    if (mem) {
+      int old_protect;
+      if (VirtualProtectFromApp (mem, page_size, PAGE_EXECUTE, &old_protect) > 0)
+        can_jit = TRUE;
+      VirtualFree (mem, 0, MEM_RELEASE);
+    }
+
+    if (!can_jit) {
+      ORC_WARNING ("Unable to allocate executable pages: using backup code or "
+        "emulation: codeGeneration capability isn't set in the app manifest?");
+      _orc_compiler_flag_backup = TRUE;
+      _orc_compiler_flag_emulate = TRUE;
+    }
+  }
+#endif
 }
 
 int
@@ -175,6 +216,44 @@ orc_program_compile_for_target (OrcProgram *program, OrcTarget *target)
   return orc_program_compile_full (program, target, flags);
 }
 
+#if defined(HAVE_CODEMEM_VIRTUALALLOC)
+static const char*
+_get_protect_name (int protect)
+{
+  switch (protect) {
+    /* These are the only two memory protection constants we use */
+    case PAGE_EXECUTE:
+      return "execute";
+    case PAGE_READWRITE:
+      return "readwrite";
+    default:
+      return "unknown";
+  }
+}
+
+static orc_bool
+_set_virtual_protect (void * mem, size_t size, int code_protect)
+{
+    char *msg;
+    DWORD old_protect;
+
+    if (!mem)
+      return FALSE;
+
+    if (_virtualprotect (mem, size, code_protect, &old_protect) > 0)
+      return TRUE;
+
+    FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER
+        | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, NULL,
+        GetLastError (), 0, (LPTSTR) &msg, 0, NULL);
+    ORC_ERROR ("Couldn't set memory protect on %p from %s to %s: %s", mem,
+        _get_protect_name (old_protect), _get_protect_name (code_protect), msg);
+    LocalFree (msg);
+
+    return FALSE;
+}
+#endif
+
 /**
  * orc_program_compile_full:
  * @program: the OrcProgram to compile
@@ -360,6 +439,11 @@ orc_program_compile_full (OrcProgram *program, OrcTarget *target,
   program->orccode->code_size = compiler->codeptr - compiler->code;
   orc_code_allocate_codemem (program->orccode, program->orccode->code_size);
 
+#if defined(HAVE_CODEMEM_VIRTUALALLOC)
+  /* Ensure that code region is writable before memcpy */
+  _set_virtual_protect (program->orccode->code, program->orccode->code_size,
+      PAGE_READWRITE);
+#endif
   memcpy (program->orccode->code, compiler->code, program->orccode->code_size);
 
 #ifdef VALGRIND_DISCARD_TRANSLATIONS
@@ -371,6 +455,13 @@ orc_program_compile_full (OrcProgram *program, OrcTarget *target,
     compiler->target->flush_cache (program->orccode);
   }
 
+#if defined(HAVE_CODEMEM_VIRTUALALLOC)
+  /* Code region is now ready for execution */
+ if (!_set_virtual_protect (program->orccode->exec, program->orccode->code_size,
+       PAGE_EXECUTE))
+   /* Can't set code as executable, force emulation */
+   program->orccode->exec = (void *)orc_executor_emulate;
+#endif
   program->code_exec = program->orccode->exec;
 
   program->asm_code = compiler->asm_code;