blob: 41fc47bc21db257d5fdd49a782acf5759a3d16c5 [file] [log] [blame]
/*
* 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
}