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 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 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, + ¬e_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 +#include + +#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(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(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(phdr[i].p_memsz), + static_cast(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(¬e_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(&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 +#include + +#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 , 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 +#include +#include +#include + +#include + +#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(__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(§ion[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 + 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(§ion[offset()]); + // Header + prop->pr_type = pr_type; + prop->pr_datasz = pr_datasz; + step(2 * sizeof(ElfW(Word))); + // Data + memcpy(§ion[offset()], reinterpret_cast(pr_data), pr_datasz); + step(pr_datasz); + // Padding + memset(§ion[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(section[offs]); + std::cout << " "; + if (value < 0x10) { + std::cout << "0"; + } + std::cout << static_cast(section[offs]); + } + std::cout << std::dec << "\n"; + } + } + + void corrupt_n_descsz(ElfW(Word) n_descsz) { note->nhdr.n_descsz = n_descsz; } + + private: + template + 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 + void step(T value) { + note->nhdr.n_descsz += static_cast(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(&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(~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_, + ¬e_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(seg_page_start), - seg_page_end - seg_page_start, - prot | extra_prot_flags); + int ret = + mprotect(reinterpret_cast(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