From 67010ab5546abe8a2793992e4322ddfea021538e Mon Sep 17 00:00:00 2001 From: tevador <37503146+tevador@users.noreply.github.com> Date: Sun, 25 Aug 2019 13:47:40 +0200 Subject: [PATCH] Optional W^X policy for JIT pages (#112) --- src/dataset.cpp | 2 ++ src/jit_compiler_x86.cpp | 14 +++++++++- src/jit_compiler_x86.hpp | 3 +++ src/randomx.cpp | 56 +++++++++++++++++++++++++++++++++------ src/randomx.h | 7 +++-- src/tests/benchmark.cpp | 11 ++++++-- src/virtual_memory.cpp | 38 ++++++++++++++++++++++---- src/virtual_memory.hpp | 5 +++- src/vm_compiled.cpp | 37 +++++++++++++++++++------- src/vm_compiled.hpp | 15 +++++++---- src/vm_compiled_light.cpp | 34 +++++++++++++++++------- src/vm_compiled_light.hpp | 28 +++++++++++--------- 12 files changed, 195 insertions(+), 55 deletions(-) diff --git a/src/dataset.cpp b/src/dataset.cpp index bc0d0d1..59aef62 100644 --- a/src/dataset.cpp +++ b/src/dataset.cpp @@ -140,8 +140,10 @@ namespace randomx { void initCacheCompile(randomx_cache* cache, const void* key, size_t keySize) { initCache(cache, key, keySize); + cache->jit->enableWriting(); cache->jit->generateSuperscalarHash(cache->programs, cache->reciprocalCache); cache->jit->generateDatasetInitCode(); + cache->jit->enableExecution(); } constexpr uint64_t superscalarMul0 = 6364136223846793005ULL; diff --git a/src/jit_compiler_x86.cpp b/src/jit_compiler_x86.cpp index 81d8cda..aa51d81 100644 --- a/src/jit_compiler_x86.cpp +++ b/src/jit_compiler_x86.cpp @@ -218,7 +218,7 @@ namespace randomx { } JitCompilerX86::JitCompilerX86() { - code = (uint8_t*)allocExecutableMemory(CodeSize); + code = (uint8_t*)allocMemoryPages(CodeSize); memcpy(code, codePrologue, prologueSize); memcpy(code + epilogueOffset, codeEpilogue, epilogueSize); } @@ -227,6 +227,18 @@ namespace randomx { freePagedMemory(code, CodeSize); } + void JitCompilerX86::enableAll() { + setPagesRWX(code, CodeSize); + } + + void JitCompilerX86::enableWriting() { + setPagesRW(code, CodeSize); + } + + void JitCompilerX86::enableExecution() { + setPagesRX(code, CodeSize); + } + void JitCompilerX86::generateProgram(Program& prog, ProgramConfiguration& pcfg) { generateProgramPrologue(prog, pcfg); memcpy(code + codePos, codeReadDataset, readDatasetSize); diff --git a/src/jit_compiler_x86.hpp b/src/jit_compiler_x86.hpp index 47d49a2..b4f1dfa 100644 --- a/src/jit_compiler_x86.hpp +++ b/src/jit_compiler_x86.hpp @@ -62,6 +62,9 @@ namespace randomx { return code; } size_t getCodeSize(); + void enableWriting(); + void enableExecution(); + void enableAll(); private: static InstructionGeneratorX86 engine[256]; std::vector instructionOffsets; diff --git a/src/randomx.cpp b/src/randomx.cpp index 95160ca..8b32894 100644 --- a/src/randomx.cpp +++ b/src/randomx.cpp @@ -175,11 +175,21 @@ extern "C" { break; case RANDOMX_FLAG_JIT: - vm = new randomx::CompiledLightVmDefault(); + if (flags & RANDOMX_FLAG_SECURE) { + vm = new randomx::CompiledLightVmDefaultSecure(); + } + else { + vm = new randomx::CompiledLightVmDefault(); + } break; case RANDOMX_FLAG_FULL_MEM | RANDOMX_FLAG_JIT: - vm = new randomx::CompiledVmDefault(); + if (flags & RANDOMX_FLAG_SECURE) { + vm = new randomx::CompiledVmDefaultSecure(); + } + else { + vm = new randomx::CompiledVmDefault(); + } break; case RANDOMX_FLAG_HARD_AES: @@ -191,11 +201,21 @@ extern "C" { break; case RANDOMX_FLAG_JIT | RANDOMX_FLAG_HARD_AES: - vm = new randomx::CompiledLightVmHardAes(); + if (flags & RANDOMX_FLAG_SECURE) { + vm = new randomx::CompiledLightVmHardAesSecure(); + } + else { + vm = new randomx::CompiledLightVmHardAes(); + } break; case RANDOMX_FLAG_FULL_MEM | RANDOMX_FLAG_JIT | RANDOMX_FLAG_HARD_AES: - vm = new randomx::CompiledVmHardAes(); + if (flags & RANDOMX_FLAG_SECURE) { + vm = new randomx::CompiledVmHardAesSecure(); + } + else { + vm = new randomx::CompiledVmHardAes(); + } break; case RANDOMX_FLAG_LARGE_PAGES: @@ -207,11 +227,21 @@ extern "C" { break; case RANDOMX_FLAG_JIT | RANDOMX_FLAG_LARGE_PAGES: - vm = new randomx::CompiledLightVmLargePage(); + if (flags & RANDOMX_FLAG_SECURE) { + vm = new randomx::CompiledLightVmLargePageSecure(); + } + else { + vm = new randomx::CompiledLightVmLargePage(); + } break; case RANDOMX_FLAG_FULL_MEM | RANDOMX_FLAG_JIT | RANDOMX_FLAG_LARGE_PAGES: - vm = new randomx::CompiledVmLargePage(); + if (flags & RANDOMX_FLAG_SECURE) { + vm = new randomx::CompiledVmLargePageSecure(); + } + else { + vm = new randomx::CompiledVmLargePage(); + } break; case RANDOMX_FLAG_HARD_AES | RANDOMX_FLAG_LARGE_PAGES: @@ -223,11 +253,21 @@ extern "C" { break; case RANDOMX_FLAG_JIT | RANDOMX_FLAG_HARD_AES | RANDOMX_FLAG_LARGE_PAGES: - vm = new randomx::CompiledLightVmLargePageHardAes(); + if (flags & RANDOMX_FLAG_SECURE) { + vm = new randomx::CompiledLightVmLargePageHardAesSecure(); + } + else { + vm = new randomx::CompiledLightVmLargePageHardAes(); + } break; case RANDOMX_FLAG_FULL_MEM | RANDOMX_FLAG_JIT | RANDOMX_FLAG_HARD_AES | RANDOMX_FLAG_LARGE_PAGES: - vm = new randomx::CompiledVmLargePageHardAes(); + if (flags & RANDOMX_FLAG_SECURE) { + vm = new randomx::CompiledVmLargePageHardAesSecure(); + } + else { + vm = new randomx::CompiledVmLargePageHardAes(); + } break; default: diff --git a/src/randomx.h b/src/randomx.h index 8f9b30c..7a5025d 100644 --- a/src/randomx.h +++ b/src/randomx.h @@ -44,6 +44,7 @@ typedef enum { RANDOMX_FLAG_HARD_AES = 2, RANDOMX_FLAG_FULL_MEM = 4, RANDOMX_FLAG_JIT = 8, + RANDOMX_FLAG_SECURE = 16 } randomx_flags; typedef struct randomx_dataset randomx_dataset; @@ -135,12 +136,14 @@ RANDOMX_EXPORT void randomx_release_dataset(randomx_dataset *dataset); /** * Creates and initializes a RandomX virtual machine. * - * @param flags is any combination of these 4 flags (each flag can be set or not set): + * @param flags is any combination of these 5 flags (each flag can be set or not set): * RANDOMX_FLAG_LARGE_PAGES - allocate scratchpad memory in large pages * RANDOMX_FLAG_HARD_AES - virtual machine will use hardware accelerated AES * RANDOMX_FLAG_FULL_MEM - virtual machine will use the full dataset * RANDOMX_FLAG_JIT - virtual machine will use a JIT compiler - * The numeric values of the flags are ordered so that a higher value will provide + * RANDOMX_FLAG_SECURE - when combined with RANDOMX_FLAG_JIT, the JIT pages are never + * writable and executable at the same time (W^X policy) + * The numeric values of the first 4 flags are ordered so that a higher value will provide * faster hash calculation and a lower numeric value will provide higher portability. * Using RANDOMX_FLAG_DEFAULT (all flags not set) works on all platforms, but is the slowest. * @param cache is a pointer to an initialized randomx_cache structure. Can be diff --git a/src/tests/benchmark.cpp b/src/tests/benchmark.cpp index 6d95a50..850a884 100644 --- a/src/tests/benchmark.cpp +++ b/src/tests/benchmark.cpp @@ -82,6 +82,7 @@ void printUsage(const char* executable) { std::cout << " --mine mining mode: 2080 MiB" << std::endl; std::cout << " --verify verification mode: 256 MiB" << std::endl; std::cout << " --jit x86-64 JIT compiled mode (default: interpreter)" << std::endl; + std::cout << " --secure W^X policy for JIT pages (default: off)" << std::endl; std::cout << " --largePages use large pages" << std::endl; std::cout << " --softAes use software AES (default: x86 AES-NI)" << std::endl; std::cout << " --threads T use T threads (default: 1)" << std::endl; @@ -126,7 +127,7 @@ void mine(randomx_vm* vm, std::atomic& atomicNonce, AtomicHash& result } int main(int argc, char** argv) { - bool softAes, miningMode, verificationMode, help, largePages, jit; + bool softAes, miningMode, verificationMode, help, largePages, jit, secure; int noncesCount, threadCount, initThreadCount; uint64_t threadAffinity; int32_t seedValue; @@ -143,6 +144,7 @@ int main(int argc, char** argv) { readOption("--largePages", argc, argv, largePages); readOption("--jit", argc, argv, jit); readOption("--help", argc, argv, help); + readOption("--secure", argc, argv, secure); store32(&seed, seedValue); @@ -171,7 +173,12 @@ int main(int argc, char** argv) { if (jit) { flags = (randomx_flags)(flags | RANDOMX_FLAG_JIT); - std::cout << " - JIT compiled mode" << std::endl; + std::cout << " - JIT compiled mode "; + if (secure) { + flags = (randomx_flags)(flags | RANDOMX_FLAG_SECURE); + std::cout << "(secure)"; + } + std::cout << std::endl; } else { std::cout << " - interpreted mode" << std::endl; diff --git a/src/virtual_memory.cpp b/src/virtual_memory.cpp index 3ddacdf..375ea14 100644 --- a/src/virtual_memory.cpp +++ b/src/virtual_memory.cpp @@ -41,6 +41,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef MAP_ANONYMOUS #define MAP_ANONYMOUS MAP_ANON #endif +#define PAGE_READONLY PROT_READ +#define PAGE_READWRITE (PROT_READ | PROT_WRITE) +#define PAGE_EXECUTE_READ (PROT_READ | PROT_EXEC) +#define PAGE_EXECUTE_READWRITE (PROT_READ | PROT_WRITE | PROT_EXEC) #endif #if defined(_WIN32) || defined(__CYGWIN__) @@ -83,20 +87,44 @@ void setPrivilege(const char* pszPrivilege, BOOL bEnable) { } #endif -void* allocExecutableMemory(std::size_t bytes) { +void* allocMemoryPages(std::size_t bytes) { void* mem; #if defined(_WIN32) || defined(__CYGWIN__) - mem = VirtualAlloc(nullptr, bytes, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + mem = VirtualAlloc(nullptr, bytes, MEM_COMMIT, PAGE_READWRITE); if (mem == nullptr) - throw std::runtime_error(getErrorMessage("allocExecutableMemory - VirtualAlloc")); + throw std::runtime_error(getErrorMessage("allocMemoryPages - VirtualAlloc")); #else - mem = mmap(nullptr, bytes, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + mem = mmap(nullptr, bytes, PAGE_READWRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (mem == MAP_FAILED) - throw std::runtime_error("allocExecutableMemory - mmap failed"); + throw std::runtime_error("allocMemoryPages - mmap failed"); #endif return mem; } +static inline void pageProtect(void* ptr, std::size_t bytes, int rules) { +#if defined(_WIN32) || defined(__CYGWIN__) + DWORD oldp; + if (!VirtualProtect(ptr, bytes, (DWORD)rules, &oldp)) { + throw std::runtime_error(getErrorMessage("VirtualProtect")); + } +#else + if (-1 == mprotect(ptr, bytes, rules)) + throw std::runtime_error("mprotect failed"); +#endif +} + +void setPagesRW(void* ptr, std::size_t bytes) { + pageProtect(ptr, bytes, PAGE_READWRITE); +} + +void setPagesRX(void* ptr, std::size_t bytes) { + pageProtect(ptr, bytes, PAGE_EXECUTE_READ); +} + +void setPagesRWX(void* ptr, std::size_t bytes) { + pageProtect(ptr, bytes, PAGE_EXECUTE_READWRITE); +} + void* allocLargePagesMemory(std::size_t bytes) { void* mem; #if defined(_WIN32) || defined(__CYGWIN__) diff --git a/src/virtual_memory.hpp b/src/virtual_memory.hpp index 3d4956e..9e8bc29 100644 --- a/src/virtual_memory.hpp +++ b/src/virtual_memory.hpp @@ -34,6 +34,9 @@ constexpr std::size_t alignSize(std::size_t pos, std::size_t align) { return ((pos - 1) / align + 1) * align; } -void* allocExecutableMemory(std::size_t); +void* allocMemoryPages(std::size_t); +void setPagesRW(void*, std::size_t); +void setPagesRX(void*, std::size_t); +void setPagesRWX(void*, std::size_t); void* allocLargePagesMemory(std::size_t); void freePagedMemory(void*, std::size_t); diff --git a/src/vm_compiled.cpp b/src/vm_compiled.cpp index 87bc3b8..c03f6b4 100644 --- a/src/vm_compiled.cpp +++ b/src/vm_compiled.cpp @@ -34,27 +34,44 @@ namespace randomx { static_assert(sizeof(MemoryRegisters) == 2 * sizeof(addr_t) + sizeof(uintptr_t), "Invalid alignment of struct randomx::MemoryRegisters"); static_assert(sizeof(RegisterFile) == 256, "Invalid alignment of struct randomx::RegisterFile"); - template - void CompiledVm::setDataset(randomx_dataset* dataset) { + template + CompiledVm::CompiledVm() { + if (!secureJit) { + compiler.enableAll(); //make JIT buffer both writable and executable + } + } + + template + void CompiledVm::setDataset(randomx_dataset* dataset) { datasetPtr = dataset; } - template - void CompiledVm::run(void* seed) { + template + void CompiledVm::run(void* seed) { VmBase::generateProgram(seed); randomx_vm::initialize(); + if (secureJit) { + compiler.enableWriting(); + } compiler.generateProgram(program, config); + if (secureJit) { + compiler.enableExecution(); + } mem.memory = datasetPtr->memory + datasetOffset; execute(); } - template - void CompiledVm::execute() { + template + void CompiledVm::execute() { compiler.getProgramFunc()(reg, mem, scratchpad, RANDOMX_PROGRAM_ITERATIONS); } - template class CompiledVm, false>; - template class CompiledVm, true>; - template class CompiledVm; - template class CompiledVm; + template class CompiledVm, false, false>; + template class CompiledVm, true, false>; + template class CompiledVm; + template class CompiledVm; + template class CompiledVm, false, true>; + template class CompiledVm, true, true>; + template class CompiledVm; + template class CompiledVm; } \ No newline at end of file diff --git a/src/vm_compiled.hpp b/src/vm_compiled.hpp index 856f00d..f7ceb0a 100644 --- a/src/vm_compiled.hpp +++ b/src/vm_compiled.hpp @@ -37,7 +37,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace randomx { - template + template class CompiledVm : public VmBase { public: void* operator new(size_t size) { @@ -49,6 +49,7 @@ namespace randomx { void operator delete(void* ptr) { AlignedAllocator::freeMemory(ptr, sizeof(CompiledVm)); } + CompiledVm(); void setDataset(randomx_dataset* dataset) override; void run(void* seed) override; @@ -65,8 +66,12 @@ namespace randomx { JitCompiler compiler; }; - using CompiledVmDefault = CompiledVm, true>; - using CompiledVmHardAes = CompiledVm, false>; - using CompiledVmLargePage = CompiledVm; - using CompiledVmLargePageHardAes = CompiledVm; + using CompiledVmDefault = CompiledVm, true, false>; + using CompiledVmHardAes = CompiledVm, false, false>; + using CompiledVmLargePage = CompiledVm; + using CompiledVmLargePageHardAes = CompiledVm; + using CompiledVmDefaultSecure = CompiledVm, true, true>; + using CompiledVmHardAesSecure = CompiledVm, false, true>; + using CompiledVmLargePageSecure = CompiledVm; + using CompiledVmLargePageHardAesSecure = CompiledVm; } diff --git a/src/vm_compiled_light.cpp b/src/vm_compiled_light.cpp index c083f4a..98dff34 100644 --- a/src/vm_compiled_light.cpp +++ b/src/vm_compiled_light.cpp @@ -32,23 +32,39 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace randomx { - template - void CompiledLightVm::setCache(randomx_cache* cache) { + template + void CompiledLightVm::setCache(randomx_cache* cache) { cachePtr = cache; mem.memory = cache->memory; + if (secureJit) { + compiler.enableWriting(); + } compiler.generateSuperscalarHash(cache->programs, cache->reciprocalCache); + if (secureJit) { + compiler.enableExecution(); + } } - template - void CompiledLightVm::run(void* seed) { + template + void CompiledLightVm::run(void* seed) { VmBase::generateProgram(seed); randomx_vm::initialize(); + if (secureJit) { + compiler.enableWriting(); + } compiler.generateProgramLight(program, config, datasetOffset); - CompiledVm::execute(); + if (secureJit) { + compiler.enableExecution(); + } + CompiledVm::execute(); } - template class CompiledLightVm, false>; - template class CompiledLightVm, true>; - template class CompiledLightVm; - template class CompiledLightVm; + template class CompiledLightVm, false, false>; + template class CompiledLightVm, true, false>; + template class CompiledLightVm; + template class CompiledLightVm; + template class CompiledLightVm, false, true>; + template class CompiledLightVm, true, true>; + template class CompiledLightVm; + template class CompiledLightVm; } \ No newline at end of file diff --git a/src/vm_compiled_light.hpp b/src/vm_compiled_light.hpp index 6af82bb..bed4ce1 100644 --- a/src/vm_compiled_light.hpp +++ b/src/vm_compiled_light.hpp @@ -33,8 +33,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace randomx { - template - class CompiledLightVm : public CompiledVm { + template + class CompiledLightVm : public CompiledVm { public: void* operator new(size_t size) { void* ptr = AlignedAllocator::allocMemory(size); @@ -49,16 +49,20 @@ namespace randomx { void setDataset(randomx_dataset* dataset) override { } void run(void* seed) override; - using CompiledVm::mem; - using CompiledVm::compiler; - using CompiledVm::program; - using CompiledVm::config; - using CompiledVm::cachePtr; - using CompiledVm::datasetOffset; + using CompiledVm::mem; + using CompiledVm::compiler; + using CompiledVm::program; + using CompiledVm::config; + using CompiledVm::cachePtr; + using CompiledVm::datasetOffset; }; - using CompiledLightVmDefault = CompiledLightVm, true>; - using CompiledLightVmHardAes = CompiledLightVm, false>; - using CompiledLightVmLargePage = CompiledLightVm; - using CompiledLightVmLargePageHardAes = CompiledLightVm; + using CompiledLightVmDefault = CompiledLightVm, true, false>; + using CompiledLightVmHardAes = CompiledLightVm, false, false>; + using CompiledLightVmLargePage = CompiledLightVm; + using CompiledLightVmLargePageHardAes = CompiledLightVm; + using CompiledLightVmDefaultSecure = CompiledLightVm, true, true>; + using CompiledLightVmHardAesSecure = CompiledLightVm, false, true>; + using CompiledLightVmLargePageSecure = CompiledLightVm; + using CompiledLightVmLargePageHardAesSecure = CompiledLightVm; } \ No newline at end of file