linker: allow using reserved space for multiple libraries.
Introduce a new flag ANDROID_DLEXT_RESERVED_ADDRESS_RECURSIVE which
instructs the linker to use the reserved address space to load all of
the newly-loaded libraries required by a dlopen() call instead of only
the main library. They will be loaded consecutively into that region if
they fit. The RELRO sections of all the loaded libraries will also be
considered for reading/writing shared RELRO data.
This will allow the WebView implementation to potentially consist of
more than one .so file while still benefiting from the RELRO sharing
optimisation, which would otherwise only apply to the "root" .so file.
Test: bionic-unit-tests (existing and newly added)
Bug: 110790153
Change-Id: I61da775c29fd5017d9a1e2b6b3757c3d20a355b3
diff --git a/tests/Android.bp b/tests/Android.bp
index c1d19fa..361d0b7 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -386,6 +386,7 @@
],
static_libs: [
"libmeminfo",
+ "libprocinfo",
"libziparchive",
"libLLVMObject",
"libLLVMBitReader",
@@ -528,6 +529,7 @@
"libdlext_test_different_soname",
"libdlext_test_fd",
"libdlext_test_norelro",
+ "libdlext_test_recursive",
"libdlext_test_runpath_zip_zipaligned",
"libdlext_test",
"libdlext_test_zip",
diff --git a/tests/dlext_test.cpp b/tests/dlext_test.cpp
index c9ecd2e..edeb4b1 100644
--- a/tests/dlext_test.cpp
+++ b/tests/dlext_test.cpp
@@ -37,6 +37,7 @@
#include <sys/wait.h>
#include <meminfo/procmeminfo.h>
+#include <procinfo/process_map.h>
#include <ziparchive/zip_archive.h>
#include "gtest_globals.h"
@@ -59,6 +60,7 @@
typedef int (*fn)(void);
constexpr const char* kLibName = "libdlext_test.so";
+constexpr const char* kLibNameRecursive = "libdlext_test_recursive.so";
constexpr const char* kLibNameNoRelro = "libdlext_test_norelro.so";
constexpr const char* kLibZipSimpleZip = "libdir/libatest_simple_zip.so";
constexpr auto kLibSize = 1024 * 1024; // how much address space to reserve for it
@@ -332,6 +334,48 @@
EXPECT_EQ(nullptr, handle_);
}
+TEST_F(DlExtTest, ReservedRecursive) {
+ void* start = mmap(nullptr, kLibSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ ASSERT_TRUE(start != MAP_FAILED);
+ android_dlextinfo extinfo;
+ extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS | ANDROID_DLEXT_RESERVED_ADDRESS_RECURSIVE;
+ extinfo.reserved_addr = start;
+ extinfo.reserved_size = kLibSize;
+ handle_ = android_dlopen_ext(kLibNameRecursive, RTLD_NOW, &extinfo);
+ ASSERT_DL_NOTNULL(handle_);
+
+ fn f = reinterpret_cast<fn>(dlsym(handle_, "getRandomNumber"));
+ ASSERT_DL_NOTNULL(f);
+ EXPECT_GE(reinterpret_cast<void*>(f), start);
+ EXPECT_LT(reinterpret_cast<void*>(f),
+ reinterpret_cast<char*>(start) + kLibSize);
+ EXPECT_EQ(4, f());
+
+ f = reinterpret_cast<fn>(dlsym(handle_, "getBiggerRandomNumber"));
+ ASSERT_DL_NOTNULL(f);
+ EXPECT_GE(reinterpret_cast<void*>(f), start);
+ EXPECT_LT(reinterpret_cast<void*>(f),
+ reinterpret_cast<char*>(start) + kLibSize);
+ EXPECT_EQ(8, f());
+
+ uint32_t* taxicab_number = reinterpret_cast<uint32_t*>(dlsym(handle_, "dlopen_testlib_taxicab_number"));
+ ASSERT_DL_NOTNULL(taxicab_number);
+ EXPECT_GE(reinterpret_cast<void*>(taxicab_number), start);
+ EXPECT_LT(reinterpret_cast<void*>(taxicab_number), reinterpret_cast<char*>(start) + kLibSize);
+ EXPECT_EQ(1729U, *taxicab_number);
+}
+
+TEST_F(DlExtTest, ReservedRecursiveTooSmall) {
+ void* start = mmap(nullptr, kLibSize/2, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ ASSERT_TRUE(start != MAP_FAILED);
+ android_dlextinfo extinfo;
+ extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS | ANDROID_DLEXT_RESERVED_ADDRESS_RECURSIVE;
+ extinfo.reserved_addr = start;
+ extinfo.reserved_size = kLibSize/2;
+ handle_ = android_dlopen_ext(kLibNameRecursive, RTLD_NOW, &extinfo);
+ EXPECT_EQ(nullptr, handle_);
+}
+
TEST_F(DlExtTest, ReservedHint) {
void* start = mmap(nullptr, kLibSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_TRUE(start != MAP_FAILED);
@@ -382,10 +426,14 @@
DlExtTest::TearDown();
}
- void CreateRelroFile(const char* lib, const char* relro_file) {
+ void CreateRelroFile(const char* lib, const char* relro_file, bool recursive) {
int relro_fd = open(relro_file, O_RDWR | O_TRUNC | O_CLOEXEC);
ASSERT_NOERROR(relro_fd);
+ if (recursive) {
+ extinfo_.flags |= ANDROID_DLEXT_RESERVED_ADDRESS_RECURSIVE;
+ }
+
pid_t pid = fork();
if (pid == 0) {
// child process
@@ -411,13 +459,19 @@
extinfo_.relro_fd = relro_fd;
}
- void TryUsingRelro(const char* lib) {
+ void TryUsingRelro(const char* lib, bool recursive) {
handle_ = android_dlopen_ext(lib, RTLD_NOW, &extinfo_);
ASSERT_DL_NOTNULL(handle_);
fn f = reinterpret_cast<fn>(dlsym(handle_, "getRandomNumber"));
ASSERT_DL_NOTNULL(f);
EXPECT_EQ(4, f());
+ if (recursive) {
+ fn f = reinterpret_cast<fn>(dlsym(handle_, "getBiggerRandomNumber"));
+ ASSERT_DL_NOTNULL(f);
+ EXPECT_EQ(8, f());
+ }
+
uint32_t* taxicab_number =
reinterpret_cast<uint32_t*>(dlsym(handle_, "dlopen_testlib_taxicab_number"));
ASSERT_DL_NOTNULL(taxicab_number);
@@ -427,6 +481,8 @@
void SpawnChildrenAndMeasurePss(const char* lib, const char* relro_file, bool share_relro,
size_t* pss_out);
+ std::string FindMappingName(void* ptr);
+
android_dlextinfo extinfo_;
};
@@ -434,8 +490,29 @@
TemporaryFile tf; // Use tf to get an unique filename.
ASSERT_NOERROR(close(tf.fd));
- ASSERT_NO_FATAL_FAILURE(CreateRelroFile(kLibName, tf.path));
- ASSERT_NO_FATAL_FAILURE(TryUsingRelro(kLibName));
+ ASSERT_NO_FATAL_FAILURE(CreateRelroFile(kLibName, tf.path, false));
+ ASSERT_NO_FATAL_FAILURE(TryUsingRelro(kLibName, false));
+ void* relro_data = dlsym(handle_, "lots_of_relro");
+ ASSERT_DL_NOTNULL(relro_data);
+ EXPECT_EQ(tf.path, FindMappingName(relro_data));
+
+ // Use destructor of tf to close and unlink the file.
+ tf.fd = extinfo_.relro_fd;
+}
+
+TEST_F(DlExtRelroSharingTest, ChildWritesGoodDataRecursive) {
+ TemporaryFile tf; // Use tf to get an unique filename.
+ ASSERT_NOERROR(close(tf.fd));
+
+ ASSERT_NO_FATAL_FAILURE(CreateRelroFile(kLibNameRecursive, tf.path, true));
+ ASSERT_NO_FATAL_FAILURE(TryUsingRelro(kLibNameRecursive, true));
+ void* relro_data = dlsym(handle_, "lots_of_relro");
+ ASSERT_DL_NOTNULL(relro_data);
+ EXPECT_EQ(tf.path, FindMappingName(relro_data));
+ void* recursive_relro_data = dlsym(handle_, "lots_more_relro");
+ ASSERT_DL_NOTNULL(recursive_relro_data);
+ EXPECT_EQ(tf.path, FindMappingName(recursive_relro_data));
+
// Use destructor of tf to close and unlink the file.
tf.fd = extinfo_.relro_fd;
@@ -445,15 +522,15 @@
TemporaryFile tf; // // Use tf to get an unique filename.
ASSERT_NOERROR(close(tf.fd));
- ASSERT_NO_FATAL_FAILURE(CreateRelroFile(kLibNameNoRelro, tf.path));
- ASSERT_NO_FATAL_FAILURE(TryUsingRelro(kLibNameNoRelro));
+ ASSERT_NO_FATAL_FAILURE(CreateRelroFile(kLibNameNoRelro, tf.path, false));
+ ASSERT_NO_FATAL_FAILURE(TryUsingRelro(kLibNameNoRelro, false));
// Use destructor of tf to close and unlink the file.
tf.fd = extinfo_.relro_fd;
}
TEST_F(DlExtRelroSharingTest, RelroFileEmpty) {
- ASSERT_NO_FATAL_FAILURE(TryUsingRelro(kLibName));
+ ASSERT_NO_FATAL_FAILURE(TryUsingRelro(kLibName, false));
}
TEST_F(DlExtRelroSharingTest, VerifyMemorySaving) {
@@ -465,7 +542,7 @@
TemporaryFile tf; // Use tf to get an unique filename.
ASSERT_NOERROR(close(tf.fd));
- ASSERT_NO_FATAL_FAILURE(CreateRelroFile(kLibName, tf.path));
+ ASSERT_NO_FATAL_FAILURE(CreateRelroFile(kLibName, tf.path, false));
int pipefd[2];
ASSERT_NOERROR(pipe(pipefd));
@@ -580,6 +657,21 @@
}
}
+std::string DlExtRelroSharingTest::FindMappingName(void* ptr) {
+ uint64_t addr = reinterpret_cast<uint64_t>(ptr);
+ std::string found_name = "<not found>";
+
+ EXPECT_TRUE(android::procinfo::ReadMapFile(
+ "/proc/self/maps",
+ [&](uint64_t start, uint64_t end, uint16_t, uint16_t, ino_t, const char* name) {
+ if (addr >= start && addr < end) {
+ found_name = name;
+ }
+ }));
+
+ return found_name;
+}
+
// Testing namespaces
static const char* g_public_lib = "libnstest_public.so";
diff --git a/tests/libs/Android.mk b/tests/libs/Android.mk
index 5bd028d..c40277d 100644
--- a/tests/libs/Android.mk
+++ b/tests/libs/Android.mk
@@ -49,6 +49,21 @@
include $(LOCAL_PATH)/Android.build.testlib.mk
# -----------------------------------------------------------------------------
+# Library used by dlext tests - recursive use of RELRO sharing
+# -----------------------------------------------------------------------------
+libdlext_test_recursive_src_files := \
+ dlext_test_recursive_library.cpp \
+
+libdlext_test_recursive_ldflags := \
+ -Wl,-z,relro \
+
+libdlext_test_recursive_shared_libraries := libdlext_test
+
+module := libdlext_test_recursive
+module_tag := optional
+include $(LOCAL_PATH)/Android.build.testlib.mk
+
+# -----------------------------------------------------------------------------
# Library used by dlext tests - different name non-default location
# -----------------------------------------------------------------------------
module := libdlext_test_fd
diff --git a/tests/libs/dlext_test_recursive_library.cpp b/tests/libs/dlext_test_recursive_library.cpp
new file mode 100644
index 0000000..b37bd70
--- /dev/null
+++ b/tests/libs/dlext_test_recursive_library.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+extern "C" int getRandomNumber();
+
+class B {
+public:
+ virtual int getBiggerRandomNumber() {
+ // Call to the other library.
+ return getRandomNumber() * 2;
+ }
+
+ virtual ~B() {}
+};
+
+B b;
+
+// nested macros to make it easy to define a large amount of read-only data
+// which will require relocation.
+#define B_16 &b, &b, &b, &b, &b, &b, &b, &b, &b, &b, &b, &b, &b, &b, &b, &b,
+#define B_128 B_16 B_16 B_16 B_16 B_16 B_16 B_16 B_16
+#define B_1024 B_128 B_128 B_128 B_128 B_128 B_128 B_128 B_128
+
+extern "C" B* const lots_more_relro[] = {
+ B_1024 B_1024 B_1024 B_1024 B_1024 B_1024 B_1024 B_1024
+};
+
+extern "C" int getBiggerRandomNumber() {
+ // access the relro section (twice, in fact, once for the pointer, and once
+ // for the vtable of B) to check it's actually there.
+ return lots_more_relro[0]->getBiggerRandomNumber();
+}