Commit efcb8d21 authored by Benoît Lizé's avatar Benoît Lizé Committed by Commit Bot

base/android: Detect and use memfd_create() in ModernLinker.

memfd_create() avoids having to write relocations to disk. It is available on
recent-ish Android kernels (>= 3.17). Use it where possible in ModernLinker.

Bug: 979638
Change-Id: I6b52373490bec0c29b9028558d3926fff75d7a4d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1730893Reviewed-by: default avatarRichard Coles <torne@chromium.org>
Reviewed-by: default avatarEgor Pasko <pasko@chromium.org>
Commit-Queue: Benoit L <lizeb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#684352}
parent 70afb65d
include_rules = [ include_rules = [
# This code cannot depend on anything from base/ # This code cannot depend on anything from base/.
"-base", "-base",
"+base/android/linker",
] ]
...@@ -7,23 +7,36 @@ ...@@ -7,23 +7,36 @@
// This source code *cannot* depend on anything from base/ or the C++ // This source code *cannot* depend on anything from base/ or the C++
// STL, to keep the final library small, and avoid ugly dependency issues. // STL, to keep the final library small, and avoid ugly dependency issues.
#include "modern_linker_jni.h" #include "base/android/linker/modern_linker_jni.h"
#include <android/dlext.h>
#include <dlfcn.h> #include <dlfcn.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <jni.h> #include <jni.h>
#include <limits.h> #include <limits.h>
#include <link.h> #include <link.h>
#include <linux/memfd.h>
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <string.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h> #include <unistd.h>
#include <android/dlext.h> #include "base/android/linker/linker_jni.h"
#include "linker_jni.h"
// From //base/posix/eintr_wrapper.h, but we don't want to depend on //base.
#define HANDLE_EINTR(x) \
({ \
decltype(x) eintr_wrapper_result; \
do { \
eintr_wrapper_result = (x); \
} while (eintr_wrapper_result == -1 && errno == EINTR); \
eintr_wrapper_result; \
})
// Not defined on all platforms. As this linker is only supported on ARM32/64, // Not defined on all platforms. As this linker is only supported on ARM32/64,
// x86/x86_64 and MIPS, page size is always 4k. // x86/x86_64 and MIPS, page size is always 4k.
...@@ -52,6 +65,36 @@ int dl_iterate_phdr(int (*cb)(dl_phdr_info* info, size_t size, void* data), ...@@ -52,6 +65,36 @@ int dl_iterate_phdr(int (*cb)(dl_phdr_info* info, size_t size, void* data),
namespace chromium_android_linker { namespace chromium_android_linker {
namespace { namespace {
// Returns whether memfd_create() is supported on this kernel.
bool IsMemfdSupported() {
// Check the kernel version. There is precedent of OEMs adding syscalls to
// shipping kernels with a number used on later ones, so relying on the kernel
// to return ENOSYS doesn't work.
static constexpr int kRequiredMajor = 3;
static constexpr int kRequiredMinor = 17;
struct utsname uts;
int major, minor;
if (uname(&uts))
return false;
if (strcmp(uts.sysname, "Linux") != 0 ||
sscanf(uts.release, "%d.%d", &major, &minor) != 2)
return false;
if (major < kRequiredMajor ||
(major == kRequiredMajor && minor < kRequiredMinor)) {
return false;
}
return true;
}
// Wrapper, as bionic doesn't expose it.
int memfd_create(const char* name, unsigned int flags) {
return syscall(__NR_memfd_create, name, flags);
}
// Record of the Java VM passed to JNI_OnLoad(). // Record of the Java VM passed to JNI_OnLoad().
static JavaVM* s_java_vm = nullptr; static JavaVM* s_java_vm = nullptr;
...@@ -183,34 +226,98 @@ class ScopedAnonymousMmap { ...@@ -183,34 +226,98 @@ class ScopedAnonymousMmap {
}; };
// Makes sure the file descriptor is closed unless |Release()| is called. // Makes sure the file descriptor is closed unless |Release()| is called.
//
// May either be backed by a memfd or a regular file, depending on the kernel.
class ScopedFileDescriptor { class ScopedFileDescriptor {
public: public:
ScopedFileDescriptor(int fd) : fd_(fd), owned_(true) {} static ScopedFileDescriptor Open(const String& path);
~ScopedFileDescriptor() { ~ScopedFileDescriptor() {
if (owned_) if (owned_)
Close(); Close();
} }
ScopedFileDescriptor(ScopedFileDescriptor&& o) ScopedFileDescriptor(ScopedFileDescriptor&& o)
: fd_(o.fd_), owned_(o.owned_) { : fd_(o.fd_), owned_(o.owned_) {
o.owned_ = false; o.owned_ = false;
} }
int get() const { return fd_; } int get() const { return fd_; }
void Release() { owned_ = false; } void Release() { owned_ = false; }
void Close() { void Close() {
if (fd_ != -1) if (fd_ != -1)
close(fd_); close(fd_);
owned_ = false; owned_ = false;
} }
bool ReopenReadOnly(const String& original_path);
private: private:
const int fd_; int fd_ = -1;
bool owned_; bool owned_ = false;
bool is_memfd_ = false;
static ScopedFileDescriptor OpenFile(const String& path);
// Must only be called if memfd is supported on this kernel.
static ScopedFileDescriptor OpenMemfd(const String& path);
ScopedFileDescriptor(int fd, bool is_memfd)
: fd_(fd), owned_(true), is_memfd_(is_memfd) {}
// Move only. // Move only.
ScopedFileDescriptor(const ScopedFileDescriptor&) = delete; ScopedFileDescriptor(const ScopedFileDescriptor&) = delete;
ScopedFileDescriptor& operator=(const ScopedFileDescriptor&) = delete; ScopedFileDescriptor& operator=(const ScopedFileDescriptor&) = delete;
}; };
ScopedFileDescriptor ScopedFileDescriptor::Open(const String& path) {
return IsMemfdSupported() ? ScopedFileDescriptor::OpenMemfd(path)
: ScopedFileDescriptor::OpenFile(path);
}
ScopedFileDescriptor ScopedFileDescriptor::OpenFile(const String& path) {
int flags = O_RDWR | O_CREAT | O_EXCL;
int mode = S_IRUSR | S_IWUSR;
int fd = HANDLE_EINTR(open(path.c_str(), flags, mode));
return {fd, false};
}
ScopedFileDescriptor ScopedFileDescriptor::OpenMemfd(const String& path) {
LOG_INFO("Using a memfd file descriptor.");
return {memfd_create(path.c_str(), MFD_ALLOW_SEALING), true};
}
bool ScopedFileDescriptor::ReopenReadOnly(const String& original_path) {
if (is_memfd_) {
// This is safe as we seal the fd before sending it to another process.
// The target process cannot do anything to alter the underlying "file".
int err = fcntl(fd_, F_ADD_SEALS,
F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL);
if (err) {
LOG_ERROR("Cannot seal the fd: %s", strerror(errno));
return false;
}
} else {
const char* filepath = original_path.c_str();
Close();
fd_ = HANDLE_EINTR(open(filepath, O_RDONLY));
if (fd_ == -1) {
LOG_ERROR("open: %s: %s", filepath, strerror(errno));
return false;
}
// Delete the directory entry for the RELRO file. The fd we hold ensures
// that its data remains intact.
if (unlink(filepath) == -1) {
LOG_ERROR("unlink: %s: %s", filepath, strerror(errno));
return false;
}
}
return true;
}
// Reserves an address space range, starting at |address|. // Reserves an address space range, starting at |address|.
// If successful, returns a valid mapping, otherwise returns an empty one. // If successful, returns a valid mapping, otherwise returns an empty one.
ScopedAnonymousMmap ScopedAnonymousMmap::ReserveAtAddress(void* address, ScopedAnonymousMmap ScopedAnonymousMmap::ReserveAtAddress(void* address,
...@@ -253,28 +360,6 @@ bool GetLibraryLoadSize(void* addr, size_t* load_size, size_t* min_vaddr) { ...@@ -253,28 +360,6 @@ bool GetLibraryLoadSize(void* addr, size_t* load_size, size_t* min_vaddr) {
return true; return true;
} }
// Reopens |fd| that was initially opened from |path| as a read-only fd.
// Deletes the file in the process, and returns the new read only file
// descriptor in case of success, -1 otherwise.
ScopedFileDescriptor ReopenReadOnly(const String& path,
ScopedFileDescriptor original_fd) {
const char* filepath = path.c_str();
original_fd.Close();
ScopedFileDescriptor scoped_fd{open(filepath, O_RDONLY)};
if (scoped_fd.get() == -1) {
LOG_ERROR("open: %s: %s", path.c_str(), strerror(errno));
return -1;
}
// Delete the directory entry for the RELRO file. The fd we hold ensures
// that its data remains intact.
if (unlink(filepath) == -1) {
LOG_ERROR("unlink: %s: %s", filepath, strerror(errno));
return -1;
}
return scoped_fd;
}
// Resizes the address space reservation to the actual required size. // Resizes the address space reservation to the actual required size.
// Failure here is only a warning, as at worst this wastes virtual address // Failure here is only a warning, as at worst this wastes virtual address
// space, not actual memory. // space, not actual memory.
...@@ -326,11 +411,10 @@ bool CallJniOnLoad(void* handle) { ...@@ -326,11 +411,10 @@ bool CallJniOnLoad(void* handle) {
return true; return true;
} }
// Load the library at |path| at address |wanted_address| if possible, and // Loads the library at |path| at address |wanted_address| if possible, and
// creates a file with relro at |relocations_path|. // creates a file with relro at |relocations_path|.
// //
// In case of success, returns a readonly file descriptor to the relocations, // Returns a readonly file descriptor to the relocations, or -1 for failure;
// otherwise returns -1.
int LoadCreateSharedRelocations(const String& path, int LoadCreateSharedRelocations(const String& path,
void* wanted_address, void* wanted_address,
const String& relocations_path) { const String& relocations_path) {
...@@ -341,8 +425,9 @@ int LoadCreateSharedRelocations(const String& path, ...@@ -341,8 +425,9 @@ int LoadCreateSharedRelocations(const String& path,
return -1; return -1;
unlink(relocations_path.c_str()); unlink(relocations_path.c_str());
ScopedFileDescriptor relro_fd = ScopedFileDescriptor{open( // TODO(lizeb): Don't fallback to a file, instead don't even reach here
relocations_path.c_str(), O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)}; // when memfd is not supported, and use the system linker in Java.
ScopedFileDescriptor relro_fd = ScopedFileDescriptor::Open(relocations_path);
if (relro_fd.get() == -1) { if (relro_fd.get() == -1) {
LOG_ERROR("open: %s: %s", relocations_path.c_str(), strerror(errno)); LOG_ERROR("open: %s: %s", relocations_path.c_str(), strerror(errno));
return -1; return -1;
...@@ -367,13 +452,14 @@ int LoadCreateSharedRelocations(const String& path, ...@@ -367,13 +452,14 @@ int LoadCreateSharedRelocations(const String& path,
unlink(relocations_path.c_str()); unlink(relocations_path.c_str());
return false; return false;
} }
ScopedFileDescriptor scoped_fd = if (!relro_fd.ReopenReadOnly(relocations_path))
ReopenReadOnly(relocations_path, std::move(relro_fd)); return -1;
scoped_fd.Release();
return scoped_fd.get(); relro_fd.Release();
return relro_fd.get();
} }
// Load the library at |path| at address |wanted_address| if possible, and // Loads the library at |path| at address |wanted_address| if possible, and
// uses the relocations in |relocations_fd| if possible. // uses the relocations in |relocations_fd| if possible.
bool LoadUseSharedRelocations(const String& path, bool LoadUseSharedRelocations(const String& path,
void* wanted_address, void* wanted_address,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment