4

I was thinking of trying my hand at some jit compilataion (just for the sake of learning) and it would be nice to have it work cross platform since I run all the major three at home (windows, os x, linux). With that in mind, I want to know if there is any way to get out of using the virtual memory windows functions to allocate memory with execution permissions. Would be nice to just use malloc or new and point the processor at such a block.

Any tips?

mac
  • 113
  • 1
  • 5
  • What is so wrong with VirtualProtectEx? "I want to do what VirtualProtectEx does, but I don't want to use VirtualProtectEx?" Huh? – Craig Stuntz Feb 20 '09 at 18:25
  • If you read my question carefully you will see that I would like to have the code cross platform. That means I don't want to use anything from the windows API if I can help it. The only platform dependent thing should preferably be the x86 instruction set. – mac Feb 20 '09 at 19:34
  • 1
    That just isn't going to happen. Compilers (as opposed to interpreters) are platform-specific. Even within x86 you have to deal with things like position-independent code in Linux. Not to mention execute permissions, which are hardware-assisted, but live in the OS. – Craig Stuntz Feb 20 '09 at 19:43
  • I would add that something akin to dependency injection is probably a good way to isolate the platform-specific code which you will need to use. – Craig Stuntz Feb 20 '09 at 19:46
  • I didn't expect anyone to have an answer for me regarding the execution protection actually but I thought it wouldn't hurt to ask just in case. – mac Feb 21 '09 at 19:45
  • What do you mean with position independent code? Are you referring to the OS having to change addresses in object code when loading an executable at a non-preferred address? That would not apply when JIT compiling so it's not an issue. – mac Feb 21 '09 at 19:46

2 Answers2

12

DEP is just turning off Execution permission from every non-code page of memory. The code of application is loaded to memory which has execution permission; and there are lot of JITs which works in Windows/Linux/MacOSX, even when DEP is active. This is because there is a way to dynamically allocate memory with needed permissions set.

Usually, plain malloc should not be used, because permissions are per-page. Aligning of malloced memory to pages is still possible at price of some overhead. If you will not use malloc, some custom memory management (only for executable code). Custom management is a common way of doing JIT.

There is a solution from Chromium project, which uses JIT for javascript V8 VM and which is cross-platform. To be cross-platform, the needed function is implemented in several files and they are selected at compile time.

Linux: (chromium src/v8/src/platform-linux.cc) flag is PROT_EXEC of mmap().

void* OS::Allocate(const size_t requested,
                   size_t* allocated,
                   bool is_executable) {
  const size_t msize = RoundUp(requested, AllocateAlignment());
  int prot = PROT_READ | PROT_WRITE | (is_executable ? PROT_EXEC : 0);
  void* addr = OS::GetRandomMmapAddr();
  void* mbase = mmap(addr, msize, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (mbase == MAP_FAILED) {
    /** handle error */
    return NULL;
  }
  *allocated = msize;
  UpdateAllocatedSpaceLimits(mbase, msize);
  return mbase;
}

Win32 (src/v8/src/platform-win32.cc): flag is PAGE_EXECUTE_READWRITE of VirtualAlloc

void* OS::Allocate(const size_t requested,
                   size_t* allocated,
                   bool is_executable) {
  // The address range used to randomize RWX allocations in OS::Allocate
  // Try not to map pages into the default range that windows loads DLLs
  // Use a multiple of 64k to prevent committing unused memory.
  // Note: This does not guarantee RWX regions will be within the
  // range kAllocationRandomAddressMin to kAllocationRandomAddressMax
#ifdef V8_HOST_ARCH_64_BIT
  static const intptr_t kAllocationRandomAddressMin = 0x0000000080000000;
  static const intptr_t kAllocationRandomAddressMax = 0x000003FFFFFF0000;
#else
  static const intptr_t kAllocationRandomAddressMin = 0x04000000;
  static const intptr_t kAllocationRandomAddressMax = 0x3FFF0000;
#endif

  // VirtualAlloc rounds allocated size to page size automatically.
  size_t msize = RoundUp(requested, static_cast<int>(GetPageSize()));
  intptr_t address = 0;

  // Windows XP SP2 allows Data Excution Prevention (DEP).
  int prot = is_executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;

  // For exectutable pages try and randomize the allocation address
  if (prot == PAGE_EXECUTE_READWRITE &&
      msize >= static_cast<size_t>(Page::kPageSize)) {
    address = (V8::RandomPrivate(Isolate::Current()) << kPageSizeBits)
      | kAllocationRandomAddressMin;
    address &= kAllocationRandomAddressMax;
  }

  LPVOID mbase = VirtualAlloc(reinterpret_cast<void *>(address),
                              msize,
                              MEM_COMMIT | MEM_RESERVE,
                              prot);
  if (mbase == NULL && address != 0)
    mbase = VirtualAlloc(NULL, msize, MEM_COMMIT | MEM_RESERVE, prot);

  if (mbase == NULL) {
    LOG(ISOLATE, StringEvent("OS::Allocate", "VirtualAlloc failed"));
    return NULL;
  }

  ASSERT(IsAligned(reinterpret_cast<size_t>(mbase), OS::AllocateAlignment()));

  *allocated = msize;
  UpdateAllocatedSpaceLimits(mbase, static_cast<int>(msize));
  return mbase;
}

MacOS (src/v8/src/platform-macos.cc): flag is PROT_EXEC of mmap, just like Linux or other posix.

void* OS::Allocate(const size_t requested,
                   size_t* allocated,
                   bool is_executable) {
  const size_t msize = RoundUp(requested, getpagesize());
  int prot = PROT_READ | PROT_WRITE | (is_executable ? PROT_EXEC : 0);
  void* mbase = mmap(OS::GetRandomMmapAddr(),
                     msize,
                     prot,
                     MAP_PRIVATE | MAP_ANON,
                     kMmapFd,
                     kMmapFdOffset);
  if (mbase == MAP_FAILED) {
    LOG(Isolate::Current(), StringEvent("OS::Allocate", "mmap failed"));
    return NULL;
  }
  *allocated = msize;
  UpdateAllocatedSpaceLimits(mbase, msize);
  return mbase;
}

And I also want note, that bcdedit.exe-like way should be used only for very old programs, which creates new executable code in memory, but not sets an Exec property on this page. For newer programs, like firefox or Chrome/Chromium, or any modern JIT, DEP should be active, and JIT will manage memory permissions in fine-grained manner.

osgx
  • 90,338
  • 53
  • 357
  • 513
  • This is the *right* way of doing it, but in a comment from the author of the question he stated: "I don't want to use anything from the windows API", so the stack-based approach. – Gigi Feb 08 '12 at 02:41
  • Or adding a POSIX layer (SFU or cygwin) to Windows and doing this in POSIX API. – osgx Feb 08 '12 at 03:09
  • @Gigi What is the stack-based approach you mention? – Brent Apr 30 '20 at 18:11
  • 1
    @Brent, Hello, there was another answer to this question by Gigi, deleted 8 years ago (2012) by him. He did recommend to put instructions into stack-allocated space; but stack space is usually also not executable. So he additionally recommends to link with `-z execstack` on linux (http://man7.org/linux/man-pages/man8/execstack.8.html) or depend on Windows's Data Execution Prevention settings. (but I commented that http://technet.microsoft.com/en-us/library/cc738483%28WS.10%29.aspx says: "On 32-bit versions of Windows, DEP is applied to the stack by default."). – osgx May 06 '20 at 00:11
0

One possibility is to make it a requirement that Windows installations running your program be either configured for DEP AlwaysOff (bad idea) or DEP OptOut (better idea).

This can be configured (under WinXp SP2+ and Win2k3 SP1+ at least) by changing the boot.ini file to have the setting:

/noexecute=OptOut

and then configuring your individual program to opt out by choosing (under XP):

Start button
    Control Panel
        System
            Advanced tab
                Performance Settings button
                    Data Execution Prevention tab

This should allow you to execute code from within your program that's created on the fly in malloc() blocks.

Keep in mind that this makes your program more susceptible to attacks that DEP was meant to prevent.

It looks like this is also possible in Windows 2008 with the command:

bcdedit.exe /set {current} nx OptOut

But, to be honest, if you just want to minimise platform-dependent code, that's easy to do just by isolating the code into a single function, something like:

void *MallocWithoutDep(size_t sz) {
    #if defined _IS_WINDOWS
        return VirtualMalloc(sz, OPT_DEP_OFF); // or whatever
    #elif defined IS_LINUX
        // Do linuxy thing
    #elif defined IS_MACOS
        // Do something almost certainly inexplicable
    #endif
}

If you put all your platform dependent functions in their own files, the rest of your code is automatically platform-agnostic.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Please, say: how JITs in JavaVM, in Firefox, in Chrome work on every computer, even when DEP is enabled? – osgx Feb 08 '12 at 02:04
  • 2
    @osgx, _why_ would I do that when you've posted an answer that covers that in _more_ than enough detail? However, I'd just like to mention that the question specifically requested the use of _standard, non-platform-specific_ functions with "I want to know if there is any way to get out of using the virtual memory windows functions to allocate memory with execution permissions. Would be nice to just use malloc or new and point the processor at such a block". – paxdiablo Feb 08 '12 at 03:35