Merge "Enable BTI in bionic linker" am: 7241ac6c07 am: dc0dd76dfb

Original change: https://android-review.googlesource.com/c/platform/bionic/+/1242754

Change-Id: I5c3182ee75ab371af884ec20e2f62f381c95cbf4
This commit is contained in:
Elliott Hughes 2020-09-25 17:16:44 +00:00 committed by Automerger Merge Worker
commit dcf91e88e4
12 changed files with 816 additions and 13 deletions

View file

@ -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_ */

View file

@ -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",
],

View file

@ -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;

View file

@ -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
};

View file

@ -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() {

View file

@ -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();

View file

@ -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.

View file

@ -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

View file

@ -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;
};

View file

@ -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
}

View file

@ -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.

View file

@ -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);