aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libc/include/bits/elf_arm64.h5
-rw-r--r--linker/Android.bp3
-rw-r--r--linker/linker.cpp8
-rw-r--r--linker/linker.h6
-rw-r--r--linker/linker_globals.cpp2
-rw-r--r--linker/linker_globals.h3
-rw-r--r--linker/linker_main.cpp24
-rw-r--r--linker/linker_note_gnu_property.cpp186
-rw-r--r--linker/linker_note_gnu_property.h93
-rw-r--r--linker/linker_note_gnu_property_test.cpp435
-rw-r--r--linker/linker_phdr.cpp53
-rw-r--r--linker/linker_phdr.h11
12 files changed, 816 insertions, 13 deletions
diff --git a/libc/include/bits/elf_arm64.h b/libc/include/bits/elf_arm64.h
index 6bb8384e4..9330d7bdb 100644
--- a/libc/include/bits/elf_arm64.h
+++ b/libc/include/bits/elf_arm64.h
@@ -89,4 +89,9 @@
#define R_AARCH64_TLSDESC 1031 /* 16-byte descriptor: resolver func + arg. */
#define R_AARCH64_IRELATIVE 1032
+/* Dynamic array tags */
+#define DT_AARCH64_BTI_PLT 0x70000001
+#define DT_AARCH64_PAC_PLT 0x70000003
+#define DT_AARCH64_VARIANT_PCS 0x70000005
+
#endif /* _AARCH64_ELF_MACHDEP_H_ */
diff --git a/linker/Android.bp b/linker/Android.bp
index a51b73fe4..6c59cffdd 100644
--- a/linker/Android.bp
+++ b/linker/Android.bp
@@ -169,6 +169,7 @@ filegroup {
"linker_namespaces.cpp",
"linker_logger.cpp",
"linker_mapped_file_fragment.cpp",
+ "linker_note_gnu_property.cpp",
"linker_phdr.cpp",
"linker_relocate.cpp",
"linker_sdk_versions.cpp",
@@ -447,6 +448,7 @@ cc_test {
"linker_block_allocator_test.cpp",
"linker_config_test.cpp",
"linked_list_test.cpp",
+ "linker_note_gnu_property_test.cpp",
"linker_sleb128_test.cpp",
"linker_utils_test.cpp",
"linker_gnu_hash_test.cpp",
@@ -455,6 +457,7 @@ cc_test {
"linker_block_allocator.cpp",
"linker_config.cpp",
"linker_debug.cpp",
+ "linker_note_gnu_property.cpp",
"linker_test_globals.cpp",
"linker_utils.cpp",
],
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 302e4b3f9..77f754cf4 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -3141,6 +3141,14 @@ bool soinfo::prelink_image() {
// resolves everything eagerly, so these can be ignored.
break;
+#if defined(__aarch64__)
+ case DT_AARCH64_BTI_PLT:
+ case DT_AARCH64_PAC_PLT:
+ case DT_AARCH64_VARIANT_PCS:
+ // Ignored: AArch64 processor-specific dynamic array tags.
+ break;
+#endif
+
default:
if (!relocating_linker) {
const char* tag_name;
diff --git a/linker/linker.h b/linker/linker.h
index 3e851daf9..e1775fb66 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -181,3 +181,9 @@ struct address_space_params {
int get_application_target_sdk_version();
ElfW(Versym) find_verdef_version_index(const soinfo* si, const version_info* vi);
bool validate_verdef_section(const soinfo* si);
+
+struct platform_properties {
+#if defined(__aarch64__)
+ bool bti_supported = false;
+#endif
+};
diff --git a/linker/linker_globals.cpp b/linker/linker_globals.cpp
index 31da02cfe..4a17d0918 100644
--- a/linker/linker_globals.cpp
+++ b/linker/linker_globals.cpp
@@ -40,6 +40,8 @@ android_namespace_t g_default_namespace;
std::unordered_map<uintptr_t, soinfo*> g_soinfo_handles_map;
+platform_properties g_platform_properties;
+
static char __linker_dl_err_buf[768];
char* linker_get_error_buffer() {
diff --git a/linker/linker_globals.h b/linker/linker_globals.h
index 83cedcab4..09986290c 100644
--- a/linker/linker_globals.h
+++ b/linker/linker_globals.h
@@ -79,11 +79,14 @@ extern char** g_envp;
struct soinfo;
struct android_namespace_t;
+struct platform_properties;
extern android_namespace_t g_default_namespace;
extern std::unordered_map<uintptr_t, soinfo*> g_soinfo_handles_map;
+extern platform_properties g_platform_properties;
+
// Error buffer "variable"
char* linker_get_error_buffer();
size_t linker_get_error_buffer_size();
diff --git a/linker/linker_main.cpp b/linker/linker_main.cpp
index 41bb4baee..aad8f6f6f 100644
--- a/linker/linker_main.cpp
+++ b/linker/linker_main.cpp
@@ -297,6 +297,13 @@ static ExecutableInfo load_executable(const char* orig_path) {
return result;
}
+static void platform_properties_init() {
+#if defined(__aarch64__)
+ const unsigned long hwcap2 = getauxval(AT_HWCAP2);
+ g_platform_properties.bti_supported = (hwcap2 & HWCAP2_BTI) != 0;
+#endif
+}
+
static ElfW(Addr) linker_main(KernelArgumentBlock& args, const char* exe_to_load) {
ProtectedDataGuard guard;
@@ -311,6 +318,9 @@ static ElfW(Addr) linker_main(KernelArgumentBlock& args, const char* exe_to_load
// Initialize system properties
__system_properties_init(); // may use 'environ'
+ // Initialize platform properties.
+ platform_properties_init();
+
// Register the debuggerd signal handler.
linker_debuggerd_init();
@@ -381,6 +391,20 @@ static ElfW(Addr) linker_main(KernelArgumentBlock& args, const char* exe_to_load
solinker->set_realpath(interp);
init_link_map_head(*solinker);
+#if defined(__aarch64__)
+ if (exe_to_load == nullptr) {
+ // Kernel does not add PROT_BTI to executable pages of the loaded ELF.
+ // Apply appropriate protections here if it is needed.
+ auto note_gnu_property = GnuPropertySection(somain);
+ if (note_gnu_property.IsBTICompatible() &&
+ (phdr_table_protect_segments(somain->phdr, somain->phnum, somain->load_bias,
+ &note_gnu_property) < 0)) {
+ __linker_error("error: can't protect segments for \"%s\": %s", exe_info.path.c_str(),
+ strerror(errno));
+ }
+ }
+#endif
+
// Register the main executable and the linker upfront to have
// gdb aware of them before loading the rest of the dependency
// tree.
diff --git a/linker/linker_note_gnu_property.cpp b/linker/linker_note_gnu_property.cpp
new file mode 100644
index 000000000..be1aebc88
--- /dev/null
+++ b/linker/linker_note_gnu_property.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "linker_note_gnu_property.h"
+
+#include <elf.h>
+#include <link.h>
+
+#include "linker.h"
+#include "linker_debug.h"
+#include "linker_globals.h"
+#include "linker_soinfo.h"
+
+GnuPropertySection::GnuPropertySection(const soinfo* si)
+ : GnuPropertySection(si->phdr, si->phnum, si->load_bias, si->get_realpath()) {}
+
+GnuPropertySection::GnuPropertySection(const ElfW(Phdr)* phdr, size_t phdr_count,
+ const ElfW(Addr) load_bias, const char* name) {
+ // Try to find PT_GNU_PROPERTY segment.
+ auto note_gnu_property = FindSegment(phdr, phdr_count, load_bias, name);
+ // Perform some validity checks.
+ if (note_gnu_property && SanityCheck(note_gnu_property, name)) {
+ // Parse section.
+ Parse(note_gnu_property, name);
+ }
+}
+
+const ElfW(NhdrGNUProperty)* GnuPropertySection::FindSegment(const ElfW(Phdr)* phdr,
+ size_t phdr_count,
+ const ElfW(Addr) load_bias,
+ const char* name) const {
+ // According to Linux gABI extension this segment should contain
+ // .note.gnu.property section only.
+ if (phdr != nullptr) {
+ for (size_t i = 0; i < phdr_count; ++i) {
+ if (phdr[i].p_type != PT_GNU_PROPERTY) {
+ continue;
+ }
+
+ TRACE("\"%s\" PT_GNU_PROPERTY: found at segment index %zu", name, i);
+
+ // Check segment size.
+ if (phdr[i].p_memsz < sizeof(ElfW(NhdrGNUProperty))) {
+ DL_ERR_AND_LOG(
+ "\"%s\" PT_GNU_PROPERTY segment is too small. Segment "
+ "size is %zu, minimum is %zu.",
+ name, static_cast<size_t>(phdr[i].p_memsz), sizeof(ElfW(NhdrGNUProperty)));
+ return nullptr;
+ }
+
+ // PT_GNU_PROPERTY contains .note.gnu.property which has SHF_ALLOC
+ // attribute, therefore it is loaded.
+ auto note_nhdr = reinterpret_cast<ElfW(NhdrGNUProperty)*>(load_bias + phdr[i].p_vaddr);
+
+ // Check that the n_descsz <= p_memsz
+ if ((phdr[i].p_memsz - sizeof(ElfW(NhdrGNUProperty))) < note_nhdr->nhdr.n_descsz) {
+ DL_ERR_AND_LOG(
+ "\"%s\" PT_GNU_PROPERTY segment p_memsz (%zu) is too small for note n_descsz (%zu).",
+ name, static_cast<size_t>(phdr[i].p_memsz),
+ static_cast<size_t>(note_nhdr->nhdr.n_descsz));
+ return nullptr;
+ }
+
+ return note_nhdr;
+ }
+ }
+
+ TRACE("\"%s\" PT_GNU_PROPERTY: not found", name);
+ return nullptr;
+}
+
+bool GnuPropertySection::SanityCheck(const ElfW(NhdrGNUProperty)* note_nhdr,
+ const char* name) const {
+ // Check .note section type
+ if (note_nhdr->nhdr.n_type != NT_GNU_PROPERTY_TYPE_0) {
+ DL_ERR_AND_LOG("\"%s\" .note.gnu.property: unexpected note type. Expected %u, got %u.", name,
+ NT_GNU_PROPERTY_TYPE_0, note_nhdr->nhdr.n_type);
+ return false;
+ }
+
+ if (note_nhdr->nhdr.n_namesz != 4) {
+ DL_ERR_AND_LOG("\"%s\" .note.gnu.property: unexpected name size. Expected 4, got %u.", name,
+ note_nhdr->nhdr.n_namesz);
+ return false;
+ }
+
+ if (strncmp(note_nhdr->n_name, "GNU", 4) != 0) {
+ DL_ERR_AND_LOG("\"%s\" .note.gnu.property: unexpected name. Expected 'GNU', got '%s'.", name,
+ note_nhdr->n_name);
+ return false;
+ }
+
+ return true;
+}
+
+bool GnuPropertySection::Parse(const ElfW(NhdrGNUProperty)* note_nhdr, const char* name) {
+ // The total length of the program property array is in _bytes_.
+ ElfW(Word) offset = 0;
+ while (offset < note_nhdr->nhdr.n_descsz) {
+ DEBUG("\"%s\" .note.gnu.property: processing at offset 0x%x", name, offset);
+
+ // At least the "header" part must fit.
+ // The ABI doesn't say that pr_datasz can't be 0.
+ if ((note_nhdr->nhdr.n_descsz - offset) < sizeof(ElfW(Prop))) {
+ DL_ERR_AND_LOG(
+ "\"%s\" .note.gnu.property: no more space left for a "
+ "Program Property Note header.",
+ name);
+ return false;
+ }
+
+ // Loop on program property array.
+ const ElfW(Prop)* property = reinterpret_cast<const ElfW(Prop)*>(&note_nhdr->n_desc[offset]);
+ const ElfW(Word) property_size =
+ align_up(sizeof(ElfW(Prop)) + property->pr_datasz, sizeof(ElfW(Addr)));
+ if ((note_nhdr->nhdr.n_descsz - offset) < property_size) {
+ DL_ERR_AND_LOG(
+ "\"%s\" .note.gnu.property: property descriptor size is "
+ "invalid. Expected at least %u bytes, got %u.",
+ name, property_size, note_nhdr->nhdr.n_descsz - offset);
+ return false;
+ }
+
+ // Cache found properties.
+ switch (property->pr_type) {
+#if defined(__aarch64__)
+ case GNU_PROPERTY_AARCH64_FEATURE_1_AND: {
+ if (property->pr_datasz != 4) {
+ DL_ERR_AND_LOG(
+ "\"%s\" .note.gnu.property: property descriptor size is "
+ "invalid. Expected %u bytes for GNU_PROPERTY_AARCH64_FEATURE_1_AND, got %u.",
+ name, 4, property->pr_datasz);
+ return false;
+ }
+
+ const ElfW(Word) flags = *reinterpret_cast<const ElfW(Word)*>(&property->pr_data[0]);
+ properties_.bti_compatible = (flags & GNU_PROPERTY_AARCH64_FEATURE_1_BTI) != 0;
+ if (properties_.bti_compatible) {
+ INFO("[ BTI compatible: \"%s\" ]", name);
+ }
+ break;
+ }
+#endif
+ default:
+ DEBUG("\"%s\" .note.gnu.property: found property pr_type %u pr_datasz 0x%x", name,
+ property->pr_type, property->pr_datasz);
+ break;
+ }
+
+ // Move offset, this should be safe to add because of previous checks.
+ offset += property_size;
+ }
+
+ return true;
+}
+
+#if defined(__aarch64__)
+bool GnuPropertySection::IsBTICompatible() const {
+ return (g_platform_properties.bti_supported && properties_.bti_compatible);
+}
+#endif
diff --git a/linker/linker_note_gnu_property.h b/linker/linker_note_gnu_property.h
new file mode 100644
index 000000000..b8b4ef792
--- /dev/null
+++ b/linker/linker_note_gnu_property.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <elf.h>
+#include <link.h>
+
+#include "linker_soinfo.h"
+
+// The Elf* structures below are derived from the document
+// Linux Extensions to gABI (https://github.com/hjl-tools/linux-abi/wiki).
+// Essentially, these types would be defined in <elf.h>, but this is not
+// the case at the moment.
+
+struct Elf32_Prop {
+ Elf32_Word pr_type;
+ Elf32_Word pr_datasz;
+ char pr_data[0];
+};
+
+// On 32-bit machines this should be 4-byte aligned.
+struct Elf32_NhdrGNUProperty {
+ Elf32_Nhdr nhdr;
+ char n_name[4];
+ char n_desc[0];
+};
+
+struct Elf64_Prop {
+ Elf64_Word pr_type;
+ Elf64_Word pr_datasz;
+ char pr_data[0];
+};
+
+// On 64-bit machines this should be 8-byte aligned.
+struct Elf64_NhdrGNUProperty {
+ Elf64_Nhdr nhdr;
+ char n_name[4];
+ char n_desc[0];
+};
+
+struct ElfProgramProperty {
+#if defined(__aarch64__)
+ bool bti_compatible = false;
+#endif
+};
+
+// Representation of the .note.gnu.property section found in the segment
+// with p_type = PT_GNU_PROPERTY.
+class GnuPropertySection {
+ public:
+ GnuPropertySection(){};
+ explicit GnuPropertySection(const soinfo* si);
+ GnuPropertySection(const ElfW(Phdr)* phdr, size_t phdr_count, const ElfW(Addr) load_bias,
+ const char* name);
+
+#if defined(__aarch64__)
+ bool IsBTICompatible() const;
+#endif
+
+ private:
+ const ElfW(NhdrGNUProperty)* FindSegment(const ElfW(Phdr)* phdr, size_t phdr_count,
+ const ElfW(Addr) load_bias, const char* name) const;
+ bool SanityCheck(const ElfW(NhdrGNUProperty)* note_nhdr, const char* name) const;
+ bool Parse(const ElfW(NhdrGNUProperty)* note_nhdr, const char* name);
+
+ ElfProgramProperty properties_ __unused;
+};
diff --git a/linker/linker_note_gnu_property_test.cpp b/linker/linker_note_gnu_property_test.cpp
new file mode 100644
index 000000000..41fc47bc2
--- /dev/null
+++ b/linker/linker_note_gnu_property_test.cpp
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "linker.h"
+#include "linker_globals.h"
+#include "linker_note_gnu_property.h"
+#include "platform/bionic/macros.h"
+
+#define SONAME "test_so"
+
+static char error_buffer[1024];
+
+char* linker_get_error_buffer() {
+ return error_buffer;
+}
+
+size_t linker_get_error_buffer_size() {
+ return std::size(error_buffer);
+}
+
+static void reset_error_buffer() {
+ error_buffer[0] = '\0';
+}
+
+platform_properties g_platform_properties {
+#if defined(__aarch64__)
+ // Assume "hardware" supports Armv8.5-A BTI.
+ .bti_supported = true
+#endif
+};
+
+// Helper macro to make the test cleaner.
+#define PHDR_WITH_NOTE_GNU_PROPERTY(__prop) \
+ reset_error_buffer(); \
+ ElfW(Phdr) phdrs[] = { \
+ {.p_type = PT_LOAD}, \
+ { \
+ .p_type = PT_GNU_PROPERTY, \
+ .p_vaddr = reinterpret_cast<ElfW(Addr)>(__prop), \
+ .p_memsz = sizeof(ElfW(NhdrGNUProperty)) + (__prop)->nhdr.n_descsz, \
+ }, \
+ {.p_type = PT_NULL}, \
+ }; \
+ auto note = GnuPropertySection(&phdrs[0], std::size(phdrs), 0, SONAME)
+
+// Helper to check for no error message.
+#define ASSERT_NO_ERROR_MSG() ASSERT_STREQ(error_buffer, "")
+
+// Helper to check expected error message.
+#define ASSERT_ERROR_MSG_EQ(__expected) ASSERT_STREQ(error_buffer, "\"" SONAME "\" " __expected)
+
+static void test_bti_not_supported(GnuPropertySection& note __unused) {
+#if defined(__aarch64__)
+ ASSERT_FALSE(note.IsBTICompatible());
+#endif
+}
+
+#if defined(__aarch64__)
+static void test_bti_supported(GnuPropertySection& note __unused) {
+ ASSERT_TRUE(note.IsBTICompatible());
+}
+#endif
+
+// Helper class to build a well-formed .note.gnu.property section.
+class GnuPropertySectionBuilder {
+ public:
+ GnuPropertySectionBuilder() {
+ note = reinterpret_cast<ElfW(NhdrGNUProperty)*>(&section[0]);
+ note->nhdr.n_namesz = 4;
+ note->nhdr.n_descsz = 0;
+ note->nhdr.n_type = NT_GNU_PROPERTY_TYPE_0;
+ memcpy(note->n_name, "GNU", 4);
+ }
+
+ template <typename T>
+ bool push(ElfW(Word) pr_type, ElfW(Word) pr_datasz, const T* pr_data) {
+ // Must be aligned.
+ const uintptr_t addition = align_up(pr_datasz, sizeof(ElfW(Addr)));
+ if ((offset() + addition) > kMaxSectionSize) {
+ return false;
+ }
+ ++entries;
+ ElfW(Prop)* prop = reinterpret_cast<ElfW(Prop)*>(&section[offset()]);
+ // Header
+ prop->pr_type = pr_type;
+ prop->pr_datasz = pr_datasz;
+ step(2 * sizeof(ElfW(Word)));
+ // Data
+ memcpy(&section[offset()], reinterpret_cast<const void*>(pr_data), pr_datasz);
+ step(pr_datasz);
+ // Padding
+ memset(&section[offset()], 0xAA, addition - pr_datasz);
+ step(addition - pr_datasz);
+ return true;
+ }
+
+ ElfW(NhdrGNUProperty)* data() const { return note; }
+
+ void dump() const {
+ std::cout << ".note.gnu.property\n";
+ dump_member("n_namesz", note->nhdr.n_namesz);
+ dump_member("n_descsz", note->nhdr.n_descsz);
+ dump_member("n_type ", note->nhdr.n_type);
+ dump_member("n_name ", note->n_name);
+ dump_member("entries ", entries);
+ if (entries > 0) {
+ std::cout << " raw data:";
+ const uintptr_t offset = note->nhdr.n_descsz + 16;
+ for (uintptr_t offs = 16; offs < offset; ++offs) {
+ std::cout << std::hex;
+ if ((offs % 8) == 0) {
+ std::cout << "\n ";
+ }
+ auto value = static_cast<unsigned>(section[offs]);
+ std::cout << " ";
+ if (value < 0x10) {
+ std::cout << "0";
+ }
+ std::cout << static_cast<unsigned>(section[offs]);
+ }
+ std::cout << std::dec << "\n";
+ }
+ }
+
+ void corrupt_n_descsz(ElfW(Word) n_descsz) { note->nhdr.n_descsz = n_descsz; }
+
+ private:
+ template <typename T>
+ void dump_member(const char* name, T value) const {
+ std::cout << " " << name << " " << value << "\n";
+ }
+
+ ElfW(Word) offset() const { return note->nhdr.n_descsz + 16; }
+
+ template <typename T>
+ void step(T value) {
+ note->nhdr.n_descsz += static_cast<ElfW(Word)>(value);
+ }
+
+ static const size_t kMaxSectionSize = 1024;
+
+ alignas(8) uint8_t section[kMaxSectionSize];
+ ElfW(NhdrGNUProperty)* note;
+ size_t entries = 0;
+};
+
+// Tests that the default constructed instance does not report support
+// for Armv8.5-A BTI.
+TEST(note_gnu_property, default) {
+ GnuPropertySection note;
+ test_bti_not_supported(note);
+ ASSERT_NO_ERROR_MSG();
+}
+
+// Tests that an instance without valid phdr pointer does not report
+// support for Armv8.5-A BTI.
+TEST(note_gnu_property, phdr_null) {
+ auto note = GnuPropertySection(nullptr, 0, 0, SONAME);
+ test_bti_not_supported(note);
+ ASSERT_NO_ERROR_MSG();
+}
+
+// Tests that an instance without finding PT_GNU_PROPERTY does not
+// report support for Armv8.5-A BTI.
+TEST(note_gnu_property, no_pt_gnu_property) {
+ ElfW(Phdr) phdrs[] = {
+ {.p_type = PT_LOAD},
+ {.p_type = PT_NULL},
+ };
+
+ reset_error_buffer();
+ auto note = GnuPropertySection(&phdrs[0], std::size(phdrs), 0, SONAME);
+ test_bti_not_supported(note);
+ ASSERT_NO_ERROR_MSG();
+}
+
+// Tests the validity check for invalid PT_GNU_PROPERTY size.
+TEST(note_gnu_property, pt_gnu_property_bad_size) {
+ ElfW(Phdr) phdrs[] = {
+ {.p_type = PT_LOAD},
+ {
+ .p_type = PT_GNU_PROPERTY,
+ .p_vaddr = 0,
+ .p_memsz = sizeof(ElfW(NhdrGNUProperty)) - 1, // Invalid
+ },
+ {.p_type = PT_NULL},
+ };
+
+ reset_error_buffer();
+ auto note = GnuPropertySection(&phdrs[0], std::size(phdrs), 0, SONAME);
+ test_bti_not_supported(note);
+ ASSERT_ERROR_MSG_EQ("PT_GNU_PROPERTY segment is too small. Segment size is 15, minimum is 16.");
+}
+
+// Tests that advertised n_descsz should still fit into p_memsz.
+TEST(note_gnu_property, pt_gnu_property_too_small) {
+ ElfW(NhdrGNUProperty) prop = {
+ .nhdr = {.n_namesz = PT_GNU_PROPERTY, .n_descsz = 1, .n_type = NT_GNU_PROPERTY_TYPE_0},
+ .n_name = "GNU",
+ };
+ ElfW(Phdr) phdrs[] = {
+ {
+ .p_type = PT_GNU_PROPERTY,
+ .p_vaddr = reinterpret_cast<ElfW(Addr)>(&prop),
+ .p_memsz = sizeof(ElfW(NhdrGNUProperty)), // Off by one
+ },
+ };
+
+ reset_error_buffer();
+ auto note = GnuPropertySection(&phdrs[0], std::size(phdrs), 0, SONAME);
+ test_bti_not_supported(note);
+ ASSERT_ERROR_MSG_EQ("PT_GNU_PROPERTY segment p_memsz (16) is too small for note n_descsz (1).");
+}
+
+// Tests the validity check for invalid .note.gnu.property type.
+TEST(note_gnu_property, pt_gnu_property_bad_type) {
+ ElfW(NhdrGNUProperty) prop = {
+ .nhdr =
+ {
+ .n_namesz = 4,
+ .n_descsz = 0,
+ .n_type = NT_GNU_PROPERTY_TYPE_0 - 1 // Invalid
+ },
+ .n_name = "GNU",
+ };
+ PHDR_WITH_NOTE_GNU_PROPERTY(&prop);
+ test_bti_not_supported(note);
+ ASSERT_ERROR_MSG_EQ(".note.gnu.property: unexpected note type. Expected 5, got 4.");
+}
+
+// Tests the validity check for invalid .note.gnu.property name size.
+TEST(note_gnu_property, pt_gnu_property_bad_namesz) {
+ ElfW(NhdrGNUProperty) prop = {
+ .nhdr = {.n_namesz = 3, // Invalid
+ .n_descsz = 0,
+ .n_type = NT_GNU_PROPERTY_TYPE_0},
+ .n_name = "GNU",
+ };
+ PHDR_WITH_NOTE_GNU_PROPERTY(&prop);
+ test_bti_not_supported(note);
+ ASSERT_ERROR_MSG_EQ(".note.gnu.property: unexpected name size. Expected 4, got 3.");
+}
+
+// Tests the validity check for invalid .note.gnu.property name.
+TEST(note_gnu_property, pt_gnu_property_bad_name) {
+ ElfW(NhdrGNUProperty) prop = {
+ .nhdr = {.n_namesz = 4, .n_descsz = 0, .n_type = NT_GNU_PROPERTY_TYPE_0},
+ .n_name = "ABC", // Invalid
+ };
+ PHDR_WITH_NOTE_GNU_PROPERTY(&prop);
+ test_bti_not_supported(note);
+ ASSERT_ERROR_MSG_EQ(".note.gnu.property: unexpected name. Expected 'GNU', got 'ABC'.");
+}
+
+// Tests the validity check for not enough space for a Program Property header.
+TEST(note_gnu_property, pt_gnu_property_pphdr_no_space) {
+ ElfW(NhdrGNUProperty) prop = {
+ .nhdr = {.n_namesz = 4,
+ .n_descsz = 7, // Invalid
+ .n_type = NT_GNU_PROPERTY_TYPE_0},
+ .n_name = "GNU",
+ };
+ PHDR_WITH_NOTE_GNU_PROPERTY(&prop);
+ test_bti_not_supported(note);
+ ASSERT_ERROR_MSG_EQ(".note.gnu.property: no more space left for a Program Property Note header.");
+}
+
+// Tests an empty .note.gnu.property.
+TEST(note_gnu_property, pt_gnu_property_no_data) {
+ GnuPropertySectionBuilder prop;
+ PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+ test_bti_not_supported(note);
+ ASSERT_NO_ERROR_MSG();
+}
+
+// Tests a .note.gnu.property section with elements with pr_datasz = 0.
+TEST(note_gnu_property, pt_gnu_property_no_prop) {
+ GnuPropertySectionBuilder prop;
+ ASSERT_TRUE(prop.push(1, 0, (void*)nullptr));
+ ASSERT_TRUE(prop.push(2, 0, (void*)nullptr));
+ ASSERT_TRUE(prop.push(3, 0, (void*)nullptr));
+ PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+ test_bti_not_supported(note);
+ ASSERT_NO_ERROR_MSG();
+}
+
+// Tests that GNU_PROPERTY_AARCH64_FEATURE_1_AND must have pr_datasz = 4.
+TEST(note_gnu_property, pt_gnu_property_bad_pr_datasz) {
+#if defined(__aarch64__)
+ GnuPropertySectionBuilder prop;
+ ElfW(Word) pr_data[] = {GNU_PROPERTY_AARCH64_FEATURE_1_BTI, 0, 0};
+ ASSERT_TRUE(prop.push(GNU_PROPERTY_AARCH64_FEATURE_1_AND, 12, &pr_data));
+ PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+ test_bti_not_supported(note);
+ ASSERT_ERROR_MSG_EQ(
+ ".note.gnu.property: property descriptor size is invalid. Expected 4 bytes for "
+ "GNU_PROPERTY_AARCH64_FEATURE_1_AND, got 12.");
+#else
+ GTEST_SKIP() << "BTI is not supported on this architecture.";
+#endif
+}
+
+// Tests a .note.gnu.property section with only GNU_PROPERTY_AARCH64_FEATURE_1_BTI property array.
+TEST(note_gnu_property, pt_gnu_property_ok_1) {
+#if defined(__aarch64__)
+ GnuPropertySectionBuilder prop;
+ ElfW(Word) pr_data[] = {GNU_PROPERTY_AARCH64_FEATURE_1_BTI};
+ ASSERT_TRUE(prop.push(GNU_PROPERTY_AARCH64_FEATURE_1_AND, sizeof(pr_data), &pr_data));
+ PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+ ASSERT_NO_ERROR_MSG();
+ test_bti_supported(note);
+#else
+ GTEST_SKIP() << "BTI is not supported on this architecture.";
+#endif
+}
+
+// Tests a .note.gnu.property section with only GNU_PROPERTY_AARCH64_FEATURE_1_BTI property array.
+TEST(note_gnu_property, pt_gnu_property_ok_2) {
+#if defined(__aarch64__)
+ GnuPropertySectionBuilder prop;
+ ElfW(Word) pr_data[] = {static_cast<ElfW(Word)>(~GNU_PROPERTY_AARCH64_FEATURE_1_BTI)};
+ ASSERT_TRUE(prop.push(GNU_PROPERTY_AARCH64_FEATURE_1_AND, sizeof(pr_data), &pr_data));
+ PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+ ASSERT_NO_ERROR_MSG();
+ test_bti_not_supported(note);
+#else
+ GTEST_SKIP() << "BTI is not supported on this architecture.";
+#endif
+}
+
+// Tests a .note.gnu.property section with more property arrays.
+TEST(note_gnu_property, pt_gnu_property_ok_3) {
+#if defined(__aarch64__)
+ GnuPropertySectionBuilder prop;
+
+ ElfW(Word) pr_data_0[8] = {0xCD};
+ ASSERT_TRUE(prop.push(1, 4, &pr_data_0));
+ ASSERT_TRUE(prop.push(2, 3, &pr_data_0));
+ ASSERT_TRUE(prop.push(3, 8, &pr_data_0));
+
+ ElfW(Word) pr_data[] = {GNU_PROPERTY_AARCH64_FEATURE_1_BTI};
+ ASSERT_TRUE(prop.push(GNU_PROPERTY_AARCH64_FEATURE_1_AND, sizeof(pr_data), &pr_data));
+
+ ASSERT_TRUE(prop.push(4, 1, &pr_data_0));
+
+ PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+ ASSERT_NO_ERROR_MSG();
+ test_bti_supported(note);
+#else
+ GTEST_SKIP() << "BTI is not supported on this architecture.";
+#endif
+}
+
+// Tests a .note.gnu.property but with bad property descriptor size.
+TEST(note_gnu_property, pt_gnu_property_bad_n_descsz) {
+#if defined(__aarch64__)
+ GnuPropertySectionBuilder prop;
+ ElfW(Word) pr_data[] = {GNU_PROPERTY_AARCH64_FEATURE_1_BTI};
+ ASSERT_TRUE(prop.push(GNU_PROPERTY_AARCH64_FEATURE_1_AND, sizeof(pr_data), &pr_data));
+
+ ElfW(Word) n_descsz;
+ if (sizeof(ElfW(Addr)) == 4) {
+ n_descsz = 11;
+ } else {
+ n_descsz = 15;
+ }
+
+ prop.corrupt_n_descsz(n_descsz);
+
+ PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+ if (sizeof(ElfW(Addr)) == 4) {
+ ASSERT_ERROR_MSG_EQ(
+ ".note.gnu.property: property descriptor size is invalid. Expected at least 12 bytes, got "
+ "11.");
+ } else {
+ ASSERT_ERROR_MSG_EQ(
+ ".note.gnu.property: property descriptor size is invalid. Expected at least 16 bytes, got "
+ "15.");
+ }
+ test_bti_not_supported(note);
+#else
+ GTEST_SKIP() << "BTI is not supported on this architecture.";
+#endif
+}
+
+// Tests if platform support is missing.
+TEST(note_gnu_property, no_platform_support) {
+#if defined(__aarch64__)
+ auto bti_supported_orig = g_platform_properties.bti_supported;
+ g_platform_properties.bti_supported = false;
+
+ GnuPropertySectionBuilder prop;
+ ElfW(Word) pr_data[] = {GNU_PROPERTY_AARCH64_FEATURE_1_BTI};
+ ASSERT_TRUE(prop.push(GNU_PROPERTY_AARCH64_FEATURE_1_AND, sizeof(pr_data), &pr_data));
+ PHDR_WITH_NOTE_GNU_PROPERTY(prop.data());
+ ASSERT_NO_ERROR_MSG();
+ test_bti_not_supported(note);
+
+ g_platform_properties.bti_supported = bti_supported_orig;
+#else
+ GTEST_SKIP() << "BTI is not supported on this architecture.";
+#endif
+}
diff --git a/linker/linker_phdr.cpp b/linker/linker_phdr.cpp
index 1e8909457..9b1b99ffd 100644
--- a/linker/linker_phdr.cpp
+++ b/linker/linker_phdr.cpp
@@ -169,8 +169,16 @@ bool ElfReader::Load(address_space_params* address_space) {
if (did_load_) {
return true;
}
- if (ReserveAddressSpace(address_space) && LoadSegments() && FindPhdr()) {
+ if (ReserveAddressSpace(address_space) && LoadSegments() && FindPhdr() &&
+ FindGnuPropertySection()) {
did_load_ = true;
+#if defined(__aarch64__)
+ // For Armv8.5-A loaded executable segments may require PROT_BTI.
+ if (note_gnu_property_.IsBTICompatible()) {
+ did_load_ = (phdr_table_protect_segments(phdr_table_, phdr_num_, load_bias_,
+ &note_gnu_property_) == 0);
+ }
+#endif
}
return did_load_;
@@ -748,15 +756,21 @@ static int _phdr_table_set_load_prot(const ElfW(Phdr)* phdr_table, size_t phdr_c
ElfW(Addr) seg_page_start = PAGE_START(phdr->p_vaddr) + load_bias;
ElfW(Addr) seg_page_end = PAGE_END(phdr->p_vaddr + phdr->p_memsz) + load_bias;
- int prot = PFLAGS_TO_PROT(phdr->p_flags);
- if ((extra_prot_flags & PROT_WRITE) != 0) {
+ int prot = PFLAGS_TO_PROT(phdr->p_flags) | extra_prot_flags;
+ if ((prot & PROT_WRITE) != 0) {
// make sure we're never simultaneously writable / executable
prot &= ~PROT_EXEC;
}
+#if defined(__aarch64__)
+ if ((prot & PROT_EXEC) == 0) {
+ // Though it is not specified don't add PROT_BTI if segment is not
+ // executable.
+ prot &= ~PROT_BTI;
+ }
+#endif
- int ret = mprotect(reinterpret_cast<void*>(seg_page_start),
- seg_page_end - seg_page_start,
- prot | extra_prot_flags);
+ int ret =
+ mprotect(reinterpret_cast<void*>(seg_page_start), seg_page_end - seg_page_start, prot);
if (ret < 0) {
return -1;
}
@@ -768,16 +782,26 @@ static int _phdr_table_set_load_prot(const ElfW(Phdr)* phdr_table, size_t phdr_c
* You should only call this after phdr_table_unprotect_segments and
* applying all relocations.
*
+ * AArch64: also called from linker_main and ElfReader::Load to apply
+ * PROT_BTI for loaded main so and other so-s.
+ *
* Input:
* phdr_table -> program header table
* phdr_count -> number of entries in tables
* load_bias -> load bias
+ * prop -> GnuPropertySection or nullptr
* Return:
* 0 on error, -1 on failure (error code in errno).
*/
-int phdr_table_protect_segments(const ElfW(Phdr)* phdr_table,
- size_t phdr_count, ElfW(Addr) load_bias) {
- return _phdr_table_set_load_prot(phdr_table, phdr_count, load_bias, 0);
+int phdr_table_protect_segments(const ElfW(Phdr)* phdr_table, size_t phdr_count,
+ ElfW(Addr) load_bias, const GnuPropertySection* prop __unused) {
+ int prot = 0;
+#if defined(__aarch64__)
+ if ((prop != nullptr) && prop->IsBTICompatible()) {
+ prot |= PROT_BTI;
+ }
+#endif
+ return _phdr_table_set_load_prot(phdr_table, phdr_count, load_bias, prot);
}
/* Change the protection of all loaded segments in memory to writable.
@@ -1081,7 +1105,7 @@ void phdr_table_get_dynamic_section(const ElfW(Phdr)* phdr_table, size_t phdr_co
* Return:
* pointer to the program interpreter string.
*/
-const char* phdr_table_get_interpreter_name(const ElfW(Phdr) * phdr_table, size_t phdr_count,
+const char* phdr_table_get_interpreter_name(const ElfW(Phdr)* phdr_table, size_t phdr_count,
ElfW(Addr) load_bias) {
for (size_t i = 0; i<phdr_count; ++i) {
const ElfW(Phdr)& phdr = phdr_table[i];
@@ -1124,6 +1148,15 @@ bool ElfReader::FindPhdr() {
return false;
}
+// Tries to find .note.gnu.property section.
+// It is not considered an error if such section is missing.
+bool ElfReader::FindGnuPropertySection() {
+#if defined(__aarch64__)
+ note_gnu_property_ = GnuPropertySection(phdr_table_, phdr_num_, load_start(), name_.c_str());
+#endif
+ return true;
+}
+
// Ensures that our program header is actually within a loadable
// segment. This should help catch badly-formed ELF files that
// would cause the linker to crash later when trying to access it.
diff --git a/linker/linker_phdr.h b/linker/linker_phdr.h
index 4cb48f5d7..548dc51dd 100644
--- a/linker/linker_phdr.h
+++ b/linker/linker_phdr.h
@@ -37,6 +37,7 @@
#include "linker.h"
#include "linker_mapped_file_fragment.h"
+#include "linker_note_gnu_property.h"
class ElfReader {
public:
@@ -67,6 +68,7 @@ class ElfReader {
bool ReserveAddressSpace(address_space_params* address_space);
bool LoadSegments();
bool FindPhdr();
+ bool FindGnuPropertySection();
bool CheckPhdr(ElfW(Addr));
bool CheckFileRange(ElfW(Addr) offset, size_t size, size_t alignment);
@@ -110,13 +112,16 @@ class ElfReader {
// Is map owned by the caller
bool mapped_by_caller_;
+
+ // Only used by AArch64 at the moment.
+ GnuPropertySection note_gnu_property_ __unused;
};
size_t phdr_table_get_load_size(const ElfW(Phdr)* phdr_table, size_t phdr_count,
ElfW(Addr)* min_vaddr = nullptr, ElfW(Addr)* max_vaddr = nullptr);
-int phdr_table_protect_segments(const ElfW(Phdr)* phdr_table,
- size_t phdr_count, ElfW(Addr) load_bias);
+int phdr_table_protect_segments(const ElfW(Phdr)* phdr_table, size_t phdr_count,
+ ElfW(Addr) load_bias, const GnuPropertySection* prop = nullptr);
int phdr_table_unprotect_segments(const ElfW(Phdr)* phdr_table, size_t phdr_count,
ElfW(Addr) load_bias);
@@ -139,5 +144,5 @@ void phdr_table_get_dynamic_section(const ElfW(Phdr)* phdr_table, size_t phdr_co
ElfW(Addr) load_bias, ElfW(Dyn)** dynamic,
ElfW(Word)* dynamic_flags);
-const char* phdr_table_get_interpreter_name(const ElfW(Phdr) * phdr_table, size_t phdr_count,
+const char* phdr_table_get_interpreter_name(const ElfW(Phdr)* phdr_table, size_t phdr_count,
ElfW(Addr) load_bias);