diff options
| -rw-r--r-- | libartbase/base/memfd.cc | 38 | ||||
| -rw-r--r-- | libartbase/base/memfd.h | 3 | ||||
| -rw-r--r-- | runtime/jit/jit_memory_region.cc | 25 | ||||
| -rw-r--r-- | runtime/jit/jit_memory_region_test.cc | 423 |
4 files changed, 463 insertions, 26 deletions
diff --git a/libartbase/base/memfd.cc b/libartbase/base/memfd.cc index c8ec18da9b..2aab7fc4fe 100644 --- a/libartbase/base/memfd.cc +++ b/libartbase/base/memfd.cc @@ -24,9 +24,16 @@ #include <sys/utsname.h> #include <unistd.h> #endif +#if defined(__BIONIC__) +#include <linux/memfd.h> // To access memfd flags. +#endif + +#include <android-base/logging.h> +#include <android-base/unique_fd.h> #include "macros.h" + // When building for linux host, glibc in prebuilts does not include memfd_create system call // number. As a temporary testing measure, we add the definition here. #if defined(__linux__) && !defined(__NR_memfd_create) @@ -91,4 +98,35 @@ int memfd_create_compat(const char* name, unsigned int flags) { return res; } +#if defined(__BIONIC__) + +static bool IsSealFutureWriteSupportedInternal() { + android::base::unique_fd fd(art::memfd_create("test_android_memfd", MFD_ALLOW_SEALING)); + if (fd == -1) { + LOG(INFO) << "memfd_create failed: " << strerror(errno) << ", no memfd support."; + return false; + } + + if (fcntl(fd, F_ADD_SEALS, F_SEAL_FUTURE_WRITE) == -1) { + LOG(INFO) << "fcntl(F_ADD_SEALS) failed: " << strerror(errno) << ", no memfd support."; + return false; + } + + LOG(INFO) << "Using memfd for future sealing"; + return true; +} + +bool IsSealFutureWriteSupported() { + static bool is_seal_future_write_supported = IsSealFutureWriteSupportedInternal(); + return is_seal_future_write_supported; +} + +#else + +bool IsSealFutureWriteSupported() { + return false; +} + +#endif + } // namespace art diff --git a/libartbase/base/memfd.h b/libartbase/base/memfd.h index b5945fb1dc..53cfe9cda1 100644 --- a/libartbase/base/memfd.h +++ b/libartbase/base/memfd.h @@ -31,6 +31,9 @@ int memfd_create(const char* name, unsigned int flags); // other way if memfd fails or isn't supported. int memfd_create_compat(const char* name, unsigned int flags); +// Return whether the kernel supports sealing future writes of a memfd. +bool IsSealFutureWriteSupported(); + } // namespace art #endif // ART_LIBARTBASE_BASE_MEMFD_H_ diff --git a/runtime/jit/jit_memory_region.cc b/runtime/jit/jit_memory_region.cc index 379aa7c5d3..9092695690 100644 --- a/runtime/jit/jit_memory_region.cc +++ b/runtime/jit/jit_memory_region.cc @@ -486,31 +486,10 @@ void JitMemoryRegion::FreeData(uint8_t* data) { #if defined(__BIONIC__) -static bool IsSealFutureWriteSupportedInternal() { - unique_fd fd(art::memfd_create("test_android_memfd", MFD_ALLOW_SEALING)); - if (fd == -1) { - LOG(INFO) << "memfd_create failed: " << strerror(errno) << ", no memfd support."; - return false; - } - - if (fcntl(fd, F_ADD_SEALS, F_SEAL_FUTURE_WRITE) == -1) { - LOG(INFO) << "fcntl(F_ADD_SEALS) failed: " << strerror(errno) << ", no memfd support."; - return false; - } - - LOG(INFO) << "Using memfd for future sealing"; - return true; -} - -static bool IsSealFutureWriteSupported() { - static bool is_seal_future_write_supported = IsSealFutureWriteSupportedInternal(); - return is_seal_future_write_supported; -} - int JitMemoryRegion::CreateZygoteMemory(size_t capacity, std::string* error_msg) { /* Check if kernel support exists, otherwise fall back to ashmem */ static const char* kRegionName = "/jit-zygote-cache"; - if (IsSealFutureWriteSupported()) { + if (art::IsSealFutureWriteSupported()) { int fd = art::memfd_create(kRegionName, MFD_ALLOW_SEALING); if (fd == -1) { std::ostringstream oss; @@ -544,7 +523,7 @@ int JitMemoryRegion::CreateZygoteMemory(size_t capacity, std::string* error_msg) } bool JitMemoryRegion::ProtectZygoteMemory(int fd, std::string* error_msg) { - if (IsSealFutureWriteSupported()) { + if (art::IsSealFutureWriteSupported()) { if (fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL | F_SEAL_FUTURE_WRITE) == -1) { std::ostringstream oss; diff --git a/runtime/jit/jit_memory_region_test.cc b/runtime/jit/jit_memory_region_test.cc index 25255bbb7d..e99ff708b6 100644 --- a/runtime/jit/jit_memory_region_test.cc +++ b/runtime/jit/jit_memory_region_test.cc @@ -16,19 +16,43 @@ #include "jit/jit_memory_region.h" +#include <signal.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <unistd.h> + #include <android-base/unique_fd.h> #include <gtest/gtest.h> -#include <sys/mman.h> #include "base/globals.h" +#include "base/memfd.h" namespace art { namespace jit { +// These tests only run on bionic. +#if defined(__BIONIC__) +static constexpr int kReturnFromFault = 42; + +// These globals are only set in child processes. +void* gAddrToFaultOn = nullptr; + +void handler(int ATTRIBUTE_UNUSED, siginfo_t* info, void* ATTRIBUTE_UNUSED) { + CHECK_EQ(info->si_addr, gAddrToFaultOn); + exit(kReturnFromFault); +} + +static void registerSignalHandler() { + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = handler; + sigaction(SIGSEGV, &sa, nullptr); +} + class TestZygoteMemory : public testing::Test { public: void BasicTest() { -#if defined(__BIONIC__) std::string error_msg; size_t size = kPageSize; android::base::unique_fd fd(JitMemoryRegion::CreateZygoteMemory(size, &error_msg)); @@ -77,7 +101,386 @@ class TestZygoteMemory : public testing::Test { // Test that we can write into the remapped mapping. addr2[0] = 4; CHECK_EQ(addr2[0], 4); -#endif + } + + void TestUnmapWritableAfterFork() { + std::string error_msg; + size_t size = kPageSize; + int32_t* addr = nullptr; + int32_t* addr2 = nullptr; + { + android::base::unique_fd fd(JitMemoryRegion::CreateZygoteMemory(size, &error_msg)); + CHECK_NE(fd.get(), -1); + + // Create a writable mapping. + addr = reinterpret_cast<int32_t*>( + mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0)); + CHECK(addr != nullptr); + CHECK_NE(addr, MAP_FAILED); + + // Test that we can write into the mapping. + addr[0] = 42; + CHECK_EQ(addr[0], 42); + + // Create a read-only mapping. + addr2 = reinterpret_cast<int32_t*>( + mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, fd.get(), 0)); + CHECK(addr2 != nullptr); + + // Protect the memory. + bool res = JitMemoryRegion::ProtectZygoteMemory(fd.get(), &error_msg); + CHECK(res); + } + // At this point, the fd has been dropped, but the memory mappings are still + // there. + + // Create a mapping of atomic ints to communicate between processes. + android::base::unique_fd fd2(JitMemoryRegion::CreateZygoteMemory(size, &error_msg)); + CHECK_NE(fd2.get(), -1); + std::atomic<int32_t>* shared = reinterpret_cast<std::atomic<int32_t>*>( + mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd2.get(), 0)); + + // Values used for the tests below. + const int32_t parent_value = 66; + const int32_t child_value = 33; + const int32_t starting_value = 22; + + shared[0] = 0; + addr[0] = starting_value; + CHECK_EQ(addr[0], starting_value); + CHECK_EQ(addr2[0], starting_value); + pid_t pid = fork(); + if (pid == 0) { + // Test that we can write into the mapping. + addr[0] = child_value; + CHECK_EQ(addr[0], child_value); + CHECK_EQ(addr2[0], child_value); + + // Unmap the writable mappping. + munmap(addr, kPageSize); + + CHECK_EQ(addr2[0], child_value); + + // Notify parent process. + shared[0] = 1; + + // Wait for parent process for a new value. + while (shared[0] != 2) { + sched_yield(); + } + CHECK_EQ(addr2[0], parent_value); + + // Test that we cannot write into the mapping. The signal handler will + // exit the process. + gAddrToFaultOn = addr; + registerSignalHandler(); + // This write will trigger a fault, as `addr` is unmapped. + addr[0] = child_value + 1; + exit(0); + } else { + while (shared[0] != 1) { + sched_yield(); + } + CHECK_EQ(addr[0], child_value); + CHECK_EQ(addr2[0], child_value); + addr[0] = parent_value; + // Notify the child if the new value. + shared[0] = 2; + int status; + CHECK_EQ(waitpid(pid, &status, 0), pid); + CHECK(WIFEXITED(status)) << strerror(errno); + CHECK_EQ(WEXITSTATUS(status), kReturnFromFault); + CHECK_EQ(addr[0], parent_value); + CHECK_EQ(addr2[0], parent_value); + munmap(addr, kPageSize); + munmap(addr2, kPageSize); + munmap(shared, kPageSize); + } + } + + void TestMadviseDontFork() { + std::string error_msg; + size_t size = kPageSize; + int32_t* addr = nullptr; + int32_t* addr2 = nullptr; + { + android::base::unique_fd fd(JitMemoryRegion::CreateZygoteMemory(size, &error_msg)); + CHECK_NE(fd.get(), -1); + + // Create a writable mapping. + addr = reinterpret_cast<int32_t*>( + mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0)); + CHECK(addr != nullptr); + CHECK_NE(addr, MAP_FAILED); + CHECK_EQ(madvise(addr, kPageSize, MADV_DONTFORK), 0); + + // Test that we can write into the mapping. + addr[0] = 42; + CHECK_EQ(addr[0], 42); + + // Create a read-only mapping. + addr2 = reinterpret_cast<int32_t*>( + mmap(nullptr, kPageSize, PROT_READ, MAP_SHARED, fd.get(), 0)); + CHECK(addr2 != nullptr); + + // Protect the memory. + bool res = JitMemoryRegion::ProtectZygoteMemory(fd.get(), &error_msg); + CHECK(res); + } + // At this point, the fd has been dropped, but the memory mappings are still + // there. + + // Create a mapping of atomic ints to communicate between processes. + android::base::unique_fd fd2(JitMemoryRegion::CreateZygoteMemory(size, &error_msg)); + CHECK_NE(fd2.get(), -1); + std::atomic<int32_t>* shared = reinterpret_cast<std::atomic<int32_t>*>( + mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd2.get(), 0)); + + // Values used for the tests below. + const int32_t parent_value = 66; + const int32_t child_value = 33; + const int32_t starting_value = 22; + + shared[0] = 0; + addr[0] = starting_value; + CHECK_EQ(addr[0], starting_value); + CHECK_EQ(addr2[0], starting_value); + pid_t pid = fork(); + if (pid == 0) { + CHECK_EQ(addr2[0], starting_value); + + // Notify parent process. + shared[0] = 1; + + // Wait for parent process for new value. + while (shared[0] != 2) { + sched_yield(); + } + + CHECK_EQ(addr2[0], parent_value); + // Test that we cannot write into the mapping. The signal handler will + // exit the process. + gAddrToFaultOn = addr; + registerSignalHandler(); + addr[0] = child_value + 1; + exit(0); + } else { + while (shared[0] != 1) { + sched_yield(); + } + CHECK_EQ(addr[0], starting_value); + CHECK_EQ(addr2[0], starting_value); + addr[0] = parent_value; + // Notify the child of the new value. + shared[0] = 2; + int status; + CHECK_EQ(waitpid(pid, &status, 0), pid); + CHECK(WIFEXITED(status)) << strerror(errno); + CHECK_EQ(WEXITSTATUS(status), kReturnFromFault); + CHECK_EQ(addr[0], parent_value); + CHECK_EQ(addr2[0], parent_value); + + munmap(addr, kPageSize); + munmap(addr2, kPageSize); + munmap(shared, kPageSize); + } + } + + // This code is testing some behavior that ART could potentially use: get a + // copy-on-write mapping that can incorporate changes from a shared mapping + // owned by another process. + void TestFromSharedToPrivate() { + // This test is only for memfd with future write sealing support: + // 1) ashmem with PROT_READ doesn't permit mapping MAP_PRIVATE | PROT_WRITE + // 2) ashmem mapped MAP_PRIVATE discards the contents already written. + if (!art::IsSealFutureWriteSupported()) { + return; + } + std::string error_msg; + size_t size = kPageSize; + int32_t* addr = nullptr; + android::base::unique_fd fd(JitMemoryRegion::CreateZygoteMemory(size, &error_msg)); + CHECK_NE(fd.get(), -1); + + // Create a writable mapping. + addr = reinterpret_cast<int32_t*>( + mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0)); + CHECK(addr != nullptr); + CHECK_NE(addr, MAP_FAILED); + + // Test that we can write into the mapping. + addr[0] = 42; + CHECK_EQ(addr[0], 42); + + // Create another mapping of atomic ints to communicate between processes. + android::base::unique_fd fd2(JitMemoryRegion::CreateZygoteMemory(size, &error_msg)); + CHECK_NE(fd2.get(), -1); + std::atomic<int32_t>* shared = reinterpret_cast<std::atomic<int32_t>*>( + mmap(nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd2.get(), 0)); + + // Protect the memory. + CHECK(JitMemoryRegion::ProtectZygoteMemory(fd.get(), &error_msg)); + + // Values used for the tests below. + const int32_t parent_value = 66; + const int32_t child_value = 33; + const int32_t starting_value = 22; + + // Check that updates done by a child mapping write-private are not visible + // to the parent. + addr[0] = starting_value; + shared[0] = 0; + pid_t pid = fork(); + if (pid == 0) { + CHECK_EQ(mmap(addr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fd.get(), 0), + addr); + addr[0] = child_value; + exit(0); + } else { + int status; + CHECK_EQ(waitpid(pid, &status, 0), pid); + CHECK(WIFEXITED(status)) << strerror(errno); + CHECK_EQ(addr[0], starting_value); + } + + addr[0] = starting_value; + shared[0] = 0; + + // Check getting back and forth on shared mapping. + pid = fork(); + if (pid == 0) { + // Map it private with write access. MAP_FIXED will replace the existing + // mapping. + CHECK_EQ(mmap(addr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fd.get(), 0), + addr); + addr[0] = child_value; + CHECK_EQ(addr[0], child_value); + + // Check that mapping shared with write access fails. + CHECK_EQ(mmap(addr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd.get(), 0), + MAP_FAILED); + CHECK_EQ(errno, EPERM); + + // Map shared with read access. + CHECK_EQ(mmap(addr, kPageSize, PROT_READ, MAP_SHARED | MAP_FIXED, fd.get(), 0), addr); + CHECK_NE(addr[0], child_value); + + // Wait for the parent to notify. + while (shared[0] != 1) { + sched_yield(); + } + CHECK_EQ(addr[0], parent_value); + + // Notify the parent for getting a new update of the buffer. + shared[0] = 2; + + // Map it private again. + CHECK_EQ(mmap(addr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fd.get(), 0), + addr); + addr[0] = child_value + 1; + CHECK_EQ(addr[0], child_value + 1); + + // And map it back shared. + CHECK_EQ(mmap(addr, kPageSize, PROT_READ, MAP_SHARED | MAP_FIXED, fd.get(), 0), addr); + while (shared[0] != 3) { + sched_yield(); + } + CHECK_EQ(addr[0], parent_value + 1); + exit(0); + } else { + addr[0] = parent_value; + CHECK_EQ(addr[0], parent_value); + + // Notify the child of the new value. + shared[0] = 1; + + // Wait for the child to ask for a new value; + while (shared[0] != 2) { + sched_yield(); + } + addr[0] = parent_value + 1; + CHECK_EQ(addr[0], parent_value + 1); + + // Notify the child of a new value. + shared[0] = 3; + int status; + CHECK_EQ(waitpid(pid, &status, 0), pid); + CHECK(WIFEXITED(status)) << strerror(errno); + CHECK_EQ(addr[0], parent_value + 1); + } + + // Check that updates done by the parent are visible after a new mmap + // write-private. + shared[0] = 0; + addr[0] = starting_value; + pid = fork(); + if (pid == 0) { + CHECK_EQ(mmap(addr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fd.get(), 0), + addr); + CHECK_EQ(addr[0], starting_value); + addr[0] = child_value; + CHECK_EQ(addr[0], child_value); + + // Notify the parent to update the buffer. + shared[0] = 1; + + // Wait for the parent update. + while (shared[0] != 2) { + sched_yield(); + } + // Test the buffer still contains our own data, and not the parent's. + CHECK_EQ(addr[0], child_value); + + // Test the buffer contains the parent data after a new mmap. + CHECK_EQ(mmap(addr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fd.get(), 0), + addr); + CHECK_EQ(addr[0], parent_value); + exit(0); + } else { + // Wait for the child to start + while (shared[0] != 1) { + sched_yield(); + } + CHECK_EQ(addr[0], starting_value); + addr[0] = parent_value; + // Notify the child that the buffer has been written. + shared[0] = 2; + int status; + CHECK_EQ(waitpid(pid, &status, 0), pid); + CHECK(WIFEXITED(status)) << strerror(errno); + CHECK_EQ(addr[0], parent_value); + } + + // Check that updates done by the parent are visible for a new mmap + // write-private that hasn't written to the buffer yet. + shared[0] = 0; + addr[0] = starting_value; + pid = fork(); + if (pid == 0) { + CHECK_EQ(mmap(addr, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fd.get(), 0), + addr); + CHECK_EQ(addr[0], starting_value); + // Notify the parent for a new update of the buffer. + shared[0] = 1; + while (addr[0] != parent_value) { + sched_yield(); + } + addr[0] = child_value; + CHECK_EQ(addr[0], child_value); + exit(0); + } else { + while (shared[0] != 1) { + sched_yield(); + } + CHECK_EQ(addr[0], starting_value); + addr[0] = parent_value; + int status; + CHECK_EQ(waitpid(pid, &status, 0), pid); + CHECK(WIFEXITED(status)) << strerror(errno); + CHECK_EQ(addr[0], parent_value); + } + munmap(addr, kPageSize); + munmap(shared, kPageSize); } }; @@ -85,5 +488,19 @@ TEST_F(TestZygoteMemory, BasicTest) { BasicTest(); } +TEST_F(TestZygoteMemory, TestUnmapWritableAfterFork) { + TestUnmapWritableAfterFork(); +} + +TEST_F(TestZygoteMemory, TestMadviseDontFork) { + TestMadviseDontFork(); +} + +TEST_F(TestZygoteMemory, TestFromSharedToPrivate) { + TestFromSharedToPrivate(); +} + +#endif // defined (__BIONIC__) + } // namespace jit } // namespace art |