| /* | |
| * 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 <android-base/file.h> | |
| #include <android-base/unique_fd.h> | |
| #include <gtest/gtest.h> | |
| #include "linker.h" | |
| #include "linker_globals.h" | |
| #include "linker_note_gnu_property.h" | |
| #include "linker_phdr.h" | |
| #include "platform/bionic/macros.h" | |
| using ::android::base::GetExecutableDirectory; | |
| using ::android::base::unique_fd; | |
| #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)*>(§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 <typename T> | |
| bool push(ElfW(Word) pr_type, ElfW(Word) pr_datasz, const T* pr_data) { | |
| // Must be aligned. | |
| const uintptr_t addition = __builtin_align_up(pr_datasz, sizeof(ElfW(Addr))); | |
| if ((offset() + addition) > kMaxSectionSize) { | |
| return false; | |
| } | |
| ++entries; | |
| ElfW(Prop)* prop = reinterpret_cast<ElfW(Prop)*>(§ion[offset()]); | |
| // Header | |
| prop->pr_type = pr_type; | |
| prop->pr_datasz = pr_datasz; | |
| step(2 * sizeof(ElfW(Word))); | |
| // Data | |
| memcpy(§ion[offset()], reinterpret_cast<const void*>(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 end = note->nhdr.n_descsz + 16; | |
| for (uintptr_t offset = 16; offset < end; ++offset) { | |
| std::cout << std::hex; | |
| if ((offset % 8) == 0) { | |
| std::cout << "\n "; | |
| } | |
| auto value = static_cast<unsigned>(section[offset]); | |
| std::cout << " "; | |
| if (value < 0x10) { | |
| std::cout << "0"; | |
| } | |
| std::cout << static_cast<unsigned>(section[offset]); | |
| } | |
| 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 | |
| } | |
| // Test that .note.gnu.property is properly loaded from a binary with a non-zero image base. | |
| TEST(note_gnu_property, find_gnu_property_offset_image_base) { | |
| #if defined(__aarch64__) | |
| std::string path = GetExecutableDirectory() + "/note_gnu_property.so"; | |
| unique_fd fd{TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC))}; | |
| ASSERT_GE(fd.get(), 0) << "Failed to open " << path << ": " << strerror(errno); | |
| struct stat file_stat; | |
| ASSERT_NE(TEMP_FAILURE_RETRY(fstat(fd.get(), &file_stat)), -1) | |
| << "Failed to stat " << path << ": " << strerror(errno); | |
| ElfReader elf_reader; | |
| ASSERT_TRUE(elf_reader.Read(path.c_str(), fd.get(), 0, file_stat.st_size)) | |
| << "Failed to read ELF file"; | |
| // Load the binary as if BTI isn't supported so that we can run on any CPU. | |
| auto bti_supported_orig = g_platform_properties.bti_supported; | |
| g_platform_properties.bti_supported = false; | |
| address_space_params address_space; | |
| EXPECT_TRUE(elf_reader.Load(&address_space)) << "Failed to load ELF file"; | |
| // IsBTICompatible() checks for g_platform_properties.bti_supported. | |
| g_platform_properties.bti_supported = true; | |
| EXPECT_TRUE(elf_reader.note_gnu_property()->IsBTICompatible()) << "Binary is not BTI-compatible"; | |
| g_platform_properties.bti_supported = bti_supported_orig; | |
| #else | |
| GTEST_SKIP() << "BTI is not supported on this architecture."; | |
| #endif | |
| } |