From 3e2c0162f7c650a7049e801004e3b8c893b86c3e Mon Sep 17 00:00:00 2001 From: Packit Service Date: Apr 13 2021 22:14:49 +0000 Subject: annobin-9.65 base --- diff --git a/annobin-global.h b/annobin-global.h index 7a43312..ca0eca3 100644 --- a/annobin-global.h +++ b/annobin-global.h @@ -19,7 +19,7 @@ extern "C" { /* The version of the package. NB/ This number is expected to be in the form "Nnn" where "N" is major version number and "nn" is the minor version number. */ -#define ANNOBIN_VERSION 950 +#define ANNOBIN_VERSION 965 /* The version of the annotation specification supported. */ #define SPEC_VERSION 3 @@ -73,6 +73,7 @@ extern "C" { #define ANNOBIN_TOOL_ID_GCC_HOT 'h' #define ANNOBIN_TOOL_ID_GCC_STARTUP 's' #define ANNOBIN_TOOL_ID_GCC_EXIT 'e' +#define ANNOBIN_TOOL_ID_GCC_LTO 'g' /* Values used in GNU .note.gnu.property notes (NT_GNU_PROPERTY_TYPE_0). */ #define GNU_PROPERTY_STACK_SIZE 1 diff --git a/annocheck/annocheck.c b/annocheck/annocheck.c index 7f032ee..77f2c36 100644 --- a/annocheck/annocheck.c +++ b/annocheck/annocheck.c @@ -15,6 +15,7 @@ #include "annobin-global.h" #include "annocheck.h" #include "config.h" +#include #include #include #include @@ -973,7 +974,7 @@ follow_debuglink (annocheck_data * data) #endif /* HAVE_LIBDEBUGINFOD */ /* Failed to find the file. */ - einfo (VERBOSE, "%s: warn: Could not find separate debug file: %s", data->filename, link); + einfo (VERBOSE2, "%s: warn: Could not find separate debug file: %s", data->filename, link); free (canon_dir); free (debugfile); @@ -1088,72 +1089,102 @@ ends_with (const char * string, const char * ending, const size_t end_len) && streq (string + (len - end_len), ending)); } -static const char * -find_symbol_in (Elf * elf, Elf_Scn * sym_sec, ulong addr, Elf64_Shdr * sym_hdr, bool prefer_func) +typedef struct find_symbol_return +{ + const char * name; + uint type; + ulong distance; +} find_symbol_return; + +static bool +find_symbol_in (Elf * elf, Elf_Scn * sym_sec, ulong start, ulong end, Elf64_Shdr * sym_hdr, bool prefer_func, find_symbol_return * data_return) { Elf_Data * sym_data; + if (data_return == NULL) + return false; + if ((sym_data = elf_getdata (sym_sec, NULL)) == NULL) { einfo (VERBOSE2, "No symbol section data"); - return NULL; + return false; } - bool use_sym = false; - bool use_saved = false; - GElf_Sym saved_sym = {0}; - GElf_Sym sym; - unsigned int symndx; + uint best_type = 0; + const char * best_name = NULL; + uint second_best_type = 0; + const char * second_best_name = NULL; + ulong best_distance_so_far = ULONG_MAX; + ulong second_best_distance = ULONG_MAX; + GElf_Sym sym; + uint symndx; for (symndx = 1; gelf_getsym (sym_data, symndx, & sym) != NULL; symndx++) { - /* As of version 3 of the protocol, start symbols might be biased by 2. */ - if (sym.st_value >= addr && sym.st_value <= addr + 2) - { - if (prefer_func && GELF_ST_TYPE (sym.st_info) == STT_FUNC) - { - use_sym = true; - break; - } + const char * name = elf_strptr (elf, sym_hdr->sh_link, sym.st_name); - const char * name = elf_strptr (elf, sym_hdr->sh_link, sym.st_name); - if (ends_with (name, "_end", strlen ("_end"))) - continue; + if (sym.st_value < start || sym.st_value >= end) + continue; - if (ends_with (name, ".end", strlen (".end"))) - continue; + /* Skip annobin symbols. */ + if (GELF_ST_TYPE (sym.st_info) == STT_NOTYPE + && GELF_ST_BIND (sym.st_info) == STB_LOCAL + && GELF_ST_VISIBILITY (sym.st_other) == STV_HIDDEN) + continue; - if (! use_saved) - { - memcpy (& saved_sym, & sym, sizeof sym); - use_saved = true; - } - else - { - /* Save this symbol if it is a better fit than the currently - saved symbol. */ - if (GELF_ST_VISIBILITY (sym.st_other) != STV_HIDDEN - && GELF_ST_TYPE (sym.st_info) != STT_NOTYPE) - memcpy (& saved_sym, & sym, sizeof sym); - } + if (ends_with (name, "_end", strlen ("_end"))) + continue; + + if (ends_with (name, ".end", strlen (".end"))) + continue; + + ulong distance_from_start = sym.st_value - start; + + uint type = GELF_ST_TYPE (sym.st_info); + + if (prefer_func && type != STT_FUNC && type != STT_GNU_IFUNC) + { + if (distance_from_start > second_best_distance) + continue; + second_best_name = name; + second_best_type = type; + second_best_distance = distance_from_start; + } + else + { + if (distance_from_start > best_distance_so_far) + continue; + best_name = name; + best_type = type; + best_distance_so_far = distance_from_start; } } - if (use_sym) - return elf_strptr (elf, sym_hdr->sh_link, sym.st_name); + assert (symndx == sym_hdr->sh_size / sym_hdr->sh_entsize); - if (use_saved) - return elf_strptr (elf, sym_hdr->sh_link, saved_sym.st_name); - - return NULL; + if (best_name != NULL) + { + data_return->name = best_name; + data_return->type = best_type; + data_return->distance = best_distance_so_far; + return true; + } + if (second_best_name != NULL) + { + data_return->name = second_best_name; + data_return->type = second_best_type; + data_return->distance = second_best_distance; + return true; + } + return false; } typedef struct walker_info { - ulong start; - ulong end; - const char ** name; - bool prefer_func; + ulong start; + ulong end; + bool prefer_func; + find_symbol_return * data_return; } walker_info; static bool @@ -1167,8 +1198,8 @@ find_symbol_addr_using_dwarf (annocheck_data * data, Dwarf * dwarf, Dwarf_Die * it might have a symbol table that we can use. */ if (data->elf != dwarf_getelf (dwarf)) { - Elf_Scn * sym_sec = NULL; - Elf * elf = dwarf_getelf (dwarf); + Elf_Scn * sym_sec = NULL; + Elf * elf = dwarf_getelf (dwarf); while ((sym_sec = elf_nextscn (elf, sym_sec)) != NULL) { @@ -1178,18 +1209,21 @@ find_symbol_addr_using_dwarf (annocheck_data * data, Dwarf * dwarf, Dwarf_Die * if ((sym_shdr.sh_type == SHT_SYMTAB) || (sym_shdr.sh_type == SHT_DYNSYM)) { - const char * name; - - name = find_symbol_in (elf, sym_sec, info->start, & sym_shdr, info->prefer_func); - if (name) + if (find_symbol_in (elf, sym_sec, info->start, info->end, & sym_shdr, info->prefer_func, info->data_return)) { - *(info->name) = name; - return false; + if (info->data_return->distance == 0) + return false; /* This means 'stop searching'. */ } } } } + /* If we found a name, even one not at START, then stop searching. + The dwarf data whilst possibly providing a better match, will + not provide any ELF symbol type information. */ + if (info->data_return->name != NULL) + return false; /* This means 'stop searching'. */ + size_t nlines; Dwarf_Lines * lines; @@ -1199,6 +1233,8 @@ find_symbol_addr_using_dwarf (annocheck_data * data, Dwarf * dwarf, Dwarf_Die * { Dwarf_Line * line; size_t indx = 1; + ulong best_distance_so_far = ULONG_MAX; + const char * best_name = NULL; einfo (VERBOSE2, "Scanning %ld lines in the DWARF line table", (unsigned long) nlines); while ((line = dwarf_onesrcline (lines, indx)) != NULL) @@ -1207,17 +1243,28 @@ find_symbol_addr_using_dwarf (annocheck_data * data, Dwarf * dwarf, Dwarf_Die * dwarf_lineaddr (line, & addr); - if (addr >= info->start && addr <= info->end) + if (addr >= info->start && addr < info->end) { - *(info->name) = dwarf_linesrc (line, NULL, NULL); - return false; + ulong distance_from_start = addr - info->start; + if (distance_from_start < best_distance_so_far) + { + best_distance_so_far = distance_from_start; + best_name = dwarf_linesrc (line, NULL, NULL); + } } - ++ indx; } + + if (best_name) + { + info->data_return->name = best_name; + info->data_return->distance = best_distance_so_far; + info->data_return->type = 0; /* No ELF type data in DWARF... */ + return false; /* This means 'stop searching'. */ + } } - return true; + return true; /* This means 'continue searching'. */ } /* Return the name of a symbol most appropriate for address range START..END. @@ -1228,21 +1275,46 @@ annocheck_find_symbol_for_address_range (annocheck_data * data, annocheck_section * sec, ulong start, ulong end, - bool prefer_func) + bool prefer) +{ + return annocheck_get_symbol_name_and_type (data, sec, start, end, prefer, NULL); +} + +/* Return the name of a symbol most appropriate for address START..END. + Returns NULL if no symbol could be found. + If a name is found, and the symbol's ELF type is available, return it in TYPE_RETURN. */ + +const char * +annocheck_get_symbol_name_and_type (annocheck_data * data, + annocheck_section * sec, + ulong start, + ulong end, + bool prefer_func, + uint * type_return) { static const char * previous_result; static ulong previous_start; static ulong previous_end; + static uint previous_type; - const char * name = NULL; Elf64_Shdr sym_shdr; Elf_Scn * sym_sec = NULL; + find_symbol_return data_return; + + if (type_return != NULL) + * type_return = 0; if (start > end) return NULL; + data_return.name = NULL; + if (start == previous_start && end == previous_end) - return previous_result; + { + if (type_return != NULL) + * type_return = previous_type; + return previous_result; + } assert (data != NULL); @@ -1259,25 +1331,27 @@ annocheck_find_symbol_for_address_range (annocheck_data * data, if (sym_shdr.sh_type == SHT_SYMTAB || sym_shdr.sh_type == SHT_DYNSYM) { - name = find_symbol_in (data->elf, sym_sec, start, & sym_shdr, prefer_func); - if (name != NULL) - goto found; - + if (find_symbol_in (data->elf, sym_sec, start, end, & sym_shdr, prefer_func, & data_return)) + { + if (data_return.distance == 0) + goto found; + } } } /* Search for symbol sections. */ sym_sec = NULL; - while ((sym_sec = elf_nextscn (data->elf, sym_sec)) != NULL) { read_section_header (data, sym_sec, & sym_shdr); if ((sym_shdr.sh_type == SHT_SYMTAB) || (sym_shdr.sh_type == SHT_DYNSYM)) { - name = find_symbol_in (data->elf, sym_sec, start, & sym_shdr, prefer_func); - if (name) - goto found; + if (find_symbol_in (data->elf, sym_sec, start, end, & sym_shdr, prefer_func, & data_return)) + { + if (data_return.distance == 0) + goto found; + } } } @@ -1285,16 +1359,21 @@ annocheck_find_symbol_for_address_range (annocheck_data * data, walker_info walker; walker.start = start; walker.end = end; - walker.name = & name; walker.prefer_func = prefer_func; + walker.data_return = & data_return; + annocheck_walk_dwarf (data, find_symbol_addr_using_dwarf, & walker); found: - /* If we have found an ".annobin_" prefixed symbol then skip the prefix. */ - if (name && strncmp (name, ANNOBIN_SYMBOL_PREFIX, strlen (ANNOBIN_SYMBOL_PREFIX)) == 0) - name += strlen (ANNOBIN_SYMBOL_PREFIX); + if (type_return != NULL) + { + * type_return = data_return.type; + previous_type = data_return.type; + } + else + previous_type = 0; - return previous_result = name; + return previous_result = data_return.name; } /* -------------------------------------------------------------------- */ diff --git a/annocheck/annocheck.h b/annocheck/annocheck.h index 5581466..ef23330 100644 --- a/annocheck/annocheck.h +++ b/annocheck/annocheck.h @@ -201,6 +201,12 @@ extern bool annocheck_add_checker (struct checker * CHECKER, uint MAJOR); extern const char * annocheck_find_symbol_for_address_range (annocheck_data * DATA, annocheck_section * SEC, ulong START, ulong ADDR, bool PREFER_FUNC); +/* Return the name of a symbol most appropriate for address START..END. + Returns NULL if no symbol could be found. + If a name is found, and the symbol's ELF type is available, return it in TYPE_RETURN. */ +extern const char * annocheck_get_symbol_name_and_type + (annocheck_data * DATA, annocheck_section * SEC, ulong START, ulong ADDR, bool PREFER_FUNC, uint * TYPE_RETURN); + /* Runs the given CHECKER over the sections and segments in FD. The filename associated with FD is assumed to be EXTRA_FILENAME. the filename associated with the file that prompted the need for these extra checks is ORIGINAL_FILENAME. */ diff --git a/annocheck/built-by.c b/annocheck/built-by.c index 5b4e7ab..9d8c9bc 100644 --- a/annocheck/built-by.c +++ b/annocheck/built-by.c @@ -109,7 +109,7 @@ parse_tool (const char * tool, const char ** program, const char ** version, con { STR_AND_LEN ("GNU C17 "), "gcc" }, /* DW_AT_producer. */ { STR_AND_LEN ("GNU Fortran2008 "), "gfortran" }, /* DW_AT_producer. */ { STR_AND_LEN ("rustc version "), "rust" }, /* DW_AT_producer. */ - { STR_AND_LEN ("Go cmd/compile "), "go" }, /* DW_AT_producer. */ + { STR_AND_LEN ("Go cmd/compile go"), "go" }, /* DW_AT_producer. */ { STR_AND_LEN ("GNU AS "), "as" }, /* DW_AT_producer. */ { STR_AND_LEN ("Guile "), "guile" }, /* DW_AT_producer. */ { STR_AND_LEN ("GHC "), "ghc" }, /* DW_AT_producer. */ @@ -242,7 +242,11 @@ builtby_check_sec (annocheck_data * data, return annocheck_walk_notes (data, sec, builtby_note_walker, (void *) data->filename); if (streq (sec->secname, ".note.go.buildid")) - found (".note.go.buildid", data->filename, "Go cmd/compile ?.?.?"); + /* FIXME: We use a different name for the language here (Go) because the note does + not contain any version information. The DW_AT_producer string does contain + version info, but it is checked after this tesst, and we do not want the found() + function to think that "go" has already been found. */ + found (".note.go.buildid", data->filename, "Go"); return true; /* Allow the search to continue. */ } diff --git a/annocheck/hardened.c b/annocheck/hardened.c index 2a4f632..d41c740 100644 --- a/annocheck/hardened.c +++ b/annocheck/hardened.c @@ -1,5 +1,5 @@ /* Checks the hardened status of the given file. - Copyright (c) 2018 - 2020 Red Hat. + Copyright (c) 2018 - 2021 Red Hat. This is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published @@ -18,40 +18,74 @@ #include "annobin-global.h" #include "annocheck.h" -typedef struct hardened_note_data +#ifndef EM_AARCH64 /* RHEL-6 does not define EM_AARCh64. */ +#define EM_AARCH64 183 /* ARM 64-bit architecture */ +#endif + +#define HARDENED_CHECKER_NAME "Hardened" + +/* Predefined names for all of the sources of information scanned by this checker. */ +#define SOURCE_ANNOBIN_NOTES "annobin notes" +#define SOURCE_DW_AT_LANGUAGE "DW_AT_language string" +#define SOURCE_DW_AT_PRODUCER "DW_AT_producer string" +#define SOURCE_DYNAMIC_SECTION "dynamic section" +#define SOURCE_DYNAMIC_SEGMENT "dynamic segment" +#define SOURCE_ELF_HEADER "ELF header" +#define SOURCE_FINAL_SCAN "final scan" +#define SOURCE_PROPERTY_NOTES "property notes" +#define SOURCE_SECTION_HEADERS "section headers" +#define SOURCE_SEGMENT_CONTENTS "segment contents" +#define SOURCE_SEGMENT_HEADERS "segment headers" +#define SOURCE_STRING_SECTION "string section" + +#define GOLD_COLOUR "\e[33;40m" +#define RED_COLOUR "\x1B[31;47m" +#define DEFAULT_COLOUR "\033[0m" + +typedef struct note_range { ulong start; ulong end; -} hardened_note_data; +} note_range; /* Set by the constructor. */ static bool disabled = false; -/* Can be changed by a command line option. */ +/* Can be changed by command line options. */ static bool ignore_gaps = false; +static bool fixed_format_messages = false; +static bool enable_colour = true; -enum tool -{ - TOOL_UNKNOWN = 0, - TOOL_MIXED, - TOOL_CLANG, - TOOL_FORTRAN, - TOOL_GAS, - TOOL_GCC, - TOOL_GIMPLE, - TOOL_GO, - TOOL_LLVM, - TOOL_RUST -}; +#define FIXED_FORMAT_STRING "%s: test: %s file: %s" + +#define TOOL_UNKNOWN 0 +#define TOOL_CLANG (1 << 0) +#define TOOL_FORTRAN (1 << 1) +#define TOOL_GAS (1 << 2) +#define TOOL_GCC (1 << 3) +#define TOOL_GIMPLE (1 << 4) +#define TOOL_GO (1 << 5) +#define TOOL_LLVM (1 << 6) +#define TOOL_RUST (1 << 7) enum lang { LANG_UNKNOWN = 0, + LANG_ASSEMBLER, LANG_C, LANG_CXX, + LANG_GO, + LANG_RUST, LANG_OTHER }; +enum short_enum_state +{ + SHORT_ENUM_STATE_UNSET = 0, + SHORT_ENUM_STATE_SHORT, + SHORT_ENUM_STATE_LONG +}; + /* The contents of this structure are used on a per-input-file basis. The fields are initialised by start(). */ static struct per_file @@ -59,30 +93,39 @@ static struct per_file Elf64_Half e_type; Elf64_Half e_machine; Elf64_Addr e_entry; - ulong text_section_name_index; + + ulong text_section_name_index; ulong text_section_alignment; + note_range text_section_range; + bool is_little_endian; bool debuginfo_file; - bool compiled_code_seen; bool build_notes_seen; int num_fails; int num_maybes; - unsigned anno_major; - unsigned anno_minor; - unsigned anno_rel; - unsigned run_major; - unsigned run_minor; - unsigned run_rel; + uint anno_major; + uint anno_minor; + uint anno_rel; + uint run_major; + uint run_minor; + uint run_rel; - enum tool tool; - uint tool_version; + uint seen_tools; + uint tool_version; + uint current_tool; + note_range note_data; + + const char * component_name; + uint component_type; + + enum short_enum_state short_enum_state; uint note_source[256]; enum lang lang; - bool has_gimple; - bool warned_producer; + bool gcc_from_comment; + bool warned_asm_not_gcc; bool warned_about_instrumentation; bool warned_version_mismatch; bool warned_command_line; @@ -90,30 +133,44 @@ static struct per_file bool also_written; } per_file; -static hardened_note_data * ranges = NULL; -static unsigned num_allocated_ranges = 0; -static unsigned next_free_range = 0; +/* Extensible array of note ranges */ +static note_range * ranges = NULL; +static uint num_allocated_ranges = 0; +static uint next_free_range = 0; #define RANGE_ALLOC_DELTA 16 /* Array used to store instruction bytes at entry point. Use for verbose reporting when the ENTRY test fails. */ static unsigned char entry_bytes[4]; -/* This structure defines an individual test. */ +/* This structure defines an individual test. + There are two types of test. One uses the annobin notes to check that the correct build time options were used. + The other checks the properties of the binary itself. + The former is dependent upon the tool(s) used to produce the binary and the source language(s) involved. + The latter is independent of the tools, languages and notes. */ + +enum test_state +{ + STATE_UNTESTED = 0, + STATE_PASSED, + STATE_FAILED, + STATE_MAYBE +}; typedef struct test { bool enabled; /* If false then do not run this test. */ - unsigned int num_pass; - unsigned int num_fail; - unsigned int num_maybe; + bool skipped; /* True is a skip message has been issued for this test. */ + bool result_announced; + enum test_state state; const char * name; /* Also used as part of the command line option to disable the test. */ const char * description; /* Used in the --help output to describe the test. */ - void (* show_result)(annocheck_data *, struct test *); } test; enum test_index { + TEST_NOTES = 0, + TEST_BIND_NOW, TEST_BRANCH_PROTECTION, TEST_CF_PROTECTION, @@ -124,7 +181,9 @@ enum test_index TEST_GLIBCXX_ASSERTIONS, TEST_GNU_RELRO, TEST_GNU_STACK, + TEST_GO_REVISION, TEST_LTO, + TEST_ONLY_GO, TEST_OPTIMIZATION, TEST_PIC, TEST_PIE, @@ -143,39 +202,18 @@ enum test_index TEST_MAX }; -static void show_BIND_NOW (annocheck_data *, test *); -static void show_BRANCH_PROTECTION (annocheck_data *, test *); -static void show_CF_PROTECTION (annocheck_data *, test *); -static void show_DYNAMIC_SEGMENT (annocheck_data *, test *); -static void show_DYNAMIC_TAGS (annocheck_data *, test *); -static void show_ENTRY (annocheck_data *, test *); -static void show_FORTIFY (annocheck_data *, test *); -static void show_GLIBCXX_ASSERTIONS (annocheck_data *, test *); -static void show_GNU_RELRO (annocheck_data *, test *); -static void show_GNU_STACK (annocheck_data *, test *); -static void show_LTO (annocheck_data *, test *); -static void show_OPTIMIZATION (annocheck_data *, test *); -static void show_PIC (annocheck_data *, test *); -static void show_PIE (annocheck_data *, test *); -static void show_PROPERTY_NOTE (annocheck_data *, test *); -static void show_RUN_PATH (annocheck_data *, test *); -static void show_RWX_SEG (annocheck_data *, test *); -static void show_SHORT_ENUM (annocheck_data *, test *); -static void show_STACK_CLASH (annocheck_data *, test *); -static void show_STACK_PROT (annocheck_data *, test *); -static void show_STACK_REALIGN (annocheck_data *, test *); -static void show_TEXTREL (annocheck_data *, test *); -static void show_THREADS (annocheck_data *, test *); -static void show_WARNINGS (annocheck_data *, test *); -static void show_WRITEABLE_GOT (annocheck_data *, test *); +#define MIN_GO_REVISION 14 +#define STR(a) #a +#define MIN_GO_REV_STR(a,b,c) a STR(b) c #define TEST(name,upper,description) \ - [ TEST_##upper ] = { true, 0, 0, 0, #name, description, show_ ## upper } + [ TEST_##upper ] = { true, false, false, STATE_UNTESTED, #name, description } /* Array of tests to run. Default to enabling them all. The result field is initialised in the start() function. */ static test tests [TEST_MAX] = { + TEST (notes, NOTES, "Annobin note coverage"), TEST (bind-now, BIND_NOW, "Linked with -Wl,-z,now"), TEST (branch-protection, BRANCH_PROTECTION, "Compiled with -mbranch-protection=bti (AArch64 only, gcc 9+ only"), TEST (cf-protection, CF_PROTECTION, "Compiled with -fcf-protection=all (x86 only, gcc 8+ only)"), @@ -186,7 +224,9 @@ static test tests [TEST_MAX] = TEST (glibcxx-assertions, GLIBCXX_ASSERTIONS, "Compiled with -D_GLIBCXX_ASSERTIONS"), TEST (gnu-relro, GNU_RELRO, "The relocations for the GOT are not writeable"), TEST (gnu-stack, GNU_STACK, "The stack is not executable"), + TEST (go-revision, GO_REVISION, MIN_GO_REV_STR ("GO compiler revision >= ", MIN_GO_REVISION, " (go only)")), TEST (lto, LTO, "Compiled with -flto"), + TEST (only-go, ONLY_GO, "GO is not mixed with other languages. (go only, x86 only)"), TEST (optimization, OPTIMIZATION, "Compiled with at least -O2"), TEST (pic, PIC, "All binaries must be compiled with -fPIC or fPIE"), TEST (pie, PIE, "Executables need to be compiled with -fPIE"), @@ -210,61 +250,85 @@ static bool report_future_fail = true; #endif static inline bool -is_compiler (enum tool tool) +is_C_compiler (uint tool) { - return tool == TOOL_GCC || tool == TOOL_CLANG || tool == TOOL_LLVM || tool == TOOL_GIMPLE; + return (tool & (TOOL_GCC | TOOL_CLANG | TOOL_LLVM | TOOL_GIMPLE)) != 0; } static inline bool -built_by_compiler (void) +includes_assembler (uint mask) { - return is_compiler (per_file.tool); + return mask & TOOL_GAS; } static inline bool -built_by_gcc (void) +includes_gcc (uint mask) { - return per_file.tool == TOOL_GCC; + return mask & TOOL_GCC; } static inline bool -built_by_clang (void) +includes_clang (uint mask) { - return per_file.tool == TOOL_CLANG; + return mask & TOOL_CLANG; } static inline bool -built_by_mixed (void) +includes_gimple (uint mask) { - return per_file.tool == TOOL_MIXED; + return mask & TOOL_GIMPLE; } -static inline bool -built_with_gimple (void) +static void +warn (annocheck_data * data, const char * message) { - return per_file.has_gimple; + if (fixed_format_messages) + return; + einfo (PARTIAL, "%s: %s: ", HARDENED_CHECKER_NAME, data->filename); + if (enable_colour && isatty (1)) + einfo (PARTIAL, RED_COLOUR); + einfo (PARTIAL, "WARN: %s", message); + if (enable_colour && isatty (1)) + einfo (PARTIAL, DEFAULT_COLOUR); + einfo (PARTIAL, "\n"); } -static void -warn (annocheck_data * data, const char * message) +static inline bool +is_x86 (void) { - /* We use the VERBOSE setting rather than WARN because that way - we not get a prefix. */ - einfo (VERBOSE, "%s: WARN: %s", data->filename, message); + return per_file.e_machine == EM_386 || per_file.e_machine == EM_X86_64; } -static void -info (annocheck_data * data, const char * message) +static inline bool +is_executable (void) { - einfo (VERBOSE, "%s: info: %s", data->filename, message); + return per_file.e_type == ET_EXEC || per_file.e_type == ET_DYN; } static bool -skip_check (enum test_index check, const char * component_name) +skip_check (enum test_index check) { if (check < TEST_MAX && ! tests[check].enabled) return true; + /* BZ 1923439: IFuncs are compiled without some of the security + features because they execute in a special enviroment. */ + if (ELF64_ST_TYPE (per_file.component_type) == STT_GNU_IFUNC) + { + switch (check) + { + case TEST_FORTIFY: + case TEST_STACK_CLASH: + case TEST_STACK_PROT: + einfo (VERBOSE2, "skipping test %s for ifunc at %#lx", tests[check].name, per_file.note_data.start); + return true; + default: + break; + } + } + + const char * component_name = per_file.component_name; + if (component_name == NULL) return false; @@ -282,17 +346,23 @@ skip_check (enum test_index check, const char * component_name) const static struct ignore { const char * func_name; - enum test_index test_indicies[4]; + enum test_index test_indicies[6]; } skip_these_funcs[] = { /* We know that some glibc startup functions cannot be compiled with stack protection enabled. So do not complain about them. */ + { "_dl_start", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } }, { "_init", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } }, { "_fini", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } }, { "__libc_csu_init", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } }, { "__libc_csu_fini", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } }, - { "_start", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } }, + { "__libc_init_first", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } }, + { "__libc_start_main", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } }, + { "_start", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_FORTIFY, TEST_PIC, TEST_MAX } }, + { "check_one_fd", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } }, + { "is_dst", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } }, + { "get_common_indices.constprop.0", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } }, /* FIXME: Not sure about these two - they need some tests skipping but I do not think that they were stack tests... */ @@ -310,42 +380,219 @@ skip_check (enum test_index check, const char * component_name) int i; for (i = ARRAY_SIZE (skip_these_funcs); i--;) - if (streq (component_name, skip_these_funcs[i].func_name)) - { - for (i = 0; i < ARRAY_SIZE (skip_these_funcs[0].test_indicies); i++) - if (skip_these_funcs[0].test_indicies[i] == check) + { + if (streq (component_name, skip_these_funcs[i].func_name)) + { + int j; + + for (j = 0; j < ARRAY_SIZE (skip_these_funcs[i].test_indicies); j++) { - if (check < TEST_MAX) - einfo (VERBOSE2, "skipping test %s for component %s", tests[check].name, component_name); - else - einfo (VERBOSE2, "skipping tests of component %s", component_name); - return true; - } + if (skip_these_funcs[i].test_indicies[j] == check) + { + if (check < TEST_MAX) + einfo (VERBOSE2, "skipping test %s for component %s", tests[check].name, component_name); + else + einfo (VERBOSE2, "skipping tests of component %s", component_name); + return true; + } - /* No need to continue searching - we have already matched the name. */ - break; - } + if (skip_these_funcs[i].test_indicies[j] == TEST_MAX) + break; + } + /* No need to continue searching - we have already matched the name. */ + break; + } + } + return false; } +/* Ensure that NAME will not use more than one line. */ static const char * -get_tool_name (enum tool tool) +sanitize_filename (const char * name) { - switch (tool) + const char * n; + + for (n = name; *n != 0; n++) + if (iscntrl (*n)) + break; + if (*n == 0) + return name; + + char * new_name; + char * p; + + p = new_name = xmalloc (strlen (name) + 1); + + for (n = name; *n != 0; n++) + *p++ = iscntrl (*n) ? ' ' : *n; + + *p = 0; + return new_name; +} + +static void +pass (annocheck_data * data, uint testnum, const char * source, const char * reason) +{ + assert (testnum < TEST_MAX); + + if (! tests[testnum].enabled) + return; + + /* If we have already seen a FAIL then do not also report a PASS. */ + if (tests[testnum].state == STATE_FAILED) + return; + + if (tests[testnum].state == STATE_UNTESTED) + tests[testnum].state = STATE_PASSED; + + if (tests[testnum].result_announced) + return; + + tests[testnum].result_announced = true; + + if (fixed_format_messages) + einfo (INFO, FIXED_FORMAT_STRING, "PASS", tests[testnum].name, sanitize_filename (data->filename)); + else { - default: return ""; - case TOOL_UNKNOWN: return ""; - case TOOL_MIXED: return ""; - case TOOL_CLANG: return "Clang"; - case TOOL_FORTRAN: return "Fortran"; - case TOOL_GAS: return "gas"; - case TOOL_GCC: return "GCC"; - case TOOL_GIMPLE: return "gimple"; - case TOOL_GO: return "GO"; - case TOOL_LLVM: return "LLVM"; - case TOOL_RUST: return "Rust"; + if (! BE_VERBOSE) + return; + + einfo (PARTIAL, "%s: %s: ", HARDENED_CHECKER_NAME, data->filename); + einfo (PARTIAL, "PASS: %s test ", tests[testnum].name); + if (reason) + einfo (PARTIAL, "because %s ", reason); + if (BE_VERY_VERBOSE) + einfo (PARTIAL, " (source: %s)\n", source); + else + einfo (PARTIAL, "\n"); + } +} + +static void +skip (annocheck_data * data, uint testnum, const char * source, const char * reason) +{ + assert (testnum < TEST_MAX); + + if (! tests[testnum].enabled) + return; + + if (tests[testnum].state == STATE_UNTESTED) + tests[testnum].state = STATE_MAYBE; /* FIXME - this is to stop final() from complaining that the test was not seen. Maybe use a new state ? */ + + if (tests[testnum].skipped) + return; + + tests[testnum].skipped = true; + + if (fixed_format_messages) + return; + + if (! BE_VERBOSE) + return; + + einfo (PARTIAL, "%s: %s: ", HARDENED_CHECKER_NAME, data->filename); + einfo (PARTIAL, "skip: %s test ", tests[testnum].name); + if (reason) + einfo (PARTIAL, "because %s ", reason); + if (BE_VERY_VERBOSE) + einfo (PARTIAL, " (source: %s)\n", source); + else + einfo (PARTIAL, "\n"); +} + +static void +fail (annocheck_data * data, + uint testnum, + const char * source, + const char * reason) +{ + assert (testnum < TEST_MAX); + + if (! tests[testnum].enabled) + return; + + per_file.num_fails ++; + + if (fixed_format_messages) + einfo (INFO, FIXED_FORMAT_STRING, "FAIL", tests[testnum].name, sanitize_filename (data->filename)); + else if (tests[testnum].state != STATE_FAILED || BE_VERBOSE) + { + einfo (PARTIAL, "%s: %s: ", HARDENED_CHECKER_NAME, data->filename); + if (enable_colour && isatty (1)) + einfo (PARTIAL, RED_COLOUR); + einfo (PARTIAL, "FAIL: %s test ", tests[testnum].name); + if (reason) + einfo (PARTIAL, "because %s ", reason); + + const char * name = per_file.component_name; + if (name && BE_VERBOSE) + { + if (const_strneq (name, "component: ")) + einfo (PARTIAL, "(function: %s) ", name + strlen ("component: ")); + else + einfo (PARTIAL, "(%s) ", name); + } + if (BE_VERY_VERBOSE) + einfo (PARTIAL, "(source: %s)", source); + if (enable_colour && isatty (1)) + einfo (PARTIAL, DEFAULT_COLOUR); + einfo (PARTIAL, "\n"); + } + + tests[testnum].state = STATE_FAILED; +} + +static void +maybe (annocheck_data * data, + uint testnum, + const char * source, + const char * reason) +{ + assert (testnum < TEST_MAX); + + if (! tests[testnum].enabled) + return; + + per_file.num_maybes ++; + + if (fixed_format_messages) + einfo (INFO, FIXED_FORMAT_STRING, "MAYB", tests[testnum].name, sanitize_filename (data->filename)); + else if (tests[testnum].state == STATE_UNTESTED || BE_VERBOSE) + { + einfo (PARTIAL, "%s: %s: ", HARDENED_CHECKER_NAME, data->filename); + if (enable_colour && isatty (1)) + einfo (PARTIAL, GOLD_COLOUR); + einfo (PARTIAL, "MAYB: test: %s ", tests[testnum].name); + if (reason) + einfo (PARTIAL, "because %s ", reason); + if (per_file.component_name) + einfo (PARTIAL, "(function: %s) ", per_file.component_name); + if (BE_VERY_VERBOSE) + einfo (PARTIAL, " (source: %s)", source); + if (enable_colour && isatty (1)) + einfo (PARTIAL, DEFAULT_COLOUR); + einfo (PARTIAL, "\n"); } + + if (tests[testnum].state != STATE_FAILED) + tests[testnum].state = STATE_MAYBE; +} + +static void +info (annocheck_data * data, uint testnum, const char * source, const char * extra) +{ + assert (testnum < TEST_MAX); + + if (! tests[testnum].enabled) + return; + + if (fixed_format_messages) + return; + + einfo (VERBOSE2, "%s: info: %s %s (source %s)", + data->filename, tests[testnum].name, extra, source); } static const char * @@ -355,9 +602,12 @@ get_lang_name (enum lang lang) { default: case LANG_UNKNOWN: return "unknown"; + case LANG_ASSEMBLER: return "Assembler"; case LANG_C: return "C"; case LANG_CXX: return "C++"; case LANG_OTHER: return "other"; + case LANG_GO: return "GO"; + case LANG_RUST: return "Rust"; } } @@ -368,7 +618,7 @@ set_lang (annocheck_data * data, { if (per_file.lang == LANG_UNKNOWN) { - einfo (VERBOSE2, "%s: info: Written in %s (source: %s)", + einfo (VERBOSE2, "%s: info: written in %s (source: %s)", data->filename, get_lang_name (lang), source); per_file.lang = lang; @@ -379,10 +629,18 @@ set_lang (annocheck_data * data, { if (! per_file.also_written) { - einfo (VERBOSE, "%s: ALSO written in %s (source: %s)", + einfo (VERBOSE, "%s: info: ALSO written in %s (source: %s)", data->filename, get_lang_name (lang), source); per_file.also_written = true; } + + if (is_x86 () && (lang == LANG_GO || per_file.lang == LANG_GO)) + { + /* FIXME: This FAIL is only true if CET is not enabled. */ + if (tests[TEST_ONLY_GO].state != STATE_FAILED) + fail (data, TEST_ONLY_GO, source, "combining GO and non-GO object files on x86 systems is not safe - it disables CET"); + } + /* FIXME: What to do ? For now we choose C++ if it is one of the languages, so that the GLIBXX_ASSERTIONS test is enabled. */ if (per_file.lang != LANG_CXX && lang == LANG_CXX) @@ -390,112 +648,124 @@ set_lang (annocheck_data * data, } } -static void -set_producer (annocheck_data * data, - enum tool tool, - unsigned int version, - const char * source) +static const char * +get_tool_name (uint tool) { - einfo (VERBOSE2, "info: Record producer %s version %u source %s", get_tool_name (tool), version, source); - - if (tool == TOOL_GIMPLE) - per_file.has_gimple = true; - - if (per_file.tool == TOOL_UNKNOWN) + switch (tool) { - per_file.tool = tool; - per_file.tool_version = version; - einfo (VERBOSE, "%s: info: Set binary producer to %s version %u", data->filename, get_tool_name (tool), version); + default: return ""; + case TOOL_UNKNOWN: return ""; + case TOOL_CLANG: return "Clang"; + case TOOL_FORTRAN: return "Fortran"; + case TOOL_GAS: return "Gas"; + case TOOL_GCC: return "GCC"; + case TOOL_GIMPLE: return "Gimple"; + case TOOL_GO: return "GO"; + case TOOL_LLVM: return "LLVM"; + case TOOL_RUST: return "Rust"; } - else if (per_file.tool == tool) +} + +#define COMMENT_SECTION "comment section" + +static void +add_producer (annocheck_data * data, + uint tool, + uint version, + const char * source, + bool update_current_tool) +{ + einfo (VERBOSE2, "%s: info: record producer: %s version: %u source: %s", + data->filename, get_tool_name (tool), version, source); + + if (tool == TOOL_GO) { - if (per_file.tool_version != version) + if (version == 0) { - if (! per_file.warned_producer) + if (tests[TEST_GO_REVISION].enabled + && tests[TEST_GO_REVISION].state == STATE_UNTESTED) + maybe (data, TEST_GO_REVISION, source, "unknown revision of the GO compiler used"); + } + else if (version < MIN_GO_REVISION) + { + if (tests[TEST_GO_REVISION].enabled + && tests[TEST_GO_REVISION].state != STATE_FAILED) { - einfo (VERBOSE, "%s: warn: Multiple versions of a tool were used to build this file (%u %u) - using highest version", - data->filename, version, per_file.tool_version); - per_file.warned_producer = true; + fail (data, TEST_GO_REVISION, source, MIN_GO_REV_STR ("GO revision must be >= ", MIN_GO_REVISION, "")); + einfo (VERBOSE, "%s: info: GO compiler revision %u detected in %s", + data->filename, version, source); } - - if (per_file.tool_version < version) - per_file.tool_version = version; } + else + pass (data, TEST_GO_REVISION, source, "GO compiler revision is sufficient"); } - else if ((per_file.tool == TOOL_GAS && is_compiler (tool)) - || (built_by_compiler () && tool == TOOL_GAS)) + + if (update_current_tool) { - if (! per_file.warned_producer) - { - info (data, "Mixed assembler and GCC detected - treating as pure GCC"); - per_file.warned_producer = true; - } - - per_file.tool = TOOL_GCC; + per_file.current_tool = tool; + if (version) + per_file.tool_version = version; } - else if ((per_file.tool == TOOL_GIMPLE && is_compiler (tool)) - || (built_by_compiler () && tool == TOOL_GIMPLE)) + + if (per_file.seen_tools == TOOL_UNKNOWN) { - if (! per_file.warned_producer) + per_file.seen_tools = tool; + per_file.tool_version = version; /* FIXME: Keep track of version numbers on a per-tool basis. */ + if (! fixed_format_messages) { - info (data, "Mixed Gimple and GCC detected - treating as pure GCC"); - per_file.warned_producer = true; + if (version) + einfo (VERBOSE, "%s: info: set binary producer to %s version %u", data->filename, get_tool_name (tool), version); + else + einfo (VERBOSE, "%s: info: set binary producer to %s", data->filename, get_tool_name (tool)); } - per_file.tool = TOOL_GCC; + if (tool == TOOL_GCC) /* FIXME: Update this if glibc ever starts using clang. */ + per_file.gcc_from_comment = streq (source, COMMENT_SECTION); } - else if (! built_by_mixed ()) + else if (per_file.seen_tools & tool) { - if (! per_file.warned_producer) + if (per_file.tool_version != version && version > 0) { - einfo (VERBOSE, "%s: info: This binary was built by more than one tool (%s and %s)", - data->filename, - get_tool_name (per_file.tool), - get_tool_name (tool)); - per_file.warned_producer = true; + if (per_file.tool_version < version) + per_file.tool_version = version; } + } + else + { + per_file.seen_tools |= tool; - if (per_file.tool != TOOL_CLANG && tool != TOOL_CLANG) + /* See BZ 1906171. + Specifically glibc creates some object files by using GCC to assemble hand + written source code and adds the -Wa,--generate-missing-build-notes=yes + option so that there is a note to cover the binary. Since gcc was involved + the .comment section will add_producer(GCC). But since the code is in fact + assembler, the usual GCC command line options will not be present. So when + we see this conflict we choose GAS. */ + if (tool == TOOL_GCC) /* FIXME: Update this if glibc ever starts using clang. */ + per_file.gcc_from_comment = streq (source, COMMENT_SECTION); + else if (tool == TOOL_GAS && per_file.gcc_from_comment) { - per_file.tool = TOOL_MIXED; - per_file.tool_version = 0; + if (! per_file.warned_asm_not_gcc) + { + if (! fixed_format_messages) + einfo (VERBOSE, "%s: info: assembler built by GCC detected - treating as pure assembler", + data->filename); + per_file.warned_asm_not_gcc = true; + } + + per_file.seen_tools &= ~ TOOL_GCC; } - else if (tool == TOOL_CLANG) + + if (! fixed_format_messages) { - per_file.tool = TOOL_CLANG; - per_file.tool_version = version; + if (version) + einfo (VERBOSE, "%s: info: set binary producer to %s version %u", data->filename, get_tool_name (tool), version); + else + einfo (VERBOSE, "%s: info: set binary producer to %s", data->filename, get_tool_name (tool)); } } } -struct tool_string -{ - const char * lead_in; - const char * tool_name; - enum tool tool_id; -}; - -typedef struct tool_id -{ - const char * producer_string; - enum tool tool_type; -} tool_id; - -static const tool_id tools[] = -{ - /* { "GNU C++", TOOL_GXX }, */ - { "GNU C", TOOL_GCC }, - { "GNU Fortran", TOOL_FORTRAN }, - { "rustc version", TOOL_RUST }, - { "clang version", TOOL_CLANG }, - { "clang LLVM", TOOL_CLANG }, /* Is this right ? */ - { "GNU Fortran", TOOL_FORTRAN }, - { "GNU GIMPLE", TOOL_GIMPLE }, - { "Go cmd/compile", TOOL_GO }, - { "GNU AS", TOOL_GAS }, - { NULL, 0 } -}; - static void parse_dw_at_language (annocheck_data * data, Dwarf_Attribute * attr) { @@ -507,8 +777,6 @@ parse_dw_at_language (annocheck_data * data, Dwarf_Attribute * attr) return; } - einfo (VERBOSE2, "%s: DW_AT_language = %x", data->filename, (int) val); - switch (val) { case DW_LANG_C89: @@ -516,7 +784,7 @@ parse_dw_at_language (annocheck_data * data, Dwarf_Attribute * attr) case DW_LANG_C99: case DW_LANG_ObjC: case DW_LANG_C11: - set_lang (data, LANG_C, "DW_AT_language"); + set_lang (data, LANG_C, SOURCE_DW_AT_LANGUAGE); break; case DW_LANG_C_plus_plus: @@ -524,33 +792,74 @@ parse_dw_at_language (annocheck_data * data, Dwarf_Attribute * attr) case DW_LANG_C_plus_plus_03: case DW_LANG_C_plus_plus_11: case DW_LANG_C_plus_plus_14: - einfo (VERBOSE, "%s: Written in C++", data->filename); - set_lang (data, LANG_CXX, "DW_AT_language"); + if (! fixed_format_messages) + einfo (VERBOSE, "%s: info: Written in C++", data->filename); + set_lang (data, LANG_CXX, SOURCE_DW_AT_LANGUAGE); + break; + + case DW_LANG_Go: + set_lang (data, LANG_GO, SOURCE_DW_AT_LANGUAGE); + break; + + case DW_LANG_Rust: + set_lang (data, LANG_RUST, SOURCE_DW_AT_LANGUAGE); break; + case DW_LANG_lo_user + 1: + /* Some of the GO runtime uses this value, */ + set_lang (data, LANG_ASSEMBLER, SOURCE_DW_AT_LANGUAGE); + break; + default: if (! per_file.other_language) { switch (val) { - case DW_LANG_Go: - einfo (VERBOSE, "%s: Written in GO", data->filename); - break; - case DW_LANG_Rust: - einfo (VERBOSE, "%s: Written in RUST", data->filename); - break; default: - einfo (VERBOSE, "%s: Written in a language other than C and CC++", data->filename); - einfo (VERBOSE2, "debugging: val = %ld", (long) val); + einfo (VERBOSE, "%s: info: Written in a language other than C/C++/Go/Rust", data->filename); + einfo (VERBOSE2, "debugging: val = %#lx", (long) val); break; } per_file.other_language = true; } - set_lang (data, LANG_OTHER, "DW_AT_language"); + set_lang (data, LANG_OTHER, SOURCE_DW_AT_LANGUAGE); break; } } +typedef struct tool_id +{ + const char * producer_string; + uint tool_type; +} tool_id; + +static const tool_id tools[] = +{ + { "GNU C", TOOL_GCC }, + { "GNU Fortran", TOOL_FORTRAN }, + { "rustc version", TOOL_RUST }, + { "clang version", TOOL_CLANG }, + { "clang LLVM", TOOL_CLANG }, /* Is this right ? */ + { "GNU Fortran", TOOL_FORTRAN }, + { "GNU GIMPLE", TOOL_GIMPLE }, + { "Go cmd/compile", TOOL_GO }, + { "GNU AS", TOOL_GAS }, + { NULL, TOOL_UNKNOWN } +}; + +struct tool_string +{ + const char * lead_in; + const char * tool_name; + uint tool_id; +}; + +static inline bool +is_object_file (void) +{ + return per_file.e_type == ET_REL; +} + static void parse_dw_at_producer (annocheck_data * data, Dwarf_Attribute * attr) { @@ -558,7 +867,7 @@ parse_dw_at_producer (annocheck_data * data, Dwarf_Attribute * attr) if (string == NULL) { - unsigned int form = dwarf_whatform (attr); + uint form = dwarf_whatform (attr); if (form == DW_FORM_GNU_strp_alt) warn (data, "DW_FORM_GNU_strp_alt not yet handled"); @@ -573,17 +882,29 @@ parse_dw_at_producer (annocheck_data * data, Dwarf_Attribute * attr) /* See if we can determine exactly which tool did produce this binary. */ const tool_id * tool; const char * where; - enum tool madeby = TOOL_UNKNOWN; - unsigned int version = 0; + uint madeby = TOOL_UNKNOWN; + uint version = 0; for (tool = tools; tool->producer_string != NULL; tool ++) if ((where = strstr (string, tool->producer_string)) != NULL) { madeby = tool->tool_type; + /* Look for a space after the ID string. */ where = strchr (where + strlen (tool->producer_string), ' '); if (where != NULL) - version = strtod (where, NULL); + { + version = strtod (where + 1, NULL); + /* Convert go1.14.13 into 14. + Note - strictly speaking 14 is the revision, not the version. + But the GO compiler is always version 1, and it is the + revision that matters as far as security features are concerened. */ + if (version == 0 + && madeby == TOOL_GO + && strncmp (where + 1, "go1.", 4) == 0) + version = strtod (where + 5, NULL); + } + break; } @@ -591,18 +912,14 @@ parse_dw_at_producer (annocheck_data * data, Dwarf_Attribute * attr) { /* FIXME: This can happen for object files because the DWARF data has not been relocated. Find out how to handle this using libdwarf. */ - if (per_file.e_type == ET_REL) + if (is_object_file ()) warn (data, "DW_AT_producer string invalid - probably due to relocations not being applied"); else warn (data, "Unable to determine the binary's producer from its DW_AT_producer string"); return; } - if (madeby != TOOL_GCC && madeby != TOOL_GIMPLE && per_file.tool == TOOL_UNKNOWN) - einfo (VERBOSE, "%s: Discovered non-gcc code producer (%s), skipping gcc specific checks", - data->filename, get_tool_name (madeby)); - - set_producer (data, madeby, version, "DW_AT_producer"); + add_producer (data, madeby, version, "DW_AT_producer", true); /* The DW_AT_producer string may also contain some of the command line options that were used to compile the binary. This happens @@ -611,106 +928,64 @@ parse_dw_at_producer (annocheck_data * data, Dwarf_Attribute * attr) options. Note - this is suboptimal since these options do not necessarily apply to the entire binary, but in the absence of annobin data they are better than nothing. */ - switch (madeby) - { - default: - break; - case TOOL_CLANG: - /* Try to determine if there are any command line options recorded in the - DW_AT_producer string. FIXME: This is not a very good heuristic. */ - if (strstr (string, "-f") || strstr (string, "-g") || strstr (string, "-O")) - { - if (! skip_check (TEST_OPTIMIZATION, NULL)) - { - if (strstr (string, " -O2") || strstr (string, " -O3")) - { - tests[TEST_OPTIMIZATION].num_pass ++; - einfo (VERBOSE2, "%s: PASS: Compiled with sufficient optimization", data->filename); - } - else if (strstr (string, " -O0") || strstr (string, " -O1")) - { - /* FIXME: This may not be a failure. GCC needs -O2 or - better for -D_FORTIFY_SOURCE to work properly, but - other compilers may not. */ - tests[TEST_OPTIMIZATION].num_fail ++; - einfo (VERBOSE, "%s: FAIL: Built with insufficient optimization", data->filename); - } - else - einfo (VERBOSE2, "%s: MAYB: Optimization level not found in DW_AT_producer string", data->filename); - } + /* Try to determine if there are any command line options recorded in the + DW_AT_producer string. FIXME: This is not a very good heuristic. */ + if (strstr (string, "-f") || strstr (string, "-g") || strstr (string, "-O")) + { + if (strstr (string, " -O2") || strstr (string, " -O3")) + pass (data, TEST_OPTIMIZATION, SOURCE_DW_AT_PRODUCER, NULL); + else if (strstr (string, " -O0") || strstr (string, " -O1")) + /* FIXME: This may not be a failure. GCC needs -O2 or + better for -D_FORTIFY_SOURCE to work properly, but + other compilers may not. */ + fail (data, TEST_OPTIMIZATION, SOURCE_DW_AT_PRODUCER, "optimization level too low"); + else + info (data, TEST_OPTIMIZATION, SOURCE_DW_AT_PRODUCER, "not found in string"); - if (! skip_check (TEST_PIC, NULL)) - { - if (strstr (string, " -fpic") || strstr (string, " -fPIC") - || strstr (string, " -fpie") || strstr (string, " -fPIE")) - { - tests[TEST_PIC].num_pass ++; - einfo (VERBOSE2, "%s: PASS: Compiled with -fpic/-fpie", data->filename); - } - else - einfo (VERBOSE2, "%s: MAYB: -fPIC/-fPIE not found in DW_AT_producer string", data->filename); - } + if (strstr (string, " -fpic") || strstr (string, " -fPIC") + || strstr (string, " -fpie") || strstr (string, " -fPIE")) + pass (data, TEST_PIC, SOURCE_DW_AT_PRODUCER, NULL); + else + info (data, TEST_PIC, SOURCE_DW_AT_PRODUCER, "-fpic/-fpie not found in string"); - if (! skip_check (TEST_STACK_PROT, NULL)) - { - if (strstr (string, "-fstack-protector-strong") - || strstr (string, "-fstack-protector-all")) - { - tests[TEST_STACK_PROT].num_pass ++; - einfo (VERBOSE, "%s: PASS: Compiled with sufficient stack protection", data->filename); - } - else if (strstr (string, "-fstack-protector")) - { - tests[TEST_STACK_PROT].num_fail ++; - einfo (VERBOSE, "%s: FAIL: Compiled with insufficient stack protection", data->filename); - } - else - einfo (VERBOSE2, "%s: MAYB: -fstack-protector not found in DW_AT_producer string", data->filename); - } + if (strstr (string, "-fstack-protector-strong") + || strstr (string, "-fstack-protector-all")) + pass (data, TEST_STACK_PROT, SOURCE_DW_AT_PRODUCER, NULL); + else if (strstr (string, "-fstack-protector")) + fail (data, TEST_STACK_PROT, SOURCE_DW_AT_PRODUCER, "insufficient protection enabled"); + else + info (data, TEST_STACK_PROT, SOURCE_DW_AT_PRODUCER, "not found in string"); - if (! skip_check (TEST_WARNINGS, NULL)) - { - if (strstr (string, "-Wall") - || strstr (string, "-Wformat-security") - || strstr (string, "-Werror=format-security")) - { - tests[TEST_WARNINGS].num_pass ++; - einfo (VERBOSE, "%s: PASS: Compiled with sufficient warning enablement", data->filename); - } - else if (! built_with_gimple ()) /* Gimple compilation drops all warnings. */ - einfo (VERBOSE2, "%s: MAYB: -Wall/-Wformat-security not found in DW_AT_producer string", data->filename); - } + if (strstr (string, "-Wall") + || strstr (string, "-Wformat-security") + || strstr (string, "-Werror=format-security")) + pass (data, TEST_WARNINGS, SOURCE_DW_AT_PRODUCER, NULL); + else + info (data, TEST_WARNINGS, SOURCE_DW_AT_PRODUCER, "not found in string"); - if ((per_file.e_machine == EM_386 || per_file.e_machine == EM_X86_64) - && ! skip_check (TEST_CF_PROTECTION, NULL)) - { - if (strstr (string, "-fcf-protection")) - { - tests[TEST_CF_PROTECTION].num_pass ++; - einfo (VERBOSE, "%s: PASS: Compiled with control flow protection enabled", data->filename); - } - else - einfo (VERBOSE2, "%s: MAYB: -fcf-protection not found in DW_AT_producer string", data->filename); - } - } - else if (! per_file.warned_command_line) + if (is_x86 ()) { - warn (data, "Command line options not recorded by -grecord-gcc-switches"); - per_file.warned_command_line = true; + if (strstr (string, "-fcf-protection")) + pass (data, TEST_CF_PROTECTION, SOURCE_DW_AT_PRODUCER, NULL); + else + info (data, TEST_CF_PROTECTION, SOURCE_DW_AT_PRODUCER, "not found in string"); } - - break; + } + else if (BE_VERBOSE && ! per_file.warned_command_line) + { + warn (data, "Command line options not recorded by -grecord-gcc-switches"); + per_file.warned_command_line = true; } } /* Look for DW_AT_producer and DW_AT_language attributes. */ static bool -hardened_dwarf_walker (annocheck_data * data, - Dwarf * dwarf ATTRIBUTE_UNUSED, - Dwarf_Die * die, - void * ptr ATTRIBUTE_UNUSED) +dwarf_attribute_checker (annocheck_data * data, + Dwarf * dwarf ATTRIBUTE_UNUSED, + Dwarf_Die * die, + void * ptr ATTRIBUTE_UNUSED) { Dwarf_Attribute attr; @@ -735,9 +1010,8 @@ start (annocheck_data * data) for (i = 0; i < TEST_MAX; i++) { - tests [i].num_pass = 0; - tests [i].num_fail = 0; - tests [i].num_maybe = 0; + tests [i].state = STATE_UNTESTED; + tests [i].result_announced = false; } /* Initialise other per-file variables. */ @@ -772,12 +1046,14 @@ start (annocheck_data * data) /* We do not expect to find ET_EXEC binaries. These days all binaries should be ET_DYN, even executable programs. */ - if (per_file.e_type == ET_EXEC && tests[TEST_PIE].enabled) - tests[TEST_PIE].num_fail ++; - + if (per_file.e_type == ET_EXEC) + fail (data, TEST_PIE, SOURCE_ELF_HEADER, "not linked with -Wl,-pie"); + else + pass (data, TEST_PIE, SOURCE_ELF_HEADER, NULL); + /* Check to see if something other than gcc produced parts of this binary. */ - (void) annocheck_walk_dwarf (data, hardened_dwarf_walker, NULL); + (void) annocheck_walk_dwarf (data, dwarf_attribute_checker, NULL); return true; } @@ -800,32 +1076,46 @@ interesting_sec (annocheck_data * data, if (sec->shdr.sh_type == SHT_NOBITS && sec->shdr.sh_size > 0) per_file.debuginfo_file = true; - per_file.text_section_name_index = sec->shdr.sh_name; - per_file.text_section_alignment = sec->shdr.sh_addralign; + per_file.text_section_name_index = sec->shdr.sh_name; + per_file.text_section_alignment = sec->shdr.sh_addralign; + per_file.text_section_range.start = sec->shdr.sh_addr; + per_file.text_section_range.end = sec->shdr.sh_addr + sec->shdr.sh_size; + return false; /* We do not actually need to scan the contents of the .text section. */ } - else if (per_file.debuginfo_file) + + if (per_file.debuginfo_file) return false; /* If the file has a stack section then check its permissions. */ if (streq (sec->secname, ".stack")) { - if ((sec->shdr.sh_flags & (SHF_WRITE | SHF_EXECINSTR)) == SHF_WRITE) - ++ tests[TEST_GNU_STACK].num_pass; + if ((sec->shdr.sh_flags & (SHF_WRITE | SHF_EXECINSTR)) != SHF_WRITE) + fail (data, TEST_GNU_STACK, SOURCE_SECTION_HEADERS, ".stack section has permissions other than just WRITE"); + else if (tests[TEST_GNU_STACK].state == STATE_PASSED) + maybe (data, TEST_GNU_STACK, SOURCE_SECTION_HEADERS, "multiple stack sections detected"); else - ++ tests[TEST_GNU_STACK].num_fail; + pass (data, TEST_GNU_STACK, SOURCE_SECTION_HEADERS, NULL); return false; } /* Note the permissions on GOT/PLT relocation sections. */ - if (streq (sec->secname, ".rel.got") + if (streq (sec->secname, ".rel.got") || streq (sec->secname, ".rela.got") || streq (sec->secname, ".rel.plt") || streq (sec->secname, ".rela.plt")) { if (sec->shdr.sh_flags & SHF_WRITE) - ++ tests[TEST_WRITEABLE_GOT].num_fail; + { + if (is_object_file ()) + skip (data, TEST_WRITEABLE_GOT, SOURCE_SECTION_HEADERS, "Object file"); + else + fail (data, TEST_WRITEABLE_GOT, SOURCE_SECTION_HEADERS, NULL); + } + else + pass (data, TEST_WRITEABLE_GOT, SOURCE_SECTION_HEADERS, NULL); + return false; } @@ -860,23 +1150,18 @@ align (unsigned long val, unsigned long alignment) return (val + (alignment - 1)) & (~ (alignment - 1)); } -static const char * -get_component_name (annocheck_data * data, - annocheck_section * sec, - hardened_note_data * note_data, - bool prefer_func_symbol) +static void +get_component_name (annocheck_data * data, + annocheck_section * sec, + note_range * note_data, + bool prefer_func_symbol) { - static char * buffer = NULL; + char * buffer; const char * sym; int res; + uint type; - if (buffer != NULL) - { - free (buffer); - buffer = NULL; - } - - sym = annocheck_find_symbol_for_address_range (data, sec, note_data->start, note_data->end, prefer_func_symbol); + sym = annocheck_get_symbol_name_and_type (data, sec, note_data->start, note_data->end, prefer_func_symbol, & type); if (sym == NULL) { @@ -888,22 +1173,17 @@ get_component_name (annocheck_data * data, else res = asprintf (& buffer, "component: %s", sym); - if (res > 0) - return buffer; - return NULL; -} + free ((char *) per_file.component_name); -static const char * -stack_prot_type (uint value) -{ - switch (value) + if (res > 0) + { + per_file.component_name = buffer; + per_file.component_type = type; + } + else { - case 0: return "-fno-stack-protector"; - case 1: return "-fstack-protector"; - case 2: return "-fstack-protector-all"; - case 3: return "-fstack-protector-strong"; - case 4: return "-fstack-protector-explicit"; - default: return ""; + per_file.component_name = NULL; + per_file.component_type = 0; } } @@ -932,43 +1212,6 @@ record_range (ulong start, ulong end) next_free_range ++; } -/* Wrapper for einfo that avoids calling get_component_name() - unless we know that the string will be needed. */ - -static void -report_i (einfo_type type, - const char * format, - annocheck_data * data, - annocheck_section * sec, - hardened_note_data * note, - bool prefer_func, - uint value) -{ - if (type == VERBOSE2 && ! BE_VERY_VERBOSE) - return; - if (type == VERBOSE && ! BE_VERBOSE) - return; - - einfo (type, format, data->filename, get_component_name (data, sec, note, prefer_func), value); -} - -static void -report_s (einfo_type type, - const char * format, - annocheck_data * data, - annocheck_section * sec, - hardened_note_data * note, - bool prefer_func, - const char * value) -{ - if (type == VERBOSE2 && ! BE_VERY_VERBOSE) - return; - if (type == VERBOSE && ! BE_VERBOSE) - return; - - einfo (type, format, data->filename, get_component_name (data, sec, note, prefer_func), value); -} - static ulong get_4byte_value (const unsigned char * data) { @@ -988,7 +1231,7 @@ static void report_note_producer (annocheck_data * data, unsigned char producer, const char * source, - unsigned int version) + uint version) { if (! BE_VERBOSE) return; @@ -998,7 +1241,11 @@ report_note_producer (annocheck_data * data, per_file.note_source[producer] = version; - einfo (PARTIAL, "Hardened: %s: info: Notes produced by %s plugin ", data->filename, source); + if (fixed_format_messages) + return; + + einfo (PARTIAL, "%s: %s: info: notes produced by %s plugin ", + HARDENED_CHECKER_NAME, data->filename, source); if (version == 0) einfo (PARTIAL, "(version unknown)\n"); @@ -1008,18 +1255,39 @@ report_note_producer (annocheck_data * data, einfo (PARTIAL, "version %u\n", version); } +static const char * +note_name (const char * attr) +{ + if (isprint (* attr)) + return attr; + + switch (* attr) + { + case GNU_BUILD_ATTRIBUTE_VERSION: return "Version"; + case GNU_BUILD_ATTRIBUTE_TOOL: return "Tool"; + case GNU_BUILD_ATTRIBUTE_RELRO: return "Relro"; + case GNU_BUILD_ATTRIBUTE_ABI: return "ABI"; + case GNU_BUILD_ATTRIBUTE_STACK_SIZE: return "StackSize"; + case GNU_BUILD_ATTRIBUTE_PIC: return "PIC"; + case GNU_BUILD_ATTRIBUTE_STACK_PROT: return "StackProt"; + case GNU_BUILD_ATTRIBUTE_SHORT_ENUM: return "Enum"; + default: return ""; + } + +} + static bool -walk_build_notes (annocheck_data * data, - annocheck_section * sec, - GElf_Nhdr * note, - size_t name_offset, - size_t data_offset, - void * ptr) +build_note_checker (annocheck_data * data, + annocheck_section * sec, + GElf_Nhdr * note, + size_t name_offset, + size_t data_offset, + void * ptr ATTRIBUTE_UNUSED) { - bool prefer_func_name; - hardened_note_data * note_data; + bool prefer_func_name; + note_range * note_data; - if (note->n_type != NT_GNU_BUILD_ATTRIBUTE_OPEN + if (note->n_type != NT_GNU_BUILD_ATTRIBUTE_OPEN && note->n_type != NT_GNU_BUILD_ATTRIBUTE_FUNC) { einfo (FAIL, "%s: Unrecognised annobin note type %d", data->filename, note->n_type); @@ -1027,7 +1295,7 @@ walk_build_notes (annocheck_data * data, } prefer_func_name = note->n_type == NT_GNU_BUILD_ATTRIBUTE_FUNC; - note_data = (hardened_note_data *) ptr; + note_data = & per_file.note_data; if (note->n_namesz < 3) { @@ -1041,9 +1309,6 @@ walk_build_notes (annocheck_data * data, ulong end = 0; const unsigned char * descdata = sec->data->d_buf + data_offset; - /* FIXME: Should we add support for earlier versions of - the annobin notes which did not include an end symbol ? */ - if (note->n_descsz == 16) { int i; @@ -1109,27 +1374,36 @@ walk_build_notes (annocheck_data * data, } } - note_data->start = start; - note_data->end = end; + if (end == (ulong) -1) + { + einfo (WARN, "%s: Corrupt annobin note : end address == -1", data->filename); + start = end; + } - if (per_file.e_type != ET_REL && ! ignore_gaps) + if (! is_object_file () && ! ignore_gaps) { /* Notes can occur in any order and may be spread across multiple note sections. So we record the range covered here and then check for gaps once we have examined all of the notes. */ record_range (start, end); } - } - /* We skip notes for empty ranges unless we are dealing with unrelocated - object files, or files not produced by gcc (where we cannot guarantee - note ranges). */ - if (per_file.e_type != ET_REL - && note_data->start == note_data->end - && per_file.tool == TOOL_GCC) - { - einfo (VERBOSE2, "Skipping note at addr 0x%lx because its range is zero", (long) note_data->start); - return true; + if (start != per_file.note_data.start + || end != per_file.note_data.end) + { + /* The range has changed. Check the old range. If it was non-zero + in length then record the last known producer for code in that region. */ + if (per_file.note_data.start != per_file.note_data.end) + add_producer (data, per_file.current_tool, per_file.tool_version, SOURCE_ANNOBIN_NOTES, false); + + /* Update the saved range. */ + per_file.note_data.start = start; + per_file.note_data.end = end; + + /* If the new range is valid, get a component name for it. */ + if (start != end) + get_component_name (data, sec, note_data, prefer_func_name); + } } const char * namedata = sec->data->d_buf + name_offset; @@ -1137,6 +1411,16 @@ walk_build_notes (annocheck_data * data, char attr_type = namedata[pos - 1]; const char * attr = namedata + pos; + /* We skip notes with empty ranges unless we are dealing with unrelocated + object files. */ + if (! is_object_file () + && note_data->start == note_data->end) + { + einfo (VERBOSE2, "skip %s note for zero-length range at %#lx", + note_name (attr), note_data->start); + return true; + } + /* Advance pos to the attribute's value. */ if (! isprint (* attr)) pos ++; @@ -1181,6 +1465,8 @@ walk_build_notes (annocheck_data * data, return true; } + einfo (VERBOSE2, "process %s note for range at %#lx..%#lx", + note_name (attr), note_data->start, note_data->end); switch (* attr) { case GNU_BUILD_ATTRIBUTE_VERSION: @@ -1199,21 +1485,24 @@ walk_build_notes (annocheck_data * data, } if (* attr > '0' + SPEC_VERSION) - einfo (WARN, "%s: This checker only supports version %d of the Watermark protocol. The data in the notes uses version %d", + einfo (INFO, "%s: WARN: This checker only supports version %d of the Watermark protocol. The data in the notes uses version %d", data->filename, SPEC_VERSION, * attr - '0'); /* Check the note per_file. */ ++ attr; char producer = * attr; ++ attr; - unsigned int version = 0; + + uint version = 0; if (* attr != 0) version = strtod (attr, NULL); + const char * name; switch (producer) { case ANNOBIN_TOOL_ID_ASSEMBLER: name = "assembler"; + add_producer (data, TOOL_GAS, 2, SOURCE_ANNOBIN_NOTES, true); break; case ANNOBIN_TOOL_ID_LINKER: @@ -1227,28 +1516,36 @@ walk_build_notes (annocheck_data * data, case ANNOBIN_TOOL_ID_GCC: name = "gcc"; producer = ANNOBIN_TOOL_ID_GCC; + if (version > 99) + add_producer (data, TOOL_GCC, version / 100, SOURCE_ANNOBIN_NOTES, true); + else + add_producer (data, TOOL_GCC, 0, SOURCE_ANNOBIN_NOTES, true); /* FIXME: Add code to check that the version of the note producer is not greater than our version. */ + break; - /* Note that we have seen compiler generated code. (As opposed - to assembler or linker generated code). This means that we - can expect to see notes for -D_FROTIFY_SOURCE and -D_GLIBCXX_ASSERTIONS, - and if they are missing, we can complain. We do not use - the value of per_file.tool because if the assembler source was - built using gcc then it will have debug information associated - with it, with a DW_AT_PRODUCER string that includes the *gcc* - version number. */ - per_file.compiled_code_seen = true; + case ANNOBIN_TOOL_ID_GCC_LTO: + name = "lto"; + if (version > 99) + add_producer (data, TOOL_GIMPLE, version / 100, SOURCE_ANNOBIN_NOTES, true); + else + add_producer (data, TOOL_GIMPLE, 0, SOURCE_ANNOBIN_NOTES, true); break; case ANNOBIN_TOOL_ID_LLVM: name = "LLVM"; - per_file.compiled_code_seen = true; + if (version > 99) + add_producer (data, TOOL_LLVM, version / 100, SOURCE_ANNOBIN_NOTES, true); + else + add_producer (data, TOOL_LLVM, 0, SOURCE_ANNOBIN_NOTES, true); break; case ANNOBIN_TOOL_ID_CLANG: name = "Clang"; - per_file.compiled_code_seen = true; + if (version > 99) + add_producer (data, TOOL_CLANG, version / 100, SOURCE_ANNOBIN_NOTES, true); + else + add_producer (data, TOOL_CLANG, 0, SOURCE_ANNOBIN_NOTES, true); break; default: @@ -1268,7 +1565,7 @@ walk_build_notes (annocheck_data * data, } /* Parse the tool attribute looking for the version of gcc used to build the component. */ - unsigned major, minor, rel; + uint major, minor, rel; /* As of version 8.80 there are two BUILT_ATTRIBUTE_TOOL version strings, one for the compiler that built the annobin plugin and one for the @@ -1295,17 +1592,20 @@ walk_build_notes (annocheck_data * data, continue; } - einfo (VERBOSE2, "%s: info: Detected information created by an annobin plugin running on %s version %u.%u.%u", + einfo (VERBOSE2, "%s: info: detected information created by an annobin plugin running on %s version %u.%u.%u", data->filename, t->tool_name, major, minor, rel); + /* Make a note of the producer in case there has not been any version notes. */ + if (t->tool_id != TOOL_GCC || per_file.current_tool != TOOL_GIMPLE) + add_producer (data, t->tool_id, major, SOURCE_ANNOBIN_NOTES, true); + if (per_file.run_major == 0) { per_file.run_major = major; - set_producer (data, t->tool_id, major, "GNU Build Attribute Tool"); } else if (per_file.run_major != major) { - einfo (WARN, "%s: this file was built by more than one version of %s (%u and %u)", + einfo (INFO, "%s: WARN: this file was built by more than one version of %s (%u and %u)", data->filename, t->tool_name, per_file.run_major, major); if (per_file.run_major < major) per_file.run_major = major; @@ -1315,7 +1615,7 @@ walk_build_notes (annocheck_data * data, { if (! per_file.warned_version_mismatch) { - einfo (INFO, "%s: ICE: Annobin plugin was built by %s version %u but run on %s version %u", + einfo (INFO, "%s: WARN: Annobin plugin was built by %s version %u but run on %s version %u", data->filename, t->tool_name, per_file.anno_major, t->tool_name, per_file.run_major); per_file.warned_version_mismatch = true; @@ -1324,6 +1624,7 @@ walk_build_notes (annocheck_data * data, per_file.run_minor = minor; per_file.run_rel = rel; + if ((per_file.anno_minor != 0 && per_file.anno_minor != minor) || (per_file.anno_rel != 0 && per_file.anno_rel != rel)) { @@ -1361,7 +1662,7 @@ walk_build_notes (annocheck_data * data, continue; } - einfo (VERBOSE2, "%s: info: Detected information stored by an annobin plugin built by %s version %u.%u.%u", + einfo (VERBOSE2, "%s: info: detected information stored by an annobin plugin built by %s version %u.%u.%u", data->filename, t->tool_name, major, minor, rel); if (per_file.anno_major == 0) @@ -1370,7 +1671,7 @@ walk_build_notes (annocheck_data * data, } else if (per_file.anno_major != major) { - einfo (WARN, "%s: notes produced by annobins compiled for more than one version of %s (%u vs %u)", + einfo (INFO, "%s: WARN: notes produced by annobins compiled for more than one version of %s (%u vs %u)", data->filename, t->tool_name, per_file.anno_major, major); if (per_file.anno_major < major) per_file.anno_major = major; @@ -1380,7 +1681,7 @@ walk_build_notes (annocheck_data * data, { if (! per_file.warned_version_mismatch) { - einfo (INFO, "%s: ICE: Annobin plugin was built by %s version %u but run on %s version %u", + einfo (INFO, "%s: WARN: Annobin plugin was built by %s version %u but run on %s version %u", data->filename, t->tool_name, per_file.anno_major, t->tool_name, per_file.run_major); per_file.warned_version_mismatch = true; } @@ -1409,22 +1710,18 @@ walk_build_notes (annocheck_data * data, if (gcc != NULL) { /* FIXME: This assumes that the tool string looks like: "gcc 7.x.x......" */ - unsigned int version = (unsigned int) strtoul (gcc + 4, NULL, 10); - - report_i (VERBOSE2, "%s: (%s) built-by gcc version %u", - data, sec, note_data, prefer_func_name, version); + uint version = (uint) strtoul (gcc + 4, NULL, 10); - set_producer (data, TOOL_GCC, version, "GNU Build Attribute"); + einfo (VERBOSE2, "%s: (%s) built-by gcc version %u", + data->filename, per_file.component_name, version); } else - report_s (VERBOSE, "%s: (%s) unable to parse tool attribute: %s", - data, sec, note_data, prefer_func_name, attr); + einfo (VERBOSE, "%s: (%s) unable to parse tool attribute: %s", + data->filename, per_file.component_name, attr); break; case GNU_BUILD_ATTRIBUTE_PIC: - if (value < 3 - && (value < 1 || per_file.e_type == ET_EXEC) - && skip_check (TEST_PIC, get_component_name (data, sec, note_data, prefer_func_name))) + if (skip_check (TEST_PIC)) break; /* Convert the pic value into a pass/fail result. */ @@ -1432,64 +1729,35 @@ walk_build_notes (annocheck_data * data, { case -1: default: - report_i (VERBOSE, "%s: MAYB: (%s): unexpected value for PIC note (%x)", - data, sec, note_data, prefer_func_name, value); - tests[TEST_PIC].num_maybe ++; + maybe (data, TEST_PIC, SOURCE_ANNOBIN_NOTES, "unexpected value"); + einfo (VERBOSE2, "debug: PIC note value: %x", value); break; case 0: - report_s (VERBOSE, "%s: FAIL: (%s): compiled without -fPIC/-fPIE", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_PIC].num_fail ++; + fail (data, TEST_PIC, SOURCE_ANNOBIN_NOTES, "-fpic/-fpie not enabled"); break; case 1: case 2: /* Compiled wth -fpic not -fpie. */ - if (per_file.e_type == ET_EXEC) - { -#if 0 /* Suppressed because ET_EXEC will already generate a failure... */ - /* Building an executable with -fPIC rather than -fPIE is a bad thing - as it means that the executable is located at a known address that - can be exploited by an attacker. Linking against shared libraries - compiled with -fPIC is OK, since they expect to have their own - address space, but linking against static libraries compiled with - -fPIC is still bad. But ... compiling with -fPIC but then linking - with -fPIE is OK. It is the final result that matters. However - we have already checked the e_type above and know that it is ET_EXEC, - ie, not a PIE executable, so this result is a FAIL. */ - report_s (VERBOSE, "%s: FAIL: (%s): compiled with -fPIC rather than -fPIE", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_PIC].num_fail ++; -#endif - } - else - { - report_s (VERBOSE2, "%s: PASS: (%s): compiled with -fPIC/-fPIE", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_PIC].num_pass ++; - } + pass (data, TEST_PIC, SOURCE_ANNOBIN_NOTES, NULL); break; case 3: case 4: - report_s (VERBOSE2, "%s: PASS: (%s): compiled with -fPIE", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_PIC].num_pass ++; + pass (data, TEST_PIC, SOURCE_ANNOBIN_NOTES, NULL); break; } break; case GNU_BUILD_ATTRIBUTE_STACK_PROT: - if (value != 2 && value != 3 - && skip_check (TEST_STACK_PROT, get_component_name (data, sec, note_data, prefer_func_name))) + if (skip_check (TEST_STACK_PROT)) break; /* We can get stack protection notes without tool notes. See BZ 1703788 for an example. */ - if (value != 2 && value != 3 && ! built_by_compiler ()) + if (per_file.current_tool == TOOL_GO) { - report_s (VERBOSE, "%s: skip: (%s): Insufficient stack protection (%s) - ignoring because not in compiled code", - data, sec, note_data, prefer_func_name, stack_prot_type (value)); + skip (data, TEST_STACK_PROT, SOURCE_ANNOBIN_NOTES, "GO code does not support stack protection"); break; } @@ -1497,232 +1765,200 @@ walk_build_notes (annocheck_data * data, { case -1: default: - report_i (VERBOSE, "%s: MAYB: (%s): unexpected value for stack protection note (%x)", - data, sec, note_data, prefer_func_name, value); - tests[TEST_STACK_PROT].num_maybe ++; + maybe (data, TEST_STACK_PROT, SOURCE_ANNOBIN_NOTES, "unexpected note value"); break; case 0: /* NONE */ - report_s (VERBOSE, "%s: FAIL: (%s): No stack protection enabled", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_STACK_PROT].num_fail ++; + /* See BZ 1923439: Parts of glibc are deliberately compiled without stack protection, + because they execute before the framework is established. This is currently handled + by tests in skip_check (). */ + fail (data, TEST_STACK_PROT, SOURCE_ANNOBIN_NOTES, "stack protection deliberately disabled"); break; case 1: /* BASIC (funcs using alloca or with local buffers > 8 bytes) */ case 4: /* EXPLICIT */ - report_s (VERBOSE, "%s: FAIL: (%s): Insufficient stack protection: %s", - data, sec, note_data, prefer_func_name, stack_prot_type (value)); - tests[TEST_STACK_PROT].num_fail ++; + fail (data, TEST_STACK_PROT, SOURCE_ANNOBIN_NOTES, "only some functions protected"); break; case 2: /* ALL */ case 3: /* STRONG */ - report_s (VERBOSE2, "%s: PASS: (%s): %s enabled", - data, sec, note_data, prefer_func_name, stack_prot_type (value)); - tests[TEST_STACK_PROT].num_pass ++; + pass (data, TEST_STACK_PROT, SOURCE_ANNOBIN_NOTES, NULL); break; } break; case GNU_BUILD_ATTRIBUTE_SHORT_ENUM: -#if 0 /* There is no valid reason to skip this test. */ - if (skip_check (TEST_SHORT_ENUM, get_component_name (data, sec, note_data, prefer_func_name))) - break; -#endif - if (value == 1) - { - tests[TEST_SHORT_ENUM].num_fail ++; - - if (tests[TEST_SHORT_ENUM].num_pass) - report_i (VERBOSE, "%s: FAIL: (%s): different -fshort-enum option used", - data, sec, note_data, prefer_func_name, value); - } - else if (value == 0) - { - tests[TEST_SHORT_ENUM].num_pass ++; + { + enum short_enum_state state = value ? SHORT_ENUM_STATE_SHORT : SHORT_ENUM_STATE_LONG; - if (tests[TEST_SHORT_ENUM].num_fail) - report_i (VERBOSE, "%s: FAIL: (%s): different -fshort-enum option used", - data, sec, note_data, prefer_func_name, value); - } - else - { - report_i (VERBOSE, "%s: MAYB: (%s): unexpected value for short-enum note (%x)", - data, sec, note_data, prefer_func_name, value); - tests[TEST_SHORT_ENUM].num_maybe ++; - } + if (value < 0 || value > 1) + { + maybe (data, TEST_SHORT_ENUM, SOURCE_ANNOBIN_NOTES, "unexpected note value"); + einfo (VERBOSE2, "debug: enum note value: %x", value); + } + else if (per_file.short_enum_state == SHORT_ENUM_STATE_UNSET) + per_file.short_enum_state = state; + else if (per_file.short_enum_state != state) + fail (data, TEST_SHORT_ENUM, SOURCE_ANNOBIN_NOTES, "both short and long enums supported"); + } break; case 'b': if (const_strneq (attr, "branch_protection:")) { -#ifdef EM_AARCH64 /* RHEL-6 does not define EM_AARCh64. */ if (per_file.e_machine != EM_AARCH64) break; -#endif + + if (skip_check (TEST_BRANCH_PROTECTION)) + break; + attr += strlen ("branch_protection:"); if (* attr == 0 || streq (attr, "(null)") || streq (attr, "default")) - { - if (skip_check (TEST_BRANCH_PROTECTION, get_component_name (data, sec, note_data, prefer_func_name))) - break; - - /* FIXME: Turn into a FAIL once -mbranch-protection is required by the security spec. */ - report_s (VERBOSE, "%s: info: (%s): Compiled without -mbranch-protection", - data, sec, note_data, prefer_func_name, NULL); - /* tests[TEST_BRANCH_PROTECTION].num_fail ++; */ - break; - } + /* FIXME: Turn into a FAIL once -mbranch-protection is required by the security spec. */ + info (data, TEST_BRANCH_PROTECTION, SOURCE_ANNOBIN_NOTES, "not enabled"); else if (streq (attr, "bti+pac-ret") || (streq (attr, "standard")) || const_strneq (attr, "pac-ret+bti")) - { - report_s (VERBOSE2, "%s: PASS: (%s): branch-protection enabled (%s)", - data, sec, note_data, prefer_func_name, attr); - tests[TEST_BRANCH_PROTECTION].num_pass ++; - } + pass (data, TEST_BRANCH_PROTECTION, SOURCE_ANNOBIN_NOTES, NULL); else if (streq (attr, "bti") || const_strneq (attr, "pac-ret")) - { - report_s (VERBOSE2, "%s: FAIL: (%s): Only partial branch-protection is enabled (%s)", - data, sec, note_data, prefer_func_name, attr); - tests[TEST_BRANCH_PROTECTION].num_fail ++; - } + fail (data, TEST_BRANCH_PROTECTION, SOURCE_ANNOBIN_NOTES, "only partially enabled"); else if (streq (attr, "none")) - { - if (skip_check (TEST_BRANCH_PROTECTION, get_component_name (data, sec, note_data, prefer_func_name))) - break; - - report_s (VERBOSE, "%s: FAIL: (%s): Compiled with -mbranch-protection=none", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_BRANCH_PROTECTION].num_fail ++; - break; - } + fail (data, TEST_BRANCH_PROTECTION, SOURCE_ANNOBIN_NOTES, "protection disabled"); else { - if (skip_check (TEST_BRANCH_PROTECTION, get_component_name (data, sec, note_data, prefer_func_name))) - break; - - report_s (VERBOSE, "%s: MAYB: (%s): unexpected value for branch-protection note (%s)", - data, sec, note_data, prefer_func_name, attr); - tests[TEST_BRANCH_PROTECTION].num_maybe ++; - break; + maybe (data, TEST_BRANCH_PROTECTION, SOURCE_ANNOBIN_NOTES, "unexpected note value"); + einfo (VERBOSE2, "debug: stack prot note value: %s", attr); } } else - einfo (VERBOSE2, "Unsupport annobin note '%s' - ignored", attr); + einfo (VERBOSE2, "Unsupported annobin note '%s' - ignored", attr); break; case 'c': if (streq (attr, "cf_protection")) { - if (per_file.e_machine != EM_386 && per_file.e_machine != EM_X86_64) + if (! is_x86 ()) break; - if (value != 4 && value != 8 - && skip_check (TEST_CF_PROTECTION, get_component_name (data, sec, note_data, prefer_func_name))) + if (skip_check (TEST_CF_PROTECTION)) break; + + if (! is_C_compiler (per_file.current_tool)) + { + skip (data, TEST_CF_PROTECTION, SOURCE_ANNOBIN_NOTES, "not built by gcc/clang"); + break; + } + + if (includes_gcc (per_file.current_tool) && per_file.tool_version < 8) + { + skip (data, TEST_CF_PROTECTION, SOURCE_ANNOBIN_NOTES, "needs gcc v8+"); + break; + } + /* Note - the annobin plugin adds one to the value of gcc's flag_cf_protection, + thus a setting of CF_FULL (3) is actually recorded as 4, and so on. */ switch (value) { case -1: default: - report_i (VERBOSE, "%s: MAYB: (%s): unexpected value for cf-protection note (%x)", - data, sec, note_data, prefer_func_name, value); - tests[TEST_CF_PROTECTION].num_maybe ++; + maybe (data, TEST_CF_PROTECTION, SOURCE_ANNOBIN_NOTES, "unexpected note value"); + einfo (VERBOSE2, "debug: cf prot note value: %x", value); break; - /* Note - the annobin plugin adds one to the value of gcc's flag_cf_protection, - thus a setting of CF_FULL (3) is actually recorded as 4, and so on. */ - case 4: /* CF_FULL. */ case 8: /* CF_FULL | CF_SET */ - report_i (VERBOSE2, "%s: PASS: (%s): cf-protection enabled (%x)", - data, sec, note_data, prefer_func_name, value); - tests[TEST_CF_PROTECTION].num_pass ++; + pass (data, TEST_CF_PROTECTION, SOURCE_ANNOBIN_NOTES, NULL); break; case 2: /* CF_BRANCH: Branch but not return. */ case 6: /* CF_BRANCH | CF_SET */ - report_s (VERBOSE, "%s: FAIL: (%s): Only compiled with -fcf-protection=branch", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_CF_PROTECTION].num_fail ++; + fail (data, TEST_CF_PROTECTION, SOURCE_ANNOBIN_NOTES, "only branch protection enabled"); break; case 3: /* CF_RETURN: Return but not branch. */ case 7: /* CF_RETURN | CF_SET */ - report_s (VERBOSE, "%s: FAIL: (%s): Only compiled with -fcf-protection=return", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_CF_PROTECTION].num_fail ++; + fail (data, TEST_CF_PROTECTION, SOURCE_ANNOBIN_NOTES, "only return protection enabled"); break; case 1: /* CF_NONE: No protection. */ case 5: /* CF_NONE | CF_SET */ - report_s (VERBOSE, "%s: FAIL: (%s): Compiled without -fcf-protection", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_CF_PROTECTION].num_fail ++; + fail (data, TEST_CF_PROTECTION, SOURCE_ANNOBIN_NOTES, "no protection enabled"); break; } } else - einfo (VERBOSE2, "Unsupport annobin note '%s' - ignored", attr); + einfo (VERBOSE2, "Unsupported annobin note '%s' - ignored", attr); break; case 'F': if (streq (attr, "FORTIFY")) { - if (value != 2 - && skip_check (TEST_FORTIFY, get_component_name (data, sec, note_data, prefer_func_name))) + if (skip_check (TEST_FORTIFY)) break; + if (! is_C_compiler (per_file.current_tool)) + { + skip (data, TEST_FORTIFY, SOURCE_ANNOBIN_NOTES, "not built by gcc/clang"); + break; + } + switch (value) { case -1: default: - report_i (VERBOSE, "%s: MAYB: (%s): unexpected value for fortify note (%x)", - data, sec, note_data, prefer_func_name, value); - tests[TEST_FORTIFY].num_maybe ++; + maybe (data, TEST_FORTIFY, SOURCE_ANNOBIN_NOTES, "unexpected note value"); + einfo (VERBOSE2, "debug: fortify note value: %x", value); + break; + + case 0xfe: + /* Note - in theory this should be a MAYBE result because we do not + know the fortify level that was used when the original sources were + compiled. But in practice doing this would generate MAYBE results + for all code compiled with -flto, even if -D_FORTIFY_SOURCE=2 was + used, and this would annoy a lot of users. (Especially since + LTO and FORTIFY are now enabled by the rpm build macros). So we + SKIP this test instead. + + In theory we could search to see if un-fortified versions of specific + functions are present in the executable's symbol table. eg memcpy + instead of memcpy_chk. This would help catch some cases where the + correct FORTIFY level was not set, but it would not work for test + cases which are intended to verify annocheck's ability to detect + this problem, but which do not call any sensitive functions. (This + is done by QE). It also fails for code which cannot be protected + by FORTIFY_SOURCE. Such code will still use the unenhanced functions + but could well have been compiled with -D_FORTIFY_SOURCE=2. + + Note - the annobin plugin for GCC will generate a compile time + warning if -D_FORTIFY_SOURCE is undefined or set to 0 or 1, but + only when compiling with -flto enabled, and not when compiling + pre-processed sources. */ + skip (data, TEST_FORTIFY, SOURCE_ANNOBIN_NOTES, "LTO compilation discards preprocessor options"); break; case 0xff: - /* BZ 1824393: We have not scanned the DWARF notes yet, so we may - not have the full picture as to which tool(s) built this binary. - BZ 1904479: But since we now do scan the DWARF early, we should - have a fuller picture. */ - if (per_file.compiled_code_seen && built_by_compiler()) - { - report_s (VERBOSE, "%s: FAIL: (%s): -D_FORTIFY_SOURCE was not present on the command line", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_FORTIFY].num_fail ++; - } + if (per_file.current_tool == TOOL_GIMPLE) + skip (data, TEST_FORTIFY, SOURCE_ANNOBIN_NOTES, "LTO compilation discards preprocessor options"); else - { - report_s (VERBOSE, "%s: MAYB: (%s): -D_FORTIFY_SOURCE not detected on the command line", - data, sec, note_data, prefer_func_name, NULL); - einfo (VERBOSE, "%s: This can happen if the source does not use C headers", - data->filename); - tests[TEST_FORTIFY].num_maybe ++; - } - + fail (data, TEST_FORTIFY, SOURCE_ANNOBIN_NOTES, "-D_FORTIFY_SOURCE=2 was not present on command line"); break; case 0: case 1: - report_i (VERBOSE, "%s: FAIL: (%s): Insufficient value for -D_FORTIFY_SOURCE: %d", - data, sec, note_data, prefer_func_name, value); - tests[TEST_FORTIFY].num_fail ++; + fail (data, TEST_FORTIFY, SOURCE_ANNOBIN_NOTES, "-O level is too low"); break; case 2: - report_s (VERBOSE2, "%s: PASS: (%s): -D_FORTIFY_SOURCE=2", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_FORTIFY].num_pass ++; + case 3: + pass (data, TEST_FORTIFY, SOURCE_ANNOBIN_NOTES, NULL); break; } } else - einfo (VERBOSE2, "Unsupport annobin note '%s' - ignored", attr); + einfo (VERBOSE2, "Unsupported annobin note '%s' - ignored", attr); break; case 'G': @@ -1730,163 +1966,128 @@ walk_build_notes (annocheck_data * data, { if (value == -1) { - report_i (VERBOSE, "%s: FAIL: (%s): unexpected value for optimize note (%x)", - data, sec, note_data, prefer_func_name, value); - tests[TEST_LTO].num_maybe ++; - tests[TEST_OPTIMIZATION].num_maybe ++; + maybe (data, TEST_OPTIMIZATION, SOURCE_ANNOBIN_NOTES, "unexpected note value"); + einfo (VERBOSE2, "debug: optimization note value: %x", value); + break; + } + + + if (skip_check (TEST_OPTIMIZATION)) + ; + else if (value & (1 << 13)) + { + /* Compiled with -Og rather than -O2. + Treat this as a flag to indicate that the package developer is + intentionally not compiling with -O2, so suppress warnings about it. */ + skip (data, TEST_OPTIMIZATION, SOURCE_ANNOBIN_NOTES, "Compiled with -Og"); + + /* Add a pass result so that we do not complain about lack of optimization information. */ + if (tests[TEST_OPTIMIZATION].state == STATE_UNTESTED) + tests[TEST_OPTIMIZATION].state = STATE_PASSED; } else { - if (value & (1 << 13)) - { - /* Compiled with -Og rather than -O2. - Treat this as a flag to indicate that the package developer is - intentionally not compiling with -O2, so suppress warnings about it. */ - report_i (VERBOSE, "%s: skip: (%s): compiled with -Og, so ignoring test for -O2+", - data, sec, note_data, prefer_func_name, value); - /* Add a pass result so that we do not complain about lack of optimization information. */ - tests[TEST_OPTIMIZATION].num_pass ++; - } - else - { - uint opt = (value >> 9) & 3; + uint opt = (value >> 9) & 3; - if (opt == 0 || opt == 1) - { - if (! skip_check (TEST_OPTIMIZATION, get_component_name (data, sec, note_data, prefer_func_name))) - { - report_i (VERBOSE, "%s: FAIL: (%s): Insufficient optimization level: -O%d", - data, sec, note_data, prefer_func_name, opt); - tests[TEST_OPTIMIZATION].num_fail ++; - } - } - else /* opt == 2 || opt == 3 */ - { - report_i (VERBOSE2, "%s: PASS: (%s): Sufficient optimization level: -O%d", - data, sec, note_data, prefer_func_name, opt); - tests[TEST_OPTIMIZATION].num_pass ++; - } - } + if (opt == 0 || opt == 1) + fail (data, TEST_OPTIMIZATION, SOURCE_ANNOBIN_NOTES, "level too low"); + else /* opt == 2 || opt == 3 */ + pass (data, TEST_OPTIMIZATION, SOURCE_ANNOBIN_NOTES, NULL); + } - if (value & (1 << 14)) - { - /* Compiled with -Wall. */ - if (value & (1 << 15)) - { - report_i (VERBOSE2, "%s: PASS: (%s): Compiled with -Wall and -Wformat-security", - data, sec, note_data, prefer_func_name, value); - tests[TEST_WARNINGS].num_pass ++; - } - else - { - /* Compiled without -Wformat-security. - Not a failure because parts of glibc are compiled with way - and because recording of -Wformat-security only started with annobin v8.84. */ - report_i (VERBOSE2, "%s: PASS: (%s): Compiled with -Wall", - data, sec, note_data, prefer_func_name, value); - tests[TEST_WARNINGS].num_pass ++; - } - } - else if (value & (1 << 15)) - { - /* Compiled with -Wformat-security but not -Wall. - FIXME: We allow this for now, but really would should check for - any warnings enabled by -Wall that are important. (Missing -Wall - itself is not bad - this happens with LTO compilation - but we - still want important warnings enabled). */ - report_i (VERBOSE2, "%s: PASS: (%s): Compiled with -Wformat-security (but not -Wall)", - data, sec, note_data, prefer_func_name, value); - tests[TEST_WARNINGS].num_pass ++; - } - else - { - /* FIXME: At the moment the clang plugin is unable to detect -Wall. - for clang v9+. */ - if (built_by_clang () && per_file.tool_version > 8) - ; - else if (built_by_mixed ()) - ; - else if (built_with_gimple ()) /* Gimple compilation drops all warnings. */ - ; - else if (! skip_check (TEST_WARNINGS, get_component_name (data, sec, note_data, prefer_func_name))) - { - report_i (VERBOSE, "%s: FAIL: (%s): Compiled without either -Wall or -Wformat-security", - data, sec, note_data, prefer_func_name, value); - tests[TEST_WARNINGS].num_fail ++; - } - } + + if (skip_check (TEST_WARNINGS)) + ; + else if (value & (1 << 14)) + { + /* Compiled with -Wall. */ + pass (data, TEST_WARNINGS, SOURCE_ANNOBIN_NOTES, NULL); + } + else if (value & (1 << 15)) + { + /* Compiled with -Wformat-security but not -Wall. + FIXME: We allow this for now, but really would should check for + any warnings enabled by -Wall that are important. (Missing -Wall + itself is not bad - this happens with LTO compilation - but we + still want important warnings enabled). */ + pass (data, TEST_WARNINGS, SOURCE_ANNOBIN_NOTES, NULL); + } + /* FIXME: At the moment the clang plugin is unable to detect -Wall. + for clang v9+. */ + else if (per_file.current_tool == TOOL_CLANG && per_file.tool_version > 8) + skip (data, TEST_WARNINGS, SOURCE_ANNOBIN_NOTES, "Warning setting not detectable in newer versions of Clang"); + /* Gimple compilation discards warnings. */ + else if (per_file.current_tool == TOOL_GIMPLE) + skip (data, TEST_WARNINGS, SOURCE_ANNOBIN_NOTES, "LTO compilation discards preprocessor options"); + else if (value & ((1 << 16) | (1 << 17))) + { + /* LTO compilation. Normally caught by the GIMPLE test + above, but that does not work on stripped binaries. + We set STATE_PASSED here so that show_WARNINGS does + not complain about not finding any information. */ + if (tests[TEST_WARNINGS].state == STATE_UNTESTED) + tests[TEST_WARNINGS].state = STATE_PASSED; + } + else + fail (data, TEST_WARNINGS, SOURCE_ANNOBIN_NOTES, "compiled without either -Wall or -Wformat-security"); - if (! skip_check (TEST_LTO, get_component_name (data, sec, note_data, prefer_func_name))) - { - if (value & (1 << 16)) - { - if (value & (1 << 17)) - { - report_i (VERBOSE, "%s: BUG!: (%s): Compiled with -flto and -fno-lto", - data, sec, note_data, prefer_func_name, value); - tests[TEST_LTO].num_fail ++; - } - else - { - /* Compiled with -flto. */ - report_i (VERBOSE2, "%s: PASS: (%s): Compiled with -flto", - data, sec, note_data, prefer_func_name, value); - tests[TEST_LTO].num_pass ++; - } - } - else if (value & (1 << 17)) - { - /* Compiled without -flto. - Not a failure because we are still bringing up universal LTO enabledment. */ - if (! report_future_fail) - report_i (VERBOSE2, "%s: look: (%s): Compiled without -flto", - data, sec, note_data, prefer_func_name, value); - else - report_i (VERBOSE, "%s: look: (%s): Compiled without -flto", - data, sec, note_data, prefer_func_name, value); - - /* tests[TEST_LTO].num_fail ++; */ - } - else - { - report_i (VERBOSE2, "%s: UNKW: (%s): LTO compilation status not recorded", - data, sec, note_data, prefer_func_name, value); - } - } + + if (skip_check (TEST_LTO)) + ; + else if (value & (1 << 16)) + { + if (value & (1 << 17)) + fail (data, TEST_LTO, SOURCE_ANNOBIN_NOTES, "compiled with both -flto and -fno-lto"); + else + pass (data, TEST_LTO, SOURCE_ANNOBIN_NOTES, NULL); + } + else if (value & (1 << 17)) + { + /* Compiled without -flto. + Not a failure because we are still bringing up universal LTO enabledment. */ + if (report_future_fail) + info (data, TEST_LTO, SOURCE_ANNOBIN_NOTES, "compiled without -flto"); + } + else + { + info (data, TEST_LTO, SOURCE_ANNOBIN_NOTES, " -flto status not recorded in notes"); } + + break; } else if (streq (attr, "GLIBCXX_ASSERTIONS")) { - if (value != 1 - && skip_check (TEST_GLIBCXX_ASSERTIONS, get_component_name (data, sec, note_data, prefer_func_name))) + if (skip_check (TEST_GLIBCXX_ASSERTIONS)) break; + if (per_file.lang != LANG_UNKNOWN && per_file.lang != LANG_CXX) + { + skip (data, TEST_GLIBCXX_ASSERTIONS, SOURCE_ANNOBIN_NOTES, "source language not C++"); + break; + } + + if (! is_C_compiler (per_file.current_tool)) + { + skip (data, TEST_GLIBCXX_ASSERTIONS, SOURCE_ANNOBIN_NOTES, "current tool not gcc/clang"); + break; + } + switch (value) { case 0: - if (per_file.lang == LANG_UNKNOWN || per_file.lang == LANG_CXX) - { - report_s (VERBOSE, "%s: FAIL: (%s): Compiled without -D_GLIBCXX_ASSERTIONS", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_GLIBCXX_ASSERTIONS].num_fail ++; - } + if (per_file.current_tool == TOOL_GIMPLE) + skip (data, TEST_GLIBCXX_ASSERTIONS, SOURCE_ANNOBIN_NOTES, "LTO compilation discards preprocessor options"); else - { - report_s (VERBOSE, "%s: skip: (%s): Compiled without -D_GLIBCXX_ASSERTIONS, but not written in C++", - data, sec, note_data, prefer_func_name, NULL); - } + fail (data, TEST_GLIBCXX_ASSERTIONS, SOURCE_ANNOBIN_NOTES, "compiled without -D_GLIBCXX_ASSERTIONS"); break; case 1: - report_s (VERBOSE2, "%s: PASS: (%s): Compiled with -D_GLIBCXX_ASSERTIONS", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_GLIBCXX_ASSERTIONS].num_pass ++; + pass (data, TEST_GLIBCXX_ASSERTIONS, SOURCE_ANNOBIN_NOTES, NULL); break; default: - report_i (VERBOSE, "%s: MAYB: (%s): unexpected value for glibcxx_assertions note (%x)", - data, sec, note_data, prefer_func_name, value); - tests[TEST_GLIBCXX_ASSERTIONS].num_maybe ++; + maybe (data, TEST_GLIBCXX_ASSERTIONS, SOURCE_ANNOBIN_NOTES, "unexpected note value"); + einfo (VERBOSE2, "debug: assertion note value: %x", value); break; } } @@ -1899,39 +2100,36 @@ walk_build_notes (annocheck_data * data, { if (! per_file.warned_about_instrumentation) { - report_s (INFO, "%s: WARN: (%s): Instrumentation enabled - this is probably a mistake for production binaries", - data, sec, note_data, prefer_func_name, NULL); - per_file.warned_about_instrumentation = false; + einfo (INFO, "%s: WARN: (%s): Instrumentation enabled - this is probably a mistake for production binaries", + data->filename, per_file.component_name); + + per_file.warned_about_instrumentation = true; if (BE_VERBOSE) { - unsigned int sanitize, instrument, profile, arcs; + uint sanitize, instrument, profile, arcs; attr += strlen ("INSTRUMENT:"); if (sscanf (attr, "%u/%u/%u/%u", & sanitize, & instrument, & profile, & arcs) != 4) { - report_s (VERBOSE, "%s: ICE: (%s): Unable to extract details from instrumentation note", - data, sec, note_data, prefer_func_name, NULL); + einfo (VERBOSE2, "%s: ICE: (%s): Unable to extract details from instrumentation note", + data->filename, per_file.component_name); } else { einfo (VERBOSE, "%s: info: (%s): Details: -fsanitize=...: %s", - data->filename, get_component_name (data, sec, note_data, prefer_func_name), - sanitize ? "enabled" : "disabled"); + data->filename, per_file.component_name, sanitize ? "enabled" : "disabled"); einfo (VERBOSE, "%s: info: (%s): Details: -finstrument-functions: %s", - data->filename, get_component_name (data, sec, note_data, prefer_func_name), - instrument ? "enabled" : "disabled"); + data->filename, per_file.component_name, instrument ? "enabled" : "disabled"); einfo (VERBOSE, "%s: info: (%s): Details: -p and/or -pg: %s", - data->filename, get_component_name (data, sec, note_data, prefer_func_name), - profile ? "enabled" : "disabled"); + data->filename, per_file.component_name, profile ? "enabled" : "disabled"); einfo (VERBOSE, "%s: info: (%s): Details: -fprofile-arcs: %s", - data->filename, get_component_name (data, sec, note_data, prefer_func_name), - arcs ? "enabled" : "disabled"); + data->filename, per_file.component_name, arcs ? "enabled" : "disabled"); } } else - report_s (INFO, "%s: info: (%s): Run with -v for more information", - data, sec, note_data, prefer_func_name, NULL); + einfo (INFO, "%s: info: (%s): Run with -v for more information", + data->filename, per_file.component_name); } } else @@ -1944,28 +2142,34 @@ walk_build_notes (annocheck_data * data, if (per_file.e_machine == EM_ARM) break; - if (value != 1 - && skip_check (TEST_STACK_CLASH, get_component_name (data, sec, note_data, prefer_func_name))) + if (skip_check (TEST_STACK_CLASH)) break; + if (! includes_gcc (per_file.current_tool) && ! includes_gimple (per_file.current_tool)) + { + skip (data, TEST_STACK_CLASH, SOURCE_ANNOBIN_NOTES, "not compiled by gcc"); + break; + } + + if (per_file.tool_version < 7) + { + skip (data, TEST_STACK_CLASH, SOURCE_ANNOBIN_NOTES, "needs gcc 7+"); + break; + } + switch (value) { case 0: - report_s (VERBOSE, "%s: FAIL: (%s): Compiled without -fstack-clash-protection", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_STACK_CLASH].num_fail ++; + fail (data, TEST_STACK_CLASH, SOURCE_ANNOBIN_NOTES, "-fstack-clash-protection not enabled"); break; case 1: - report_s (VERBOSE2, "%s: PASS: (%s): Compiled with -fstack-clash-protection", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_STACK_CLASH].num_pass ++; + pass (data, TEST_STACK_CLASH, SOURCE_ANNOBIN_NOTES, NULL); break; default: - report_i (VERBOSE, "%s: MAYB: (%s): unexpected value for stack-clash note (%x)", - data, sec, note_data, prefer_func_name, value); - tests[TEST_STACK_CLASH].num_maybe ++; + maybe (data, TEST_STACK_CLASH, SOURCE_ANNOBIN_NOTES, "unexpected note value"); + einfo (VERBOSE2, "debug: stack clash note vbalue: %x", value); break; } } @@ -1974,81 +2178,57 @@ walk_build_notes (annocheck_data * data, if (per_file.e_machine != EM_386) break; - if (value != 2 - && skip_check (TEST_STACK_REALIGN, get_component_name (data, sec, note_data, prefer_func_name))) + if (skip_check (TEST_STACK_REALIGN)) break; + if (! includes_gcc (per_file.current_tool) && ! includes_gimple (per_file.current_tool)) + { + skip (data, TEST_STACK_REALIGN, SOURCE_ANNOBIN_NOTES, "Not built by gcc"); + break; + } + switch (value) { - case -1: - report_i (VERBOSE, "%s: MAYB: (%s): unexpected value for stack realign note (%x)", - data, sec, note_data, prefer_func_name, value); - tests[TEST_STACK_REALIGN].num_maybe ++; + default: + maybe (data, TEST_STACK_REALIGN, SOURCE_ANNOBIN_NOTES, "unexpected note value"); + einfo (VERBOSE2, "debug: stack realign note vbalue: %x", value); break; case 0: - report_s (VERBOSE, "%s: FAIL: (%s): Compiled without -fstack-realign", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_STACK_REALIGN].num_fail ++; + fail (data, TEST_STACK_REALIGN, SOURCE_ANNOBIN_NOTES, "-fstack-realign not enabled"); break; case 1: - report_s (VERBOSE2, "%s: PASS: (%s): Compiled with -fstack-realign", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_STACK_REALIGN].num_pass ++; + pass (data, TEST_STACK_REALIGN, SOURCE_ANNOBIN_NOTES, NULL); break; } } else if (streq (attr, "sanitize_cfi")) { - if (value < 1 - && skip_check (TEST_CF_PROTECTION, get_component_name (data, sec, note_data, prefer_func_name))) - break; - - if (! built_by_clang() && ! built_by_mixed ()) - report_s (VERBOSE, "%s: WARN: (%s): Control flow sanitization note found, but the compiler is not clang", - data, sec, note_data, prefer_func_name, NULL); - - if (value < 1) - { - report_s (VERBOSE, "%s: FAIL: (%s): Compiled with insufficient Control Flow sanitization", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_CF_PROTECTION].num_fail ++; - } + if (skip_check (TEST_CF_PROTECTION)) + ; + else if (! includes_clang (per_file.current_tool)) + skip (data, TEST_CF_PROTECTION, SOURCE_ANNOBIN_NOTES, "not built by clang"); + else if (value < 1) + fail (data, TEST_CF_PROTECTION, SOURCE_ANNOBIN_NOTES, "insufficient Control Flow sanitization"); else /* FIXME: Should we check that specific sanitizations are enabled ? */ - { - report_s (VERBOSE2, "%s: PASS: (%s): Compiled with sufficient Control Flow sanitization", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_CF_PROTECTION].num_pass ++; - } + pass (data, TEST_CF_PROTECTION, SOURCE_ANNOBIN_NOTES, NULL); break; } else if (streq (attr, "sanitize_safe_stack")) { - if (value < 1 - && skip_check (TEST_STACK_PROT, get_component_name (data, sec, note_data, prefer_func_name))) - break; - - if (! built_by_clang() && ! built_by_mixed ()) - report_s (VERBOSE, "%s: WARN: (%s): Stack protection sanitization note found, but the compiler is not clang", - data, sec, note_data, prefer_func_name, NULL); - - if (value < 1) - { - report_s (VERBOSE, "%s: FAIL: (%s): Compiled with insufficient Stack Safe sanitization", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_STACK_PROT].num_fail ++; - } + if (skip_check (TEST_STACK_PROT)) + ; + else if (! includes_clang (per_file.current_tool)) + skip (data, TEST_STACK_PROT, SOURCE_ANNOBIN_NOTES, "not built by clang"); + else if (value < 1) + fail (data, TEST_STACK_PROT, SOURCE_ANNOBIN_NOTES, "insufficient Stack Safe sanitization"); else - { - report_s (VERBOSE2, "%s: PASS: (%s): Compiled with sufficient Stack Safe sanitization", - data, sec, note_data, prefer_func_name, NULL); - tests[TEST_STACK_PROT].num_pass ++; - } + pass (data, TEST_STACK_PROT, SOURCE_ANNOBIN_NOTES, NULL); break; } else - einfo (VERBOSE2, "Unsupport annobin note '%s' - ignored", attr); + einfo (VERBOSE2, "Unsupported annobin note '%s' - ignored", attr); break; case 'o': @@ -2058,7 +2238,7 @@ walk_build_notes (annocheck_data * data, /* Fall through. */ default: - einfo (VERBOSE2, "Unsupport annobin note '%s' - ignored", attr); + einfo (VERBOSE2, "Unsupported annobin note '%s' - ignored", attr); break; case GNU_BUILD_ATTRIBUTE_RELRO: @@ -2093,23 +2273,23 @@ vfuture_fail (annocheck_data * data, const char * message) ffail (data, message, VERBOSE); } -static bool +static const char * handle_ppc64_property_note (annocheck_data * data, annocheck_section * sec, ulong type, ulong size, const unsigned char * notedata) { - einfo (VERBOSE, "PPC64 property note handler not yet written...\n"); - return true; + einfo (VERBOSE2, "PPC64 property note handler not yet written...\n"); + return NULL; } -static bool -handle_aarch64_property_note (annocheck_data * data, - annocheck_section * sec, - ulong type, - ulong size, - const unsigned char * notedata) +static const char * +handle_aarch64_property_note (annocheck_data * data, + annocheck_section * sec, + ulong type, + ulong size, + const unsigned char * notedata) { /* These are not defined in the RHEL-7 build environment. */ #ifndef GNU_PROPERTY_AARCH64_FEATURE_1_AND @@ -2121,42 +2301,41 @@ handle_aarch64_property_note (annocheck_data * data, if (type != GNU_PROPERTY_AARCH64_FEATURE_1_AND) { einfo (VERBOSE2, "%s: Ignoring property note type %lx", data->filename, type); - return true; + return NULL; } if (size != 4) { - einfo (VERBOSE, "%s: FAIL: Property note data has invalid size", data->filename); - einfo (VERBOSE2, "debugging: data note at offset %lx has size %lu, expected 4", + einfo (VERBOSE2, "debug: data note at offset %lx has size %lu, expected 4", (long)(notedata - (const unsigned char *) sec->data->d_buf), size); - return false; + return "Property note data has invalid size"; } ulong property = get_4byte_value (notedata); if ((property & GNU_PROPERTY_AARCH64_FEATURE_1_BTI) == 0) { + einfo (VERBOSE2, "debug: property bits = %lx", property); vfuture_fail (data, "The BTI property is not enabled"); - einfo (VERBOSE2, "debugging: property bits = %lx", property); - return false; + return NULL; } if ((property & GNU_PROPERTY_AARCH64_FEATURE_1_PAC) == 0) { + einfo (VERBOSE2, "debug: property bits = %lx", property); vfuture_fail (data, "The PAC property is not enabled"); - einfo (VERBOSE2, "debugging: property bits = %lx", property); - return false; + return NULL; } - einfo (INFO, "%s: PASS: Both the BTI and PAC properties are present in the GNU Property note", data->filename); - return true; + einfo (VERBOSE2, "%s: PASS: Both the BTI and PAC properties are present in the GNU Property note", data->filename); + return NULL; } -static bool -handle_x86_property_note (annocheck_data * data, - annocheck_section * sec, - ulong type, - ulong size, +static const char * +handle_x86_property_note (annocheck_data * data, + annocheck_section * sec, + ulong type, + ulong size, const unsigned char * notedata) { /* These are not defined in the RHEL-7 build environment. */ @@ -2170,49 +2349,48 @@ handle_x86_property_note (annocheck_data * data, if (type != GNU_PROPERTY_X86_FEATURE_1_AND) { einfo (VERBOSE2, "%s: Ignoring property note type %lx", data->filename, type); - return true; + return NULL; } if (size != 4) { - einfo (VERBOSE, "%s: FAIL: Property note data has invalid size", data->filename); - einfo (VERBOSE2, "debugging: data note at offset %lx has size %lu, expected 4", + einfo (VERBOSE2, "debug: data note at offset %lx has size %lu, expected 4", (long)(notedata - (const unsigned char *) sec->data->d_buf), size); - return false; + return "Property note data has invalid size"; } ulong property = get_4byte_value (notedata); if ((property & GNU_PROPERTY_X86_FEATURE_1_IBT) == 0) { - einfo (VERBOSE, "%s: FAIL: The IBT property is not enabled", data->filename); - einfo (VERBOSE2, "debugging: property bits = %lx", property); - return false; + einfo (VERBOSE2, "debug: property bits = %lx", property); + return "The IBT property is not enabled"; } if ((property & GNU_PROPERTY_X86_FEATURE_1_SHSTK) == 0) { - einfo (VERBOSE, "%s: FAIL: The SHSTK property is not enabled", data->filename); - einfo (VERBOSE2, "debugging: property bits = %lx", property); - return false; + einfo (VERBOSE2, "debug: property bits = %lx", property); + return "The SHSTK property is not enabled"; } - einfo (VERBOSE2, "%s: PASS: Both the IBT and SHSTK properties are present in the GNU Property note", data->filename); - return true; + pass (data, TEST_CF_PROTECTION, SOURCE_PROPERTY_NOTES, NULL); + return NULL; } static bool -walk_property_notes (annocheck_data * data, - annocheck_section * sec, - GElf_Nhdr * note, - size_t name_offset, - size_t data_offset, - void * ptr) -{ - if (skip_check (TEST_PROPERTY_NOTE, NULL)) +property_note_checker (annocheck_data * data, + annocheck_section * sec, + GElf_Nhdr * note, + size_t name_offset, + size_t data_offset, + void * ptr) +{ + const char * reason = NULL; + + if (skip_check (TEST_PROPERTY_NOTE)) return true; - bool (* handler) (annocheck_data *, annocheck_section *, ulong, ulong, const unsigned char *); + const char * (* handler) (annocheck_data *, annocheck_section *, ulong, ulong, const unsigned char *); switch (per_file.e_machine) { case EM_X86_64: @@ -2229,24 +2407,23 @@ walk_property_notes (annocheck_data * data, break; default: - /* FIXME: ICE here ? */ + einfo (VERBOSE2, "%s: WARN: Property notes for architecture %d not handled", data->filename, per_file.e_machine); return true; } if (note->n_type != NT_GNU_PROPERTY_TYPE_0) { - einfo (VERBOSE, "%s: FAIL: Unexpected GNU Property note type", data->filename); - einfo (VERBOSE2, "debugging: note type is %x, expected %x", note->n_type, NT_GNU_PROPERTY_TYPE_0); - goto fail; + einfo (VERBOSE2, "%s: info: unexpected GNU Property note type %x", data->filename, note->n_type); + return true; } - if (per_file.e_type == ET_EXEC || per_file.e_type == ET_DYN) + if (is_executable ()) { /* More than one note in an executable is an error. */ - if (tests[TEST_PROPERTY_NOTE].num_pass) + if (tests[TEST_PROPERTY_NOTE].state == STATE_PASSED) { /* The loader will only process the first note, so having more than one is an error. */ - einfo (VERBOSE, "%s: FAIL: More than one GNU Property note", data->filename); + reason = "More than one GNU Property note"; goto fail; } } @@ -2254,22 +2431,22 @@ walk_property_notes (annocheck_data * data, if (note->n_namesz != sizeof ELF_NOTE_GNU || strncmp ((char *) sec->data->d_buf + name_offset, ELF_NOTE_GNU, strlen (ELF_NOTE_GNU)) != 0) { - einfo (VERBOSE, "%s: FAIL: Property note does not have expected name", data->filename); - einfo (VERBOSE2, "debugging: Expected name %s, got %.*s", ELF_NOTE_GNU, + reason = "Property note does not have expected name"; + einfo (VERBOSE2, "debug: Expected name '%s', got '%.*s'", ELF_NOTE_GNU, (int) strlen (ELF_NOTE_GNU), (char *) sec->data->d_buf + name_offset); goto fail; } - unsigned int expected_quanta = data->is_32bit ? 4 : 8; + uint expected_quanta = data->is_32bit ? 4 : 8; if (note->n_descsz < 8 || (note->n_descsz % expected_quanta) != 0) { - einfo (VERBOSE, "%s: FAIL: Property note data has the wrong size", data->filename); - einfo (VERBOSE2, "debugging: Expected data size to be a multiple of %d but the size is 0x%x", + reason = "Property note data has the wrong size"; + einfo (VERBOSE2, "debug: Expected data size to be a multiple of %d but the size is 0x%x", expected_quanta, note->n_descsz); goto fail; } - unsigned int remaining = note->n_descsz; + uint remaining = note->n_descsz; const unsigned char * notedata = sec->data->d_buf + data_offset; while (remaining) { @@ -2280,24 +2457,24 @@ walk_property_notes (annocheck_data * data, notedata += 8; if (size > remaining) { - einfo (VERBOSE, "%s: FAIL: Property note data has invalid size", data->filename); - einfo (VERBOSE2, "debugging: data size for note at offset %lx is %lu but remaining data is only %u", + reason = "Property note data has invalid size"; + einfo (VERBOSE2, "debug: data size for note at offset %lx is %lu but remaining data is only %u", (long)(notedata - (const unsigned char *) sec->data->d_buf), size, remaining); goto fail; } - if (! handler (data, sec, type, size, notedata)) + if ((reason = handler (data, sec, type, size, notedata)) != NULL) goto fail; notedata += ((size + (expected_quanta - 1)) & ~ (expected_quanta - 1)); remaining -= ((size + (expected_quanta - 1)) & ~ (expected_quanta - 1)); } - tests[TEST_PROPERTY_NOTE].num_pass ++; + pass (data, TEST_PROPERTY_NOTE, SOURCE_PROPERTY_NOTES, NULL); return true; fail: - tests[TEST_PROPERTY_NOTE].num_fail ++; + fail (data, TEST_PROPERTY_NOTE, SOURCE_PROPERTY_NOTES, reason); return false; } @@ -2306,7 +2483,9 @@ supports_property_notes (int e_machine) { return e_machine == EM_X86_64 || e_machine == EM_AARCH64 +#if 0 || e_machine == EM_PPC64 +#endif || e_machine == EM_386; } @@ -2316,30 +2495,34 @@ check_note_section (annocheck_data * data, { if (sec->shdr.sh_addralign != 4 && sec->shdr.sh_addralign != 8) { - einfo (WARN, "%s: note section %s not properly aligned (alignment: %ld)", + einfo (INFO, "%s: WARN: note section %s not properly aligned (alignment: %ld)", data->filename, sec->secname, (long) sec->shdr.sh_addralign); } if (const_strneq (sec->secname, GNU_BUILD_ATTRS_SECTION_NAME)) { - hardened_note_data hard_data; - - hard_data.start = 0; - hard_data.end = 0; + bool res; per_file.build_notes_seen = true; - return annocheck_walk_notes (data, sec, walk_build_notes, (void *) & hard_data); + per_file.note_data.start = per_file.note_data.end = 0; + per_file.seen_tools = TOOL_UNKNOWN; + + res = annocheck_walk_notes (data, sec, build_note_checker, NULL); + + per_file.component_name = NULL; + if (per_file.note_data.start != per_file.note_data.end) + add_producer (data, per_file.current_tool, 0, "annobin notes", false); + return res; } - if (supports_property_notes (per_file.e_machine) - && streq (sec->secname, ".note.gnu.property")) + if (streq (sec->secname, ".note.gnu.property")) { - return annocheck_walk_notes (data, sec, walk_property_notes, NULL); + return annocheck_walk_notes (data, sec, property_note_checker, NULL); } if (streq (sec->secname, ".note.go.buildid")) { - set_producer (data, TOOL_GO, 0, ".note.go.buildid"); + add_producer (data, TOOL_GO, 0, ".note.go.buildid", true); } return true; @@ -2353,7 +2536,7 @@ check_string_section (annocheck_data * data, This is not as accurate as checking for a function symbol with this name, but it is a lot faster. */ if (strstr ((const char *) sec->data->d_buf, "__pthread_register_cancel")) - tests[TEST_THREADS].num_fail ++; + fail (data, TEST_THREADS, SOURCE_STRING_SECTION, "not compiled with -fexceptions"); return true; } @@ -2381,23 +2564,22 @@ static bool check_dynamic_section (annocheck_data * data, annocheck_section * sec) { + bool dynamic_relocs_seen = false; + bool aarch64_bti_plt_seen = false; + bool aarch64_pac_plt_seen = false; + if (sec->shdr.sh_size == 0 || sec->shdr.sh_entsize == 0) { einfo (VERBOSE, "%s: WARN: Dynamic section %s is empty - ignoring", data->filename, sec->secname); return true; } - if (tests[TEST_DYNAMIC_SEGMENT].num_pass == 0) - { - tests[TEST_DYNAMIC_SEGMENT].num_pass = 1; - } - else - { - einfo (VERBOSE, "%s: FAIL: contains multiple dynamic sections", data->filename); - tests[TEST_DYNAMIC_SEGMENT].num_fail ++; - } + if (tests[TEST_DYNAMIC_SEGMENT].state == STATE_UNTESTED) + pass (data, TEST_DYNAMIC_SEGMENT, SOURCE_DYNAMIC_SECTION, NULL); + else if (tests[TEST_DYNAMIC_SEGMENT].state == STATE_PASSED) + fail (data, TEST_DYNAMIC_SEGMENT, SOURCE_DYNAMIC_SECTION, "multiple dynamic sections detected"); - size_t num_entries = sec->shdr.sh_size == 0 / sec->shdr.sh_entsize; + size_t num_entries = sec->shdr.sh_size / sec->shdr.sh_entsize; /* Walk the dynamic tags. */ while (num_entries --) @@ -2411,48 +2593,63 @@ check_dynamic_section (annocheck_data * data, switch (dyn->d_tag) { case DT_BIND_NOW: - tests[TEST_BIND_NOW].num_pass ++; + pass (data, TEST_BIND_NOW, SOURCE_DYNAMIC_SECTION, NULL); break; case DT_FLAGS: if (dyn->d_un.d_val & DF_BIND_NOW) - tests[TEST_BIND_NOW].num_pass ++; + pass (data, TEST_BIND_NOW, SOURCE_DYNAMIC_SECTION, NULL); break; case DT_RELSZ: case DT_RELASZ: - if (dyn->d_un.d_val > 0) - tests[TEST_BIND_NOW].num_maybe ++; + if (dyn->d_un.d_val == 0) + skip (data, TEST_BIND_NOW, SOURCE_DYNAMIC_SECTION, "no dynamic relocations"); + else + dynamic_relocs_seen = true; break; case DT_TEXTREL: - tests[TEST_TEXTREL].num_fail ++; + if (is_object_file ()) + skip (data, TEST_TEXTREL, SOURCE_DYNAMIC_SECTION, "Object files are allowed text relocations"); + else + fail (data, TEST_TEXTREL, SOURCE_DYNAMIC_SECTION, NULL); break; case DT_RPATH: + { + if (skip_check (TEST_RUN_PATH)) + break; + + const char * path = elf_strptr (data->elf, sec->shdr.sh_link, dyn->d_un.d_val); + + if (not_rooted_at_usr (path)) + fail (data, TEST_RUN_PATH, SOURCE_DYNAMIC_SECTION, NULL); + else + vfuture_fail (data, "The RPATH dynamic tag is deprecated. Link with --enable-new-dtags to use RUNPATH instead"); + } + break; + case DT_RUNPATH: { - if (skip_check (TEST_RUN_PATH, NULL)) + if (skip_check (TEST_RUN_PATH)) break; const char * path = elf_strptr (data->elf, sec->shdr.sh_link, dyn->d_un.d_val); if (not_rooted_at_usr (path)) - { - einfo (VERBOSE, "%s: FAIL: Bad runpath: %s", data->filename, path); - tests[TEST_RUN_PATH].num_fail ++; - } + fail (data, TEST_RUN_PATH, SOURCE_DYNAMIC_SECTION, NULL); + else + pass (data, TEST_RUN_PATH, SOURCE_DYNAMIC_SECTION, NULL); } break; case DT_AARCH64_BTI_PLT: - if (per_file.e_machine == EM_AARCH64) - tests[TEST_DYNAMIC_TAGS].num_pass |= 1; + aarch64_bti_plt_seen = true; break; case DT_AARCH64_PAC_PLT: - if (per_file.e_machine == EM_AARCH64) - tests[TEST_DYNAMIC_TAGS].num_pass |= 2; + aarch64_pac_plt_seen = true; break; default: @@ -2460,6 +2657,44 @@ check_dynamic_section (annocheck_data * data, } } + if (dynamic_relocs_seen && tests[TEST_BIND_NOW].state != STATE_PASSED) + { + if (! is_executable ()) + skip (data, TEST_BIND_NOW, SOURCE_DYNAMIC_SECTION, "not an executable"); + else if (per_file.seen_tools & TOOL_GO) + /* FIXME: Should be changed once GO supports PIE & BIND_NOW. */ + skip (data, TEST_BIND_NOW, SOURCE_DYNAMIC_SECTION, "binary was built by GO"); + else + fail (data, TEST_BIND_NOW, SOURCE_DYNAMIC_SECTION, "not linked with -Wl,-z,now"); + } + + if (per_file.e_machine == EM_AARCH64) + { + if (is_object_file ()) + skip (data, TEST_DYNAMIC_TAGS, SOURCE_DYNAMIC_SECTION, "not needed in object files"); + else + { + uint res = aarch64_bti_plt_seen ? 1 : 0; + + res += aarch64_pac_plt_seen ? 2 : 0; + switch (res) + { + case 0: + future_fail (data, "BTI_PLT and PAC_PLT tags missing from dynamic tags"); + break; + case 1: + future_fail (data, "PAC_PLT tag is missing from dynamic tags"); + break; + case 2: + future_fail (data, "BTI_PLT tag is missing from dynamic tags"); + break; + case 3: + pass (data, TEST_DYNAMIC_TAGS, SOURCE_DYNAMIC_SECTION, NULL); + break; + } + } + } + return true; } @@ -2485,22 +2720,22 @@ check_code_section (annocheck_data * data, static const char * gcc_prefix = "GCC: (GNU) "; static const char * clang_prefix = "clang version "; static const char * lld_prefix = "Linker: LLD "; - unsigned int version; + uint version; const char * where; if ((where = strstr (tool, gcc_prefix)) != NULL) { /* FIXME: This assumes that the gcc identifier looks like: "GCC: (GNU) 8.1.1"" */ - version = (unsigned int) strtod (where + strlen (gcc_prefix), NULL); - set_producer (data, TOOL_GCC, version, "comment section"); + version = (uint) strtod (where + strlen (gcc_prefix), NULL); + add_producer (data, TOOL_GCC, version, COMMENT_SECTION, true); einfo (VERBOSE2, "%s: built by gcc version %u (extracted from '%s' in comment section)", data->filename, version, where); } else if ((where = strstr (tool, clang_prefix)) != NULL) { /* FIXME: This assumes that the clang identifier looks like: "clang version 7.0.1"" */ - version = (unsigned int) strtod (where + strlen (clang_prefix), NULL); - set_producer (data, TOOL_CLANG, version, "comment section"); + version = (uint) strtod (where + strlen (clang_prefix), NULL); + add_producer (data, TOOL_CLANG, version, COMMENT_SECTION, true); einfo (VERBOSE2, "%s: built by clang version %u (extracted from '%s' in comment section)", data->filename, version, where); } @@ -2539,78 +2774,71 @@ check_sec (annocheck_data * data, } static bool +is_shared_lib (annocheck_data * data) +{ + /* FIXME: Need a better test. */ + return strstr (data->filename, ".so") != NULL; +} + +static bool interesting_seg (annocheck_data * data, annocheck_segment * seg) { if (disabled) return false; - if ((seg->phdr->p_flags & (PF_X | PF_W | PF_R)) == (PF_X | PF_W | PF_R) - && ! skip_check (TEST_RWX_SEG, NULL)) + if (! skip_check (TEST_RWX_SEG)) { - if (seg->phdr->p_type == PT_GNU_STACK) - { - einfo (VERBOSE, "%s: FAIL: The GNU stack segment is executable\n", - data->filename); - tests[TEST_GNU_STACK].num_fail ++; - } - else + if ((seg->phdr->p_flags & (PF_X | PF_W | PF_R)) == (PF_X | PF_W | PF_R)) { - einfo (VERBOSE, "%s: FAIL: seg %d has Read, Write and eXecute flags\n", - data->filename, seg->number); - tests[TEST_RWX_SEG].num_fail ++; + /* Object files should not have segments. */ + assert (! is_object_file ()); + fail (data, TEST_RWX_SEG, SOURCE_SEGMENT_HEADERS, "Segment has Read, Write and eXecute flags set"); + einfo (VERBOSE2, "RWX segment number: %d", seg->number); } } switch (seg->phdr->p_type) { - case PT_INTERP: - tests[TEST_ENTRY].num_maybe ++; - break; - case PT_GNU_RELRO: - tests[TEST_GNU_RELRO].num_pass ++; + pass (data, TEST_GNU_RELRO, SOURCE_SEGMENT_HEADERS, NULL); break; case PT_GNU_STACK: - if ((seg->phdr->p_flags & (PF_W | PF_R)) != (PF_W | PF_R)) + if (! skip_check (TEST_GNU_STACK)) { - einfo (VERBOSE, "%s: FAIL: The GNU stack segment does not have both read & write permissions\n", - data->filename); - tests[TEST_GNU_STACK].num_fail ++; + if ((seg->phdr->p_flags & (PF_W | PF_R)) != (PF_W | PF_R)) + fail (data, TEST_GNU_STACK, SOURCE_SEGMENT_HEADERS, "The GNU stack segment does not have both read & write permissions"); + /* If the segment has the PF_X flag set it will have been reported as a failure above. */ + else if ((seg->phdr->p_flags & PF_X) == 0) + pass (data, TEST_GNU_STACK, SOURCE_SEGMENT_HEADERS, NULL); } - /* If the segment has the PF_X flag set it will have been reported as a failure above. */ - else if ((seg->phdr->p_flags & PF_X) == 0) - tests[TEST_GNU_STACK].num_pass ++; break; case PT_DYNAMIC: - if (tests[TEST_DYNAMIC_SEGMENT].num_pass < 2) - /* 0 means it had no dynamic sections, 1 means it had a dynamic section. */ - tests[TEST_DYNAMIC_SEGMENT].num_pass = 2; - else - { - einfo (VERBOSE, "FAIL: %s: contains multiple dynamic segments.", data->filename); - tests[TEST_DYNAMIC_SEGMENT].num_fail ++; - } + pass (data, TEST_DYNAMIC_SEGMENT, SOURCE_SEGMENT_HEADERS, NULL); + /* FIXME: We do not check to see if there is a second dynamic segment. + Checking is complicated by the fact that there can be both a dynamic + segment and a dynamic section. */ break; case PT_NOTE: - if (skip_check (TEST_PROPERTY_NOTE, NULL)) + if (skip_check (TEST_PROPERTY_NOTE)) break; - /* We true if we want to examine the note segments. */ + /* We return true if we want to examine the note segments. */ return supports_property_notes (per_file.e_machine); case PT_LOAD: /* If we are checking the entry point instruction then we need to load the segment. We check segments rather than sections because executables do not have to have sections. */ - if ((per_file.e_type == ET_EXEC || per_file.e_type == ET_DYN) - && (per_file.e_machine == EM_386 || per_file.e_machine == EM_X86_64) + if (per_file.e_type == ET_DYN + && is_x86 () + && ! is_shared_lib (data) && seg->phdr->p_memsz > 0 && seg->phdr->p_vaddr <= per_file.e_entry && seg->phdr->p_vaddr + seg->phdr->p_memsz > per_file.e_entry - && ! skip_check (TEST_ENTRY, NULL)) + && ! skip_check (TEST_ENTRY)) return true; break; @@ -2639,16 +2867,24 @@ check_seg (annocheck_data * data, assert (entry_point + 3 < seg->data->d_size); memcpy (entry_bytes, seg->data->d_buf + entry_point, sizeof entry_bytes); - if (per_file.e_machine == EM_386) + if (tests[TEST_ENTRY].state == STATE_MAYBE) + ; /* A signal from interesting_seg() that this is interpreted code. */ + else if (per_file.e_machine == EM_386) { /* Look for ENDBR32: 0xf3 0x0f 0x1e 0xfb. */ if ( entry_bytes[0] == 0xf3 && entry_bytes[1] == 0x0f && entry_bytes[2] == 0x1e && entry_bytes[3] == 0xfb) - tests[TEST_ENTRY].num_pass ++; + pass (data, TEST_ENTRY, SOURCE_SEGMENT_CONTENTS, NULL); else - tests[TEST_ENTRY].num_fail ++; + { + fail (data, TEST_ENTRY, SOURCE_SEGMENT_CONTENTS, "instruction at entry is not ENDBR32"); + + einfo (VERBOSE, "%s: info: entry address: %#lx. Bytes at this address: %x %x %x %x", + data->filename, (long) per_file.e_entry, + entry_bytes[0], entry_bytes[1], entry_bytes[2], entry_bytes[3]); + } } else /* per_file.e_machine == EM_X86_64 */ { @@ -2657,18 +2893,30 @@ check_seg (annocheck_data * data, && entry_bytes[1] == 0x0f && entry_bytes[2] == 0x1e && entry_bytes[3] == 0xfa) - tests[TEST_ENTRY].num_pass ++; + pass (data, TEST_ENTRY, SOURCE_SEGMENT_CONTENTS, NULL); else - tests[TEST_ENTRY].num_fail ++; + { + fail (data, TEST_ENTRY, SOURCE_SEGMENT_CONTENTS, "instruction at entry is not ENDBR64"); + + einfo (VERBOSE, "%s: info: entry address: %#lx. Bytes at this address: %x %x %x %x", + data->filename, (long) per_file.e_entry, + entry_bytes[0], entry_bytes[1], entry_bytes[2], entry_bytes[3]); + } } return true; } + if (seg->phdr->p_type != PT_NOTE) + return true; + if (per_file.e_machine != EM_X86_64) return true; - /* FIXME: Only run these checks if the note section is missing ?? */ + if (skip_check (TEST_PROPERTY_NOTE)) + return true; + + /* FIXME: Only run these checks if the note section is missing ? */ GElf_Nhdr note; size_t name_off; @@ -2681,68 +2929,31 @@ check_seg (annocheck_data * data, { if (seg->phdr->p_align != 4) { - einfo (VERBOSE, "%s: warn: Note segment not 4 or 8 byte aligned (alignment: %ld)", - data->filename, (long) seg->phdr->p_align); - tests[TEST_PROPERTY_NOTE].num_fail ++; + fail (data, TEST_PROPERTY_NOTE, SOURCE_SEGMENT_CONTENTS, "Note segment not 4 or 8 byte aligned"); + einfo (VERBOSE2, "debug: note segment alignment: %ld", (long) seg->phdr->p_align); } - - if (note.n_type == NT_GNU_PROPERTY_TYPE_0) + else if (note.n_type == NT_GNU_PROPERTY_TYPE_0) { - warn (data, "GNU Property note segment not 8 byte aligned"); - tests[TEST_PROPERTY_NOTE].num_fail ++; + fail (data, TEST_PROPERTY_NOTE, SOURCE_SEGMENT_CONTENTS, "GNU Property note segment not 8 byte aligned"); } } if (note.n_type == NT_GNU_PROPERTY_TYPE_0) { if (offset != 0) - { - warn (data, "More than one GNU Property note in note segment"); - tests[TEST_PROPERTY_NOTE].num_fail ++; - } + fail (data, TEST_PROPERTY_NOTE, SOURCE_SEGMENT_CONTENTS, "More than one GNU Property note in note segment"); else - tests[TEST_PROPERTY_NOTE].num_pass ++; + /* FIXME: We should check the contents of the note. */ + pass (data, TEST_PROPERTY_NOTE, SOURCE_SEGMENT_CONTENTS, NULL); } return true; } -static void -fail (annocheck_data * data, const char * message) -{ - einfo (INFO, "%s: FAIL: %s", data->filename, message); - ++ per_file.num_fails; -} - -static void -maybe (annocheck_data * data, const char * message) -{ - einfo (INFO, "%s: MAYB: %s", data->filename, message); - ++ per_file.num_maybes; -} - -static void -pass (annocheck_data * data, const char * message) -{ - einfo (VERBOSE, "%s: PASS: %s", data->filename, message); -} - -static void -skip (annocheck_data * data, const char * message) -{ - einfo (VERBOSE, "%s: skip: %s", data->filename, message); -} - -static void -look (annocheck_data * data, const char * message) -{ - future_fail (data, message); -} - /* Returns true if GAP is one that can be ignored. */ static bool -ignore_gap (annocheck_data * data, hardened_note_data * gap) +ignore_gap (annocheck_data * data, note_range * gap) { Elf_Scn * addr1_scn = NULL; Elf_Scn * addr2_scn = NULL; @@ -2878,8 +3089,8 @@ ignore_gap (annocheck_data * data, hardened_note_data * gap) static signed int compare_range (const void * r1, const void * r2) { - hardened_note_data * n1 = (hardened_note_data *) r1; - hardened_note_data * n2 = (hardened_note_data *) r2; + note_range * n1 = (note_range *) r1; + note_range * n2 = (note_range *) r2; if (n1->end < n2->start) return -1; @@ -2909,16 +3120,29 @@ compare_range (const void * r1, const void * r2) static bool skip_gap_sym (const char * sym) { + if (sym == NULL) + return false; + /* G++ will generate virtual and non-virtual thunk functions all on its own, without telling the annobin plugin about them. Detect them here and do not complain about the gap in the coverage. */ if (const_strneq (sym, "_ZThn") || const_strneq (sym, "_ZTv0")) return true; + /* The GO infrastructure is not annotated. */ + if (const_strneq (sym, "internal/cpu.Initialize")) + return true; + /* If the symbol is for a function/file that we know has special reasons for not being proplerly annotated then we skip it. */ - if (skip_check (TEST_MAX, sym)) - return true; + const char * saved_sym = per_file.component_name; + per_file.component_name = sym; + if (skip_check (TEST_MAX)) + { + per_file.component_name = saved_sym; + return true; + } + per_file.component_name = saved_sym; if (per_file.e_machine == EM_386) { @@ -2964,13 +3188,13 @@ check_for_gaps (annocheck_data * data) /* Sort the ranges array. */ qsort (ranges, next_free_range, sizeof ranges[0], compare_range); - hardened_note_data current = ranges[0]; + note_range current = ranges[0]; /* Scan the ranges array. */ bool gap_found = false; - unsigned i; + uint i; for (i = 1; i < next_free_range; i++) - { + { if (ranges[i].start <= current.end) { if (ranges[i].start < current.start) @@ -2988,7 +3212,7 @@ check_for_gaps (annocheck_data * data) } else { - hardened_note_data gap; + note_range gap; gap.start = current.end; gap.end = ranges[i].start; @@ -3070,868 +3294,360 @@ check_for_gaps (annocheck_data * data) sym = cpsym; } - einfo (VERBOSE, "%s: gap: (%lx..%lx probable component: %s) in annobin notes", + einfo (VERBOSE, "%s: gap: (%#lx..%#lx probable component: %s) in annobin notes", data->filename, gap.start, gap.end, sym); free ((char *) cpsym); } else - einfo (VERBOSE, "%s: gap: (%lx..%lx) in annobin notes", + einfo (VERBOSE, "%s: gap: (%#lx..%#lx) in annobin notes", data->filename, gap.start, gap.end); } } if (! gap_found) - pass (data, "No gaps found"); - else if (! BE_VERBOSE) - fail (data, "Gaps were detected in the annobin coverage. Run with -v to list"); - else - fail (data, "Gaps were detected in the annobin coverage"); -} - -static void -show_BRANCH_PROTECTION (annocheck_data * data, test * results) -{ -#ifdef EM_AARCH64 /* RHEL-6 does not define EM_AARCH64. */ - if (per_file.e_machine != EM_AARCH64) - skip (data, "Branch protection. (Not an AArch64 binary)"); + pass (data, TEST_NOTES, SOURCE_ANNOBIN_NOTES, "no gaps found"); else -#endif - if (! built_by_gcc ()) - skip (data, "Branch protection. (Not built by gcc)"); - - else if (per_file.tool_version < 9) - skip (data, "Branch protection. (Needs gcc 9+)"); + fail (data, TEST_NOTES, SOURCE_ANNOBIN_NOTES, "gaps were detected in the annobin coverage"); - else if (results->num_fail > 0) + /* Now check to see that the notes covered the whole of the .text section. */ + /* FIXME: We should actually do this for an executable section. */ + + /* Scan forward through the ranges array looking for overlaps with the start of the .text section. */ + if (per_file.text_section_range.end != 0) { - if (results->num_pass > 0 || results->num_maybe > 0) + for (i = 0; i < next_free_range; i++) { - if (BE_VERBOSE) - fail (data, "Parts of the binary were compiled without (sufficient) branch protection"); - else - fail (data, "Parts of the binary were compiled without branch protection. Run with -v to see where"); + if (ranges[i].start <= per_file.text_section_range.start + && ranges [i].end > per_file.text_section_range.start) + /* We have found a note range the occludes the start of the text section. + Move the start up to the end of this note, aligned to 16 bytes. */ + { + per_file.text_section_range.start = align (ranges[i].end, 16); + if (per_file.text_section_range.start >= per_file.text_section_range.end) + { + per_file.text_section_range.start = per_file.text_section_range.end = 0; + break; + } + } } - else - fail (data, "The binary was compiled without -mbranch-protection"); - } - else if (results->num_maybe > 0) - { - if (BE_VERBOSE) - maybe (data, "Unknown string used with -mbranch-protection="); - else - maybe (data, "Unknown string used with -mbranch-protection= run with -v to see where"); } - else if (results->num_pass > 0) + + /* Now scan backwards through the ranges array looking for overlaps with the end of the .text section. */ + if (per_file.text_section_range.end != 0) { - pass (data, "Compiled with sufficient -mbranch-protection"); + for (i = next_free_range; i--;) + { + if (ranges[i].start < per_file.text_section_range.end + && align (ranges [i].end, 16) >= per_file.text_section_range.end) + /* We have found a note range the occludes the end of the text section. + Move the end up to the start of this note, aligned to 16 bytes. */ + { + per_file.text_section_range.end = align (ranges[i].start - 15, 16); + if (per_file.text_section_range.start >= per_file.text_section_range.end) + { + per_file.text_section_range.start = per_file.text_section_range.end = 0; + break; + } + } + } } - else + + if (per_file.text_section_range.end > 0) { - /* FIXME: Only inform the user for now. Once -mbranch-protection has - been added to the rpm macros then change this result to a maybe(). */ - /* maybe (data, "The -mbranch-protection setting was not recorded"); */ - look (data, "The -mbranch-protection setting was not recorded"); + /* This test does not account for ranges that occlude part + of the .text section, so make it an INFO result for now. + Nor does it allow for linker generated code that have no notes. */ + einfo (VERBOSE, "%s: info: not all of the .text section is covered by notes", + data->filename); + einfo (VERBOSE, "%s: info: addr range not covered: %lx..%lx", + data->filename, per_file.text_section_range.start, per_file.text_section_range.end); } } -static void -show_ENTRY (annocheck_data * data, test * results) -{ - if (per_file.e_machine != EM_386 && per_file.e_machine != EM_X86_64) - skip (data, "Entry point instruction is ENDBR. (Not an x86 binary)"); - else if (per_file.e_type != ET_DYN && per_file.e_type != ET_EXEC) - skip (data, "Entry point instruction is ENDBR. (Not a dynamic executable)"); - else if (results->num_maybe == 0) /* Ie there was no PT_INTERP segment. */ - skip (data, "Entry point instruction is ENDBR. (Not an executable)"); - else if (! per_file.compiled_code_seen) - skip (data, "Entry point not ENDBR, but code not produced by a known compiler"); - else if (per_file.e_entry == 0) - maybe (data, "Entry point address is zero"); - else if (results->num_fail > 0) - { - if (per_file.e_machine == EM_386) - fail (data, "Entry point instruction is not ENDBR32"); - else - fail (data, "Entry point instruction is not ENDBR64"); - - if (BE_VERBOSE) - einfo (VERBOSE, "%s: (Entry Address: %#lx. Bytes at this address: %x %x %x %x)", - data->filename, (long) per_file.e_entry, - entry_bytes[0], entry_bytes[1], entry_bytes[2], entry_bytes[3]); - } - else - pass (data, "Entry point instruction is ENDBR"); -} -static void -show_SHORT_ENUM (annocheck_data * data, test * results) +static bool +finish (annocheck_data * data) { - if (results->num_fail > 0 && results->num_pass > 0) - { - if (BE_VERBOSE) - fail (data, "Linked with different -fshort-enum settings"); - else - fail (data, "Linked with different -fshort-enum settings. Run with -v to see where"); - } - else if (results->num_maybe > 0) - maybe (data, "Corrupt notes on the -fshort-enum setting detected"); - else if (results->num_fail > 0 || results->num_pass > 0) - pass (data, "Consistent use of the -fshort-enum option"); - else if (! built_by_gcc ()) - skip (data, "Test of enum size. (Not built by gcc)"); - else - /* Use SKIP rather than MAYBE here as this is not critical. */ - skip (data, "No data about the use of -fshort-enum available"); -} + if (disabled || per_file.debuginfo_file) + return true; -static void -show_WARNINGS (annocheck_data * data, test * results) -{ - if (results->num_fail > 0) - { - if (built_with_gimple ()) - skip (data, "Checking for warning options. (Gimple compilation drops warnings)"); - else if (BE_VERBOSE) - fail (data, "Compiled without using either the -Wall or -Wformat-security options"); - else - fail (data, "Compiled without using either the -Wall or -Wformat-security options. Run with -v to see where"); - } - else if (results->num_maybe > 0) - { - maybe (data, "Corrupted warning data encountered"); - } - else if (results->num_pass == 0) + if (! per_file.build_notes_seen + /* NB/ This code must happen after the call to annocheck_walk_dwarf() + as that function is responsible for following links to debuginfo + files. */ + && data->dwarf_filename != NULL + && data->dwarf_fd != data->fd) { - if (built_with_gimple ()) - skip (data, "Checking for warning options. (Built by gimple)"); - else if (! built_by_compiler ()) - skip (data, "Checking for warning options. (Not built by gcc)"); - else - maybe (data, "No data about compilation warnings found"); + struct checker hardened_notechecker = + { + HARDENED_CHECKER_NAME, + NULL, /* start_file */ + interesting_note_sec, + check_note_section, + NULL, /* interesting_seg */ + NULL, /* check_seg */ + NULL, /* end_file */ + NULL, /* process_arg */ + NULL, /* usage */ + NULL, /* version */ + NULL, /* start_scan */ + NULL, /* end_scan */ + NULL, /* internal */ + }; + + /* There is a separate debuginfo file. Scan it to see if there are any notes that we can use. */ + einfo (VERBOSE2, "%s: info: running subchecker on %s", data->filename, data->dwarf_filename); + annocheck_process_extra_file (& hardened_notechecker, data->dwarf_filename, data->filename, data->dwarf_fd); } - else - pass (data, "Compiled with either -Wall and/or -Wformat-security"); -} -static void -show_PROPERTY_NOTE (annocheck_data * data, test * results) -{ - if (! supports_property_notes (per_file.e_machine)) - skip (data, "GNU Property note check. (Only useful on x86_64 and aarch64 binaries)"); + if (! per_file.build_notes_seen && is_C_compiler (per_file.seen_tools)) + fail (data, TEST_NOTES, SOURCE_ANNOBIN_NOTES, "Annobin notes were not found"); - else if (results->num_fail > 0) + if (! ignore_gaps) { - if (per_file.e_machine == EM_AARCH64) - look (data, "Bad GNU Property note(s)"); - else if (BE_VERBOSE) - fail (data, "Bad GNU Property note(s)"); + if (is_object_file ()) + einfo (VERBOSE, "%s: Not checking for gaps (object file)", data->filename); + else if (! is_C_compiler (per_file.seen_tools) && ! includes_assembler (per_file.seen_tools)) + einfo (VERBOSE, "%s: Not checking for gaps (binary created by a tool without an annobin plugin)", + data->filename); else - fail (data, "Bad GNU Property note(s). Run with -v to see what is wrong"); + check_for_gaps (data); } - else if (results->num_maybe > 0) - maybe (data, "Corrupt GNU Property note"); + if (per_file.seen_tools == TOOL_UNKNOWN) + per_file.seen_tools = per_file.current_tool; - else if (results->num_pass > 0) - pass (data, "Good GNU Property note"); - - else + int i; + for (i = 0; i < TEST_MAX; i++) { - switch (per_file.e_machine) + if (! tests[i].enabled) + continue; + + if (tests[i].state == STATE_UNTESTED) { - case EM_X86_64: - case EM_386: - if (tests[TEST_CF_PROTECTION].enabled && tests[TEST_CF_PROTECTION].num_pass > 0) + switch (i) { - if (! built_by_compiler ()) - skip (data, "Control flow protection is enabled, but some parts of the binary have been created by a tool other than GCC or CLANG, and so do not have the necessary markup. This means that Intel's control flow protection technology (CET) will *not* be enabled for any part of the binary"); + case TEST_GNU_STACK: + case TEST_NOTES: + case TEST_LTO: + case TEST_ENTRY: + case TEST_SHORT_ENUM: + case TEST_DYNAMIC_SEGMENT: + case TEST_RUN_PATH: + case TEST_RWX_SEG: + case TEST_TEXTREL: + case TEST_THREADS: + case TEST_WRITEABLE_GOT: + /* The absence of a result for these tests actually means that they have passed. */ + pass (data, i, SOURCE_FINAL_SCAN, NULL); + break; + + case TEST_BIND_NOW: + if (! is_executable ()) + skip (data, i, SOURCE_FINAL_SCAN, "only needed for executables"); + else if (tests[TEST_DYNAMIC_SEGMENT].state == STATE_UNTESTED) + skip (data, i, SOURCE_FINAL_SCAN, "no dynamic segment present"); else - fail (data, "Control flow protection has been enabled for only some parts of the binary. Other parts (probably assembler sources) are missing the protection, and without it global control flow protection cannot be enabled"); - } - break; + skip (data, i, SOURCE_FINAL_SCAN, "no dynamic relocs found"); + break; - case EM_AARCH64: - if (tests[TEST_BRANCH_PROTECTION].enabled && tests[TEST_BRANCH_PROTECTION].num_pass > 0) - { - if (! built_by_compiler ()) - skip (data, "Branch protection is enabled, but some parts of the binary have been created by a tool other than GCC or CLANG, and so do not have the necessary markup. This means that the BTI/PAC protection will *not* be enabled for any part of the binary"); + case TEST_GNU_RELRO: + if (is_object_file ()) + skip (data, i, SOURCE_FINAL_SCAN, "not needed in object files"); + else if (tests[TEST_DYNAMIC_SEGMENT].state == STATE_UNTESTED) + skip (data, i, SOURCE_FINAL_SCAN, "no dynamic segment present"); + else if (tests [TEST_BIND_NOW].state == STATE_UNTESTED) + skip (data, i, SOURCE_FINAL_SCAN, "no dynamic relocations"); + else if (per_file.seen_tools & TOOL_GO) + /* FIXME: This is for GO binaries. Should be changed once GO supports PIE & BIND_NOW. */ + skip (data, i, SOURCE_FINAL_SCAN, "built by GO"); else - look (data, "branch protection has been enabled for only some parts of the binary. Other parts (probably assembler sources) are missing the protection, and without it global BTI/PAC protection cannot be enabled"); - } - break; + fail (data, i, SOURCE_FINAL_SCAN, "not linked with -Wl,-z,relro"); + break; - case EM_PPC64: - look (data, "Missing GNU Property note (specific to PowerPC)"); - break; + case TEST_DYNAMIC_TAGS: + if (per_file.e_machine != EM_AARCH64) + skip (data, i, SOURCE_FINAL_SCAN, "AArch64 specific"); + else if (is_object_file ()) + skip (data, i, SOURCE_FINAL_SCAN, "not needed in object files"); + else + future_fail (data, "no dynamic tags found"); + break; - default: - fail (data, "ICE: property notes for this architecture not handled"); - break; - } - } -} + case TEST_GLIBCXX_ASSERTIONS: + if (per_file.lang != LANG_UNKNOWN && per_file.lang != LANG_CXX) + { + skip (data, i, SOURCE_FINAL_SCAN, "source language not C++"); + break; + } + /* Fall through. */ + case TEST_WARNINGS: + case TEST_FORTIFY: + if (tests[TEST_LTO].state == STATE_PASSED) + { + skip (data, i, SOURCE_FINAL_SCAN, "compiling in LTO mode hides preprocessor and warning options"); + break; + } + else if (is_C_compiler (per_file.seen_tools)) + { + fail (data, i, SOURCE_FINAL_SCAN, "no indication that the necessary option was used"); + break; + } + else if (per_file.current_tool == TOOL_GO) + { + skip (data, i, SOURCE_FINAL_SCAN, "GO compilation does not use the C preprocessor"); + break; + } + /* Fall through. */ + default: + /* Do not complain about compiler specific tests being missing + if all that we have seen is assembler produced code. */ + if (per_file.seen_tools == TOOL_GAS + || (per_file.gcc_from_comment && per_file.seen_tools == (TOOL_GAS | TOOL_GCC))) + skip (data, i, SOURCE_FINAL_SCAN, "no compiled code found"); + /* There may be notes on this test, but the are for a zero-length range. */ + else + maybe (data, i, SOURCE_FINAL_SCAN, "no valid notes found regarding this test"); + break; -static void -show_BIND_NOW (annocheck_data * data, test * results) -{ - if (per_file.e_type != ET_EXEC && per_file.e_type != ET_DYN) - skip (data, "Test for -Wl,-z,now. (Only needed for executables)"); - else if (tests[TEST_DYNAMIC_SEGMENT].num_pass == 0) - skip (data, "Test for -Wl,-z,now. (No dynamic segment present)"); - else if (results->num_maybe == 0) - skip (data, "Test for -Wl,-z-now. (Dynamic segment present, but no dynamic relocations found)"); - else if (per_file.tool == TOOL_GO) - /* FIXME: This is for GO binaries. Should be changed once GO supports PIE & BIND_NOW. */ - skip (data, "Test for -Wl,-z,now. (Binary was built by GO)"); - else if (built_by_mixed ()) - /* FIXME: Should be changed once GO supports PIE & BIND_NOW. */ - skip (data, "Test for -Wl,-z,now. (Binary was built by different compilers)"); - else if (results->num_pass == 0 || results->num_fail > 0) - fail (data, "Not linked with -Wl,-z,now"); - else - pass (data, "Linked with -Wl,-z,now"); -} + case TEST_PIC: + if (per_file.current_tool == TOOL_GO) + skip (data, i, SOURCE_FINAL_SCAN, "GO does not support a -fPIC option"); + else if (is_C_compiler (per_file.seen_tools)) + maybe (data, i, SOURCE_FINAL_SCAN, "no valid notes found regarding this test"); + else + skip (data, i, SOURCE_FINAL_SCAN, "not compiled code"); + break; -static void -show_DYNAMIC_SEGMENT (annocheck_data * data, test * results) -{ - if (results->num_fail > 0) - fail (data, "Multiple dynamic sections/segments found"); - else if (results->num_pass == 0) - pass (data, "No dynamic sections/segments found"); - else - pass (data, "One dynamic section/segment found"); -} + case TEST_STACK_PROT: + if (per_file.current_tool == TOOL_GO) + skip (data, i, SOURCE_FINAL_SCAN, "GO is stack safe"); + else if (is_C_compiler (per_file.seen_tools)) + maybe (data, i, SOURCE_FINAL_SCAN, "no valid notes found regarding this test"); + else + skip (data, i, SOURCE_FINAL_SCAN, "not compiled code"); + break; -static void -show_DYNAMIC_TAGS (annocheck_data * data, test * results) -{ - if (per_file.e_machine != EM_AARCH64) - skip (data, "Test of dynamic tags. (AArch64 specific)"); - else if (per_file.e_type == ET_REL) - skip (data, "Test of dynamic tags. (Not needed in object files)"); - else if (results->num_pass == 3) - pass (data, "Both PAC and BTI dynamic tags are present"); - else if (results->num_pass == 2) - look (data, "The BTI dynamic tags is missing"); - else if (results->num_pass == 1) - look (data, "The PAC dynamic tags is missing"); - else - look (data, "The BTI and PAC dynamic tags are missing"); -} + case TEST_OPTIMIZATION: + if (per_file.current_tool == TOOL_GO) + skip (data, i, SOURCE_FINAL_SCAN, "GO optimizes by default"); + else if (is_C_compiler (per_file.seen_tools)) + maybe (data, i, SOURCE_FINAL_SCAN, "no valid notes found regarding this test"); + else + skip (data, i, SOURCE_FINAL_SCAN, "not compiled code"); + break; -static void -show_GNU_RELRO (annocheck_data * data, test * results) -{ - /* Relocateable object files are not yet linked. */ - if (per_file.e_type == ET_REL) - skip (data, "Test for -Wl,-z,relro. (Not needed in object files)"); - else if (tests[TEST_DYNAMIC_SEGMENT].num_pass == 0) - skip (data, "Test for -Wl,-z,relro. (No dynamic segment present)"); - else if (tests [TEST_BIND_NOW].num_maybe == 0) - skip (data, "Test for -Wl,-z,relro. (No dynamic relocations)"); - else if (per_file.tool == TOOL_GO) - /* FIXME: This is for GO binaries. Should be changed once GO supports PIE & BIND_NOW. */ - skip (data, "Test for -Wl,z,relro. (Built by GO)"); - else if (results->num_pass == 0 || results->num_fail > 0) - fail (data, "Not linked with -Wl,-z,relro"); - else - pass (data, "Linked with -Wl,-z,relro"); -} + case TEST_STACK_CLASH: + if (per_file.e_machine == EM_ARM) + skip (data, i, SOURCE_FINAL_SCAN, "not support on ARM architectures"); + else if (per_file.seen_tools == TOOL_GAS + || (per_file.gcc_from_comment && per_file.seen_tools == (TOOL_GAS | TOOL_GCC))) + skip (data, i, SOURCE_FINAL_SCAN, "no compiled code found"); + else if (per_file.current_tool == TOOL_GO) + skip (data, i, SOURCE_FINAL_SCAN, "GO is stack safe"); + else if (is_C_compiler (per_file.seen_tools)) + skip (data, i, SOURCE_FINAL_SCAN, "no compiled code found"); + else + maybe (data, i, SOURCE_FINAL_SCAN, "no notes found regarding this test"); + break; -static void -show_GNU_STACK (annocheck_data * data, test * results) -{ - /* Relocateable object files do not have a stack segment. */ - if (per_file.e_type == ET_REL) - skip (data, "Test of stack segment. (Object files do not have segments)"); - else if (results->num_fail > 0 || results->num_maybe > 0) - fail (data, "The GNU stack segment has the wrong permissions"); - else if (results->num_pass > 1) - maybe (data, "Multiple GNU stack segments found!"); - else if (results->num_pass == 1) - pass (data, "Stack not executable"); - else - pass (data, "No stack section found"); -} + case TEST_PROPERTY_NOTE: + if (! supports_property_notes (per_file.e_machine)) + skip (data, i, SOURCE_FINAL_SCAN, "property notes not used"); + else if (is_object_file ()) + skip (data, i, SOURCE_FINAL_SCAN, "property notes not needed in object files"); + else if (per_file.current_tool == TOOL_GO) + skip (data, i, SOURCE_FINAL_SCAN, "property notes not needed for GO binaries"); + else if (per_file.e_machine == EM_AARCH64) + future_fail (data, ".note.gnu.property section not found"); + else + fail (data, i, SOURCE_FINAL_SCAN, "no .note.gnu.property section found"); + break; -static void -show_RWX_SEG (annocheck_data * data, test * results) -{ - if (per_file.e_type == ET_REL) - skip (data, "Check for RWX segments. (Object files do not have segments)"); - else if (results->num_fail > 0 || results->num_maybe > 0) - fail (data, "A segment with RWX permissions was found"); - else - pass (data, "No RWX segments found"); -} + case TEST_CF_PROTECTION: + if (is_x86 () && is_executable ()) + { + if (per_file.current_tool == TOOL_GO) + skip (data, i, SOURCE_FINAL_SCAN, "control flow protection is not needed for GO binaries"); + else if (tests[TEST_PROPERTY_NOTE].enabled + && tests[TEST_PROPERTY_NOTE].state == STATE_UNTESTED) + fail (data, i, SOURCE_FINAL_SCAN, "no .note.gnu.property section = no control flow information"); + else + fail (data, i, SOURCE_FINAL_SCAN, "control flow protection is not enabled"); + } + else + skip (data, i, SOURCE_FINAL_SCAN, "not an x86 executable"); + break; -static void -show_TEXTREL (annocheck_data * data, test * results) -{ - if (per_file.e_type == ET_REL) - skip (data, "Object files are allowed text relocations"); - else if (results->num_fail > 0 || results->num_maybe > 0) - fail (data, "Text relocations found"); - else - pass (data, "No text relocations found"); -} + case TEST_STACK_REALIGN: + if (per_file.seen_tools == TOOL_GAS + || (per_file.gcc_from_comment && per_file.seen_tools == (TOOL_GAS | TOOL_GCC))) + skip (data, i, SOURCE_FINAL_SCAN, "no compiled code found"); + else if (per_file.e_machine == EM_386) + fail (data, i, SOURCE_FINAL_SCAN, "stack realign support is mandatory"); + else + skip (data, i, SOURCE_FINAL_SCAN, "not an x86 executable"); + break; -static void -show_RUN_PATH (annocheck_data * data, test * results) -{ - if (per_file.e_type == ET_REL) - skip (data, "Test of runpath. (Object files do not have one)"); - else if (results->num_fail > 0 || results->num_maybe > 0) - { - if (BE_VERBOSE) - fail (data, "DT_RPATH/DT_RUNPATH contains directories not starting with /usr"); - else - fail (data, "DT_RPATH/DT_RUNPATH contains directories not starting with /usr. Run with -v for details."); - } - else - pass (data, "DT_RPATH/DT_RUNPATH absent or rooted at /usr"); -} - -static void -show_THREADS (annocheck_data * data, test * results) -{ - if (results->num_fail > 0 || results->num_maybe > 0) - fail (data, "Thread cancellation not hardened. (Compiled without -fexceptions)"); - else - pass (data, "No thread cancellation problems"); -} - -static void -show_WRITEABLE_GOT (annocheck_data * data, test * results) -{ - if (per_file.e_type == ET_REL) - skip (data, "Test for writeable GOT. (Object files do not have a GOT)"); - else if (results->num_fail > 0 || results->num_maybe > 0) - fail (data, "Relocations for the GOT/PLT sections are writeable"); - else - pass (data, "GOT/PLT relocations are read only"); -} - -static void -show_OPTIMIZATION (annocheck_data * data, test * results) -{ - if (results->num_fail > 0) - { - if (results->num_pass > 0 || results->num_maybe > 0) - { - if (BE_VERBOSE) - fail (data, "Parts of the binary were compiled without sufficient optimization"); - else - fail (data, "Parts of the binary were compiled without sufficient optimization. Run with -v to see where"); - } - else - fail (data, "The binary was compiled without sufficient optimization"); - } - else if (results->num_maybe > 0) - { - if (results->num_pass > 0) - { - if (! BE_VERBOSE) - maybe (data, "Some parts of the binary do not record their optimization setting. Run with -v to see where"); - else - maybe (data, "Some parts of the binary do not record their optimization setting"); - } - else - maybe (data, "The optimization setting was not recorded"); - } - else if (results->num_pass > 0) - { - pass (data, "Compiled with sufficient optimization"); - } - else if (! built_by_compiler ()) - { - skip (data, "Test of optimization level. (Not built by gcc/clang)"); - } - else - { - maybe (data, "The optimization setting was not recorded"); - } -} - -static void -show_LTO (annocheck_data * data, test * results) -{ - /* FIXME: For now these checks are soft fails. */ - if (results->num_fail > 0) - { - if (results->num_pass > 0 || results->num_maybe > 0) - { - if (BE_VERBOSE) - look (data, "Parts of the binary were compiled without LTO enabled"); - else - look (data, "Parts of the binary were compiled without LTO enabled. Run with -v to see where"); - } - else - look (data, "The binary was compiled without LTO enabled"); - } - else if (results->num_maybe > 0) - { - if (results->num_pass > 0) - { - if (! BE_VERBOSE) - fail (data, "Some parts of the binary had corrupt LTO data. Run with -v to see where"); - else - fail (data, "Some parts of the binary had corrupt LTO data"); - } - else - fail (data, "The LTO data was corrupt"); - } - else if (results->num_pass > 0) - { - pass (data, "Compiled with LTO enabled"); - } - else if (! built_by_compiler ()) - { - skip (data, "Test of LTO enablement. (Not built by gcc/clang)"); - } - else - { - skip (data, "The LTO setting was not recorded (probably due to the use of an old version of the annobin plugin)"); - } -} - -static void -show_PIC (annocheck_data * data, test * results) -{ - if (results->num_fail > 0) - { - if (results->num_pass > 0 || results->num_maybe > 0) - { - if (BE_VERBOSE) - fail (data, "Parts of the binary were compiled without the proper PIC/PIE option"); - else - fail (data, "Parts of the binary were compiled without the proper PIC/PIE option. Run with -v to see where"); - } - else - fail (data, "The binary was compiled without -fPIC/-fPIE specified"); - } - else if (results->num_maybe > 0) - { - if (results->num_pass > 0) - { - if (! BE_VERBOSE) - maybe (data, "Some parts of the binary do not record the PIC/PIE setting. Run with -v to see where"); - else - maybe (data, "Some parts of the binary do not record the PIC/PIE setting"); - } - else - maybe (data, "The PIC/PIE setting was not recorded"); - } - else if (results->num_pass > 0) - { - pass (data, "Compiled with PIC/PIE"); - } - else if (! built_by_compiler ()) - { - skip (data, "Test for PIC compilation. (Not built by gcc/clang)"); - } - else - { - maybe (data, "The PIC/PIE setting was not recorded"); - } -} - -static void -show_PIE (annocheck_data * data, test * results) -{ - if (! built_by_compiler ()) - skip (data, "Test for -pie. (Not built with gcc/clang)"); - - else if (results->num_fail > 0) - fail (data, "Not linked as a position independent executable (ie need to add '-pie' to link command line)"); - - else /* Ignore maybe results - they should not happen. */ - pass (data, "Compiled as a position independent binary"); -} - -static void -show_STACK_PROT (annocheck_data * data, test * results) -{ - if (results->num_fail > 0) - { - if (results->num_pass > 0 || results->num_maybe > 0) - { - if (BE_VERBOSE) - fail (data, "Parts of the binary were compiled without a suffcient -fstack-protector setting"); - else - fail (data, "Parts of the binary were compiled without a suffcient -fstack-protector setting. Run with -v to see where"); - } - else - fail (data, "The binary was compiled without -fstack-protector-strong"); - } - else if (results->num_maybe > 0) - { - if (results->num_pass > 0) - { - if (! BE_VERBOSE) - maybe (data, "Some parts of the binary do not record the -fstack-protector setting. Run with -v to see where"); - else - maybe (data, "Some parts of the binary do not record the -fstack-protector setting"); - } - else - maybe (data, "The -fstack-protector setting was not recorded"); - } - else if (results->num_pass > 0) - { - pass (data, "Compiled with sufficient stack protection"); - } - else if (! built_by_compiler ()) - { - skip (data, "Test for stack protection. (Not built by gcc/clang)"); - } - else - { - maybe (data, "The -fstack-protector setting was not recorded"); - } -} - -static void -show_STACK_CLASH (annocheck_data * data, test * results) -{ - if (per_file.e_machine == EM_ARM) - skip (data, "Test for stack clash support. (Not enabled on the ARM)"); - - else if (! built_by_gcc ()) - skip (data, "Test for stack clash support. (Not built by gcc)"); - - else if (per_file.tool_version < 7) - skip (data, "Test for stack clash support. (Needs gcc 7+)"); - - else if (results->num_fail > 0) - { - if (results->num_pass > 0 || results->num_maybe > 0) - { - if (BE_VERBOSE) - fail (data, "Parts of the binary were compiled without stack clash protection"); - else - fail (data, "Parts of the binary were compiled without stack clash protection. Run with -v to see where"); - } - else - fail (data, "The binary was compiled without -fstack-clash-protection"); - } - else if (results->num_maybe > 0) - { - if (results->num_pass > 0) - { - if (! BE_VERBOSE) - maybe (data, "Some parts of the binary do not record -fstack-clash-protection. Run with -v to see where"); - else - maybe (data, "Some parts of the binary do not record -fstack-clash-protection"); - } - else - maybe (data, "The stack clash protection setting was not recorded"); - } - - else if (results->num_pass > 0) - pass (data, "Compiled with -fstack-clash-protection"); - - else if (per_file.compiled_code_seen) - maybe (data, "The -fstack-clash-protection setting was not recorded"); - - else - skip (data, "Test for stack clash support. (No GCC compiled object files)"); -} - -static void -show_FORTIFY (annocheck_data * data, test * results) -{ - if (! built_by_compiler ()) - skip (data, "Test for -D_FORTIFY_SOURCE=2. (Not built by gcc/clang)"); - - else if (results->num_fail > 0) - { - if (tests[TEST_LTO].num_pass > 0) - /* FIXME: This is wrong. It only applies if -flto was used in the parts - of the binary where -D_FORTIFY_SOURCES was not detected. We ought - to be checking this. */ - skip (data, "The -D_FORTIFY_SOURCE=2 option was not seen (which happens when compiling with LTO enabled)"); - - else if (per_file.e_type == ET_REL) - { - if (per_file.lang == LANG_OTHER || per_file.lang == LANG_UNKNOWN) - skip (data, "Could not determine if -D_FORTIFY_SOURCE=2 was used, but this may be because this is an object file compiled from a language that does not use C headers"); - else - maybe (data, "Could not determine if -D_FORTIFY_SOURCE=2 was used"); - } - - else if (results->num_pass > 0 || results->num_maybe > 0) - { - if (BE_VERBOSE) - fail (data, "Parts of the binary were compiled without -D_FORTIFY_SOURCE=2"); - else - fail (data, "Parts of the binary were compiled without -D_FORTIFY_SOURCE=2. Run with -v to see where"); - } - - else - fail (data, "The binary was compiled without -D_FORTIFY_SOURCE=2"); - } - - else if (results->num_maybe > 0) - { - if (per_file.e_type == ET_REL) - { - if (per_file.lang == LANG_OTHER || per_file.lang == LANG_UNKNOWN) - skip (data, "Could not determine if -D_FORTIFY_SOURCE=2 was used, but this may be because this is an object file compiled from a language that does not use C headers"); - else - maybe (data, "Could not determine if -D_FORTIFY_SOURCE=2 was used"); - } - - else if (tests[TEST_LTO].num_pass > 0) - skip (data, "The -D_FORTIFY_SOURCE=2 option was not seen (which happens when compiling with LTO enabled)"); - - /* If we know that we have seen -D_GLIBCXX_ASSERTIONS then we - should also have seen -D_FORTIFY_SOURCE. Hence its absence - is a failure. */ - else if (tests[TEST_GLIBCXX_ASSERTIONS].num_pass > 0) - fail (data, "Some parts of the binary were compiled without -D_FORTIFY_SOURCE=2 but with -D_GLIBCXX_ASSERTIONS"); - - else if (results->num_pass > 0) - { - if (! BE_VERBOSE) - fail (data, "Some parts of the binary were not compiled with -D_FORTIFY_SOURCE=2. Run with -v to see where"); - else - fail (data, "Some parts of the binary were not compiled with -D_FORTIFY_SOURCE=2"); - } - - else - maybe (data, "The -D_FORTIFY_SOURCE=2 option was not seen"); - } - - else if (results->num_pass > 0) - pass (data, "Compiled with -D_FORTIFY_SOURCE=2"); - - else if (tests[TEST_LTO].num_pass > 0) - skip (data, "The -D_FORTIFY_SOURCE=2 option was not seen (which happens when compiling with LTO enabled)"); - - else if (per_file.compiled_code_seen) - maybe (data, "The -D_FORTIFY_SOURCE=2 option was not seen"); - - else - skip (data, "Test for -D_FORTIFY_SOURCE. (No GCC compiled object files)"); -} - -static void -show_CF_PROTECTION (annocheck_data * data, test * results) -{ - if (per_file.e_machine != EM_386 && per_file.e_machine != EM_X86_64) - skip (data, "Test for control flow protection. (Only supported on x86 binaries)"); - - else if (! built_by_compiler ()) - skip (data, "Test for control flow protection. (Not built by gcc/clang)"); - - else if (built_by_gcc () && per_file.tool_version < 8) - skip (data, "Test for control flow protection. (Needs gcc v8+)"); - - else if (results->num_fail > 0) - { - if (results->num_pass > 0 || results->num_maybe > 0) - { - if (BE_VERBOSE) - fail (data, "Parts of the binary were compiled without sufficient -fcf-protection"); - else - fail (data, "Parts of the binary were compiled without sufficient -fcf-protection. Run with -v to see where"); - } - else - fail (data, "The binary was compiled without sufficient -fcf-protection"); - } - - else if (results->num_maybe > 0) - { - if (results->num_pass > 0) - { - if (! BE_VERBOSE) - maybe (data, "Some parts of the binary do not record whether -fcf-protection was used. Run with -v to see where"); - else - maybe (data, "Some parts of the binary do not record whether -fcf-protection was used"); - } - else - maybe (data, "The -fcf-protection option was not seen"); - } - - else if (results->num_pass > 0) - pass (data, "Compiled with -fcf-protection"); - - else - maybe (data, "The -fcf-protection option was not seen"); -} - -static void -show_GLIBCXX_ASSERTIONS (annocheck_data * data, test * results) -{ - if (results->num_fail > 0) - { - if (results->num_pass > 0 || results->num_maybe > 0 || ! built_by_compiler ()) - { - if (BE_VERBOSE) - fail (data, "Parts of the binary were compiled without -D_GLIBCXX_ASSRTIONS"); - else - fail (data, "Parts of the binary were compiled without -D_GLIBCXX_ASSRTIONS. Run with -v to see where"); - } - else if (per_file.lang == LANG_CXX || per_file.lang == LANG_UNKNOWN) - fail (data, "The binary was compiled without -D_GLIBCXX_ASSERTIONS"); - else - skip (data, "The binary was compiled without -D_GLIBCXX_ASSERTIONS but it is not written in C++"); - } - - else if (! built_by_compiler ()) - skip (data, "Test for -D_GLIBCXX_ASSERTONS. (Not built by gcc/clang)"); - - else if (results->num_maybe > 0) - { - if (results->num_pass > 0) - { - if (! BE_VERBOSE) - maybe (data, "Some parts of the binary do not record whether -D_GLIBCXX_ASSERTIONS was used. Run with -v to see where"); - else - maybe (data, "Some parts of the binary do not record whether -D_GLIBCXX_ASSERTIONS was used"); - } - - /* If we know that we have seen -D_FORTIFY_SOURCE then we should also - have seen -D_GLIBCXX_ASSERTIONS. Hence its absence is a failure. */ - else if (tests[TEST_FORTIFY].num_pass > 0) - fail (data, "The binary was compiled without -D_GLIBCXX_ASSERTIONS"); - - else if (tests[TEST_LTO].num_pass > 0) - skip (data, "The -D_GLIBCXX_ASSERTIONS option was not seen (which happens when compiling with LTO enabled)"); - - else - maybe (data, "The -D_GLIBCXX_ASSERTIONS option was not seen"); - } - - else if (results->num_pass > 0) - pass (data, "Compiled with -D_GLIBCXX_ASSERTIONS"); - - else if (tests[TEST_LTO].num_pass > 0) - skip (data, "The -D_GLIBCXX_ASSERTIONS option was not seen (which happens when compiling with LTO enabled)"); - - else if (per_file.compiled_code_seen) - maybe (data, "The -D_GLIBCXX_ASSERTIONS option was not seen"); - - else - skip (data, "The test for -D_GLIBCXX_ASSERTIONS. (No compiled code seen)"); -} - -static void -show_STACK_REALIGN (annocheck_data * data, test * results) -{ - if (per_file.e_machine != EM_386) - skip (data, "Test for stack realignment support. (Only needed on i686 binaries)"); - - else if (! built_by_gcc ()) - skip (data, "Test for stack realignment support. (Not built by gcc)"); + case TEST_BRANCH_PROTECTION: + if (per_file.e_machine != EM_AARCH64) + skip (data, i, SOURCE_FINAL_SCAN, "not an AArch64 binary"); + else if (! includes_gcc (per_file.seen_tools) && ! includes_gimple (per_file.current_tool)) + skip (data, i, SOURCE_FINAL_SCAN, "not built by gcc"); + else if (per_file.tool_version < 9) + skip (data, i, SOURCE_FINAL_SCAN, "needs gcc 9+"); + else + /* FIXME: Only inform the user for now. Once -mbranch-protection has + been added to the rpm macros then change this result to a maybe(). */ + /* maybe (data, "The -mbranch-protection setting was not recorded"); */ + future_fail (data, "The -mbranch-protection setting was not recorded"); + break; - else if (results->num_fail > 0) - { - if (results->num_pass > 0 || results->num_maybe > 0) - { - if (BE_VERBOSE) - fail (data, "Parts of the binary were compiled without -mstack-realign"); - else - fail (data, "Parts of the binary were compiled without -mstack-realign. Run with -v to see where"); - } - else - fail (data, "The binary was compiled without -mstack-realign"); - } - else if (results->num_maybe > 0) - { - if (results->num_pass > 0) - { - if (! BE_VERBOSE) - maybe (data, "Some parts of the binary do not record whether -mstack_realign was used. Run with -v to see where"); - else - maybe (data, "Some parts of the binary do not record whether -mstack_realign was used"); + case TEST_GO_REVISION: + if (per_file.seen_tools & TOOL_GO) + fail (data, i, SOURCE_FINAL_SCAN, "no Go compiler revision information found"); + else + skip (data, i, SOURCE_FINAL_SCAN, "no GO compiled code found"); + + case TEST_ONLY_GO: + if (! is_x86 ()) + skip (data, i, SOURCE_FINAL_SCAN, "not compiled for x86"); + else if (per_file.seen_tools == TOOL_GO) + pass (data, i, SOURCE_FINAL_SCAN, "only GO compiled code found"); + else if (per_file.seen_tools & TOOL_GO) + fail (data, i, SOURCE_FINAL_SCAN, "mixed GO and another language found"); + else + skip (data, i, SOURCE_FINAL_SCAN, "no GO compiled code found"); + break; + } } - else - maybe (data, "The -mstack-realign option was not seen"); - } - - else if (results->num_pass > 0) - pass (data, "Compiled with -mstack_realign"); - - else - maybe (data, "The -mstack-realign option was not seen"); -} - -static bool -finish (annocheck_data * data) -{ - if (disabled || per_file.debuginfo_file) - return true; - - if (! per_file.build_notes_seen - /* NB/ This code must happen after the call to annocheck_walk_dwarf() - as that function is responsible for following links to debuginfo - files. */ - && data->dwarf_filename != NULL - && data->dwarf_fd != data->fd) - { - struct checker hardened_notechecker = - { - "Hardened", - NULL, /* start_file */ - interesting_note_sec, - check_note_section, - NULL, /* interesting_seg */ - NULL, /* check_seg */ - NULL, /* end_file */ - NULL, /* process_arg */ - NULL, /* usage */ - NULL, /* version */ - NULL, /* start_scan */ - NULL, /* end_scan */ - NULL, /* internal */ - }; - - /* There is a separate debuginfo file. Scan it to see if there are any notes that we can use. */ - einfo (VERBOSE, "%s: info: Running subchecker on %s", data->filename, data->dwarf_filename); - annocheck_process_extra_file (& hardened_notechecker, data->dwarf_filename, data->filename, data->dwarf_fd); - } - - if (! per_file.build_notes_seen && per_file.compiled_code_seen) - fail (data, "Build notes were not found for this executable"); - - if (! ignore_gaps) - { - if (per_file.e_type == ET_REL) - skip (data, "Not checking for gaps (object file)"); - else if (! built_by_compiler ()) - skip (data, "Not checking for gaps (non-gcc compiled binary)"); - else - check_for_gaps (data); } - int i; - for (i = 0; i < TEST_MAX; i++) + if (per_file.num_fails > 0) { - if (tests[i].enabled) + static bool tell_rerun = true; + if (! BE_VERBOSE && tell_rerun) { - tests[i].show_result (data, tests + i); - einfo (VERBOSE2, " Use --skip-%s to disable this test", tests[i].name); + einfo (INFO, "Rerun annocheck with --verbose to see more information on the tests"); + tell_rerun = false; } - else - einfo (VERBOSE, "%s: skip: %s", data->filename, tests[i].description); + return false; } - if (per_file.num_fails > 0) - return false; - if (per_file.num_maybes > 0) return false; /* FIXME: Add an option to ignore MAYBE results ? */ + if (BE_VERBOSE) + return true; + return einfo (INFO, "%s: PASS", data->filename); } static void version (void) { - einfo (INFO, "Version 1.3"); + einfo (INFO, "Version 1.4"); } static void @@ -3954,8 +3670,10 @@ usage (void) einfo (INFO, " --disable-hardened Disables the hardening checker"); einfo (INFO, " --enable-hardened Reenables the hardening checker"); - einfo (INFO, " Still to do:"); - einfo (INFO, " Add a machine readable output mode"); + einfo (INFO, " The tool will generate messages based upon the verbosity level"); + einfo (INFO, " but the format is not fixed. In order to have a consistent"); + einfo (INFO, " output enable this option:"); + einfo (INFO, " --fixed-format-messages"); } static bool @@ -4041,13 +3759,25 @@ process_arg (const char * arg, const char ** argv, const uint argc, uint * next) return true; } + if (streq (arg, "--fixed-format-messages")) + { + fixed_format_messages = true; + return true; + } + + if (streq (arg, "--disable-colour") || streq (arg, "--disable-color")) + { + enable_colour = false; + return true; + } + return false; } struct checker hardened_checker = { - "Hardened", + HARDENED_CHECKER_NAME, start, interesting_sec, check_sec, @@ -4068,3 +3798,4 @@ register_checker (void) if (! annocheck_add_checker (& hardened_checker, ANNOBIN_VERSION / 100)) disabled = true; } + diff --git a/annocheck/notes.c b/annocheck/notes.c index f877603..0af5861 100644 --- a/annocheck/notes.c +++ b/annocheck/notes.c @@ -1,5 +1,5 @@ /* Displays the Annobin notes in binary files. - Copyright (c) 2019-2020 Red Hat. + Copyright (c) 2019-2021 Red Hat. This is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published @@ -21,11 +21,11 @@ typedef struct local_note { - ulong start; - ulong end; - uint value; - bool open; - const char * data; + ulong start; + ulong end; + uint value; + const char * data; + bool open; } local_note; @@ -42,8 +42,6 @@ static uint num_allocated_notes = 0; static bool notes_start_file (annocheck_data * data) { - assert (saved_notes == NULL && num_saved_notes == 0 && num_allocated_notes == 0); - if (data->is_32bit) { Elf32_Ehdr * hdr = elf32_getehdr (data->elf); @@ -81,7 +79,7 @@ record_new_range (ulong start, ulong end) saved_end = end; } -#define RANGE_ALLOC_DELTA 16 +#define RANGE_ALLOC_DELTA 32 static void record_note (uint value, const char * data, bool open) @@ -103,6 +101,7 @@ record_note (uint value, const char * data, bool open) note->value = value; note->open = open; note->data = data; + ++ num_saved_notes; } @@ -288,7 +287,15 @@ compare_range (const void * r1, const void * r2) if (n1->end > n2->end) return 1; + if (n1->end < n2->end) + return -1; + /* Put open notes before function notes. */ + if (n1->open && ! n2->open) + return -1; + if (! n1->open && n2->open) + return 1; +#if 0 /* N1 is wholly covered by N2: n2->start <= n1->start <= n2->end n2->start <= n1->end <= n2->end. @@ -296,22 +303,26 @@ compare_range (const void * r1, const void * r2) n1->start = n2->start; n1->end = n2->end; assert (n1->start <= n1->end); +#endif return 0; } static bool notes_end_file (annocheck_data * data) { + uint i; + if (disabled) return true; - einfo (VERBOSE, "%u notes found", num_saved_notes); + einfo (PARTIAL, "\n"); + einfo (VERBOSE, "%s: %u notes found", data->filename, num_saved_notes); /* Sort the saved notes. */ qsort (saved_notes, num_saved_notes, sizeof saved_notes[0], compare_range); /* Display the saved notes. */ - uint i; + ulong prev_start = 0, prev_end = 0; for (i = 0; i < num_saved_notes; i++) { @@ -321,7 +332,7 @@ notes_end_file (annocheck_data * data) if (note->start == note->end && ! BE_VERBOSE && e_type != ET_REL) continue; - if (note->start != prev_start || note->end != prev_end) + if (i == 0 || note->start != prev_start || note->end != prev_end) { einfo (INFO, "Range: %#lx .. %#lx", note->start, note->end); prev_start = note->start; @@ -341,7 +352,26 @@ notes_end_file (annocheck_data * data) { case GNU_BUILD_ATTRIBUTE_VERSION: if (value == -1) - einfo (PARTIAL, "Version: %s\n", note->data + 1); + { + einfo (PARTIAL, "Version: %s", note->data + 1); + + switch (note->data[2]) + { + case ANNOBIN_TOOL_ID_CLANG: einfo (PARTIAL, " [clang]"); break; + case ANNOBIN_TOOL_ID_LLVM: einfo (PARTIAL, " [llvm]"); break; + case ANNOBIN_TOOL_ID_ASSEMBLER: einfo (PARTIAL, " [gas]"); break; + case ANNOBIN_TOOL_ID_LINKER: einfo (PARTIAL, " [linker]"); break; + case ANNOBIN_TOOL_ID_GCC: einfo (PARTIAL, " [gcc]"); break; + case ANNOBIN_TOOL_ID_GCC_COLD: einfo (PARTIAL, " [gcc:.text.cold]"); break; + case ANNOBIN_TOOL_ID_GCC_HOT: einfo (PARTIAL, " [gcc:.text.hot]"); break; + case ANNOBIN_TOOL_ID_GCC_STARTUP: einfo (PARTIAL, " [gcc:.text.startup]"); break; + case ANNOBIN_TOOL_ID_GCC_EXIT: einfo (PARTIAL, " [gcc:.text.exit]"); break; + case ANNOBIN_TOOL_ID_GCC_LTO: einfo (PARTIAL, " [gcc in LTO mode]"); break; + default: einfo (PARTIAL, " [??]"); break; + } + + einfo (PARTIAL, "\n"); + } else einfo (PARTIAL, "Version: %x (?)\n", value); break; @@ -447,11 +477,14 @@ notes_end_file (annocheck_data * data) { case 255: einfo (PARTIAL, "not detected\n"); break; + case 254: + einfo (PARTIAL, "hidden by LTO compilation\n"); break; default: einfo (PARTIAL, "*unknown (%d)*\n", value); break; case 0: case 1: case 2: + case 3: einfo (PARTIAL, "%d\n", value); break; } } @@ -591,7 +624,8 @@ notes_end_file (annocheck_data * data) /* Free up the notes. */ free (saved_notes); num_saved_notes = num_allocated_notes = 0; - + saved_notes = NULL; + return true; } diff --git a/clang-plugin/Makefile.in b/clang-plugin/Makefile.in index 9836112..7db3b47 100644 --- a/clang-plugin/Makefile.in +++ b/clang-plugin/Makefile.in @@ -47,8 +47,8 @@ PLUGIN_TEST_OPTIONS = \ # -fcf-protection \ check: @srcdir@/hello.c - @ $(CLANG) -fplugin=$(PLUGIN) $(PLUGIN_TEST_OPTIONS) -c @srcdir@/hello.c - @ $(READELF) --wide --notes hello.o > clang-plugin-test.out + $(CLANG) -fplugin=$(PLUGIN) $(PLUGIN_TEST_OPTIONS) -c @srcdir@/hello.c + $(READELF) --wide --notes hello.o > clang-plugin-test.out @ grep --silent -e "annobin built by clang version" clang-plugin-test.out @ grep --silent -e "running on clang version" clang-plugin-test.out @ grep --silent -e "sanitize_cfi" clang-plugin-test.out diff --git a/doc/annobin.info b/doc/annobin.info index 61f7a13..9239430 100644 --- a/doc/annobin.info +++ b/doc/annobin.info @@ -3,7 +3,7 @@ annobin.texi. This file documents the annobin plugin on the Fedora system. - Copyright (C) 2018 - 2020 Red Hat. + Copyright (C) 2018 - 2021 Red Hat. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or @@ -212,14 +212,20 @@ accept all of these options. The options are: 'active-checks' 'no-active-checks' - The 'active-checks' option enables compile time checking by the - annobin plugin. The plugin will actively examine the gcc command - line and generate errors if required security options are missing - or have the wrong value. The default is not to perform these - checkes. + The annobin plugin will normally generate a warning message if it + detects that the '-D_FORTIFY_SOURCE=2' has not been provided on the + command line and '-flto' has been enabled. This is because LTO + compilation hides preprocessor options, so information about them + cannot be passed on to the 'annocheck' tool. - Note - this option is currently under development, and is not yet - fully functional. + The 'active-checks' option changes the warning message into an + error message, just as if '-Werror' had been specified. + + The 'no-active-checks' option disables the warning message + entirely. + + Note - in the future the annobin plugin might be extended to + produce warning messages for other missing command line options. 'dynamic-notes' 'no-dynamic-notes' @@ -227,10 +233,20 @@ accept all of these options. The options are: 'no-static-notes' These options are deprecated. +'ppc64-nops' +'no-ppc64-nops' + This option either enables or disables the insertion of NOP + instructions in the some of the code sections of PowerPC64 + binaries. This is necessary to avoid problems with the 'elflint' + program which will complain about binaries built without this + option enabled. The option is enabled by default, but since it + does increase the size of compiled programs by a small amount, the + 'no-ppc64-nops' is provided in order to turn it off. + The plugins record information appropriate to the compiler that is running them. So the 'gcc' plugin records information about the following options: -'-D_FORTIFY_SOURCE=2' +'-D_FORTIFY_SOURCE=[2|3]' '-D_GLIBCXX_ASSERTIONS' '-O' '-Wall' @@ -271,7 +287,7 @@ LLVM plugin. The 'LLVM' plugin records information on the following command line options: -'-D_FORTIFY_SOURCE=2' +'-D_FORTIFY_SOURCE=[2|3]' '-O' '-Wall' '-flto' @@ -344,7 +360,44 @@ File: annobin.info, Node: The Version Encoding, Next: The STACK Encoding, Up: The 'version' note encodes the version of the Watermark specification used and the version of the tool used to generate the notes. Typically -the protocol version will be 3 and the plugin version will be 5. +the protocol version will be 3 and the plugin version will be 9. It +also encodes the tool used to generate the notes as a single character. +The following characters are used: + +'L' + The notes have been produced by the Clang plugin. + +'V' + The notes have been produced by the LLVM plugin. + +'a' + The notes have been produced by the assembler. + +'c' + The notes have been produced by the gcc plugin for the .text.cold + section. + +'e' + The notes have been produced by the gcc plugin for the .text.exit + section. + +'g' + The notes have been produced by the gcc plugin when running in LTO + mode. + +'h' + The notes have been produced by the gcc plugin for the .text.hot + section. + +'l' + The notes have been produced by the linker. + +'p' + The notes have been produced by the gcc plugin. + +'s' + The notes have been produced by the gcc plugin for the + .text.startup section.  File: annobin.info, Node: The STACK Encoding, Next: The PIC Encoding, Prev: The Version Encoding, Up: Examining @@ -694,6 +747,8 @@ File: annobin.info, Node: Hardened, Next: Notes, Prev: Built-By, Up: Annoche [-test-all] [-test-future] [-ignore-gaps] + [-fixed-format-messages] + [-disable-colour] [-disable-hardened] [-enable-hardened] FILE... @@ -741,8 +796,9 @@ code to support the test. 'Strong stack protection' The program must have been compiled with the '-fstack-protector-strong' option enabled, and with - '-D_FORTIFY_SOURCE=2' specified. It must also have been compiled - at at least optimization level 2. Disabled by '--skip-stack-prot'. + '-D_FORTIFY_SOURCE=[2|3]' specified. It must also have been + compiled at at least optimization level 2. Disabled by + '--skip-stack-prot'. 'Dynamic data present' Dynamic executables must have a dynamic segment. Disabled by @@ -779,8 +835,9 @@ code to support the test. specified. Disabled by '--skip-stack-realign'. 'Source fortification' - The program must have been compiled with the '-D_FORTIFY_SOURCE=2' - command line option specified. Disabled by '--skip-fortify'. + The program must have been compiled with the + '-D_FORTIFY_SOURCE=[2|3]' command line option specified. Disabled + by '--skip-fortify'. 'Optimization' The program must have been compiled with at least '-O2' @@ -827,6 +884,31 @@ code to support the test. '--disable-hardened' Disable the tool. +'--ignore-gaps' + Do not complain about gaps in the note data. + +'--fixed-format-messages' + Display messages in a fixed, machine parseable format. The format + is: + + Hardened: : test: file: + + Where '' is _PASS_ or _FAIL_ and '' is the name + of the test, which is the same as the name used in the + '--test-' option. The '' is the name of the + input file, but with any special characters replaced so that it + always fits on one line. + + Here is an example: + + Hardened: FAIL: test: pie file: a.out. + +'--disable-colour' + Do not use colour to enhance FAIL, MAYB and WARN messages. By + default annocheck will add colour to these messages so that they + stand out when displayed by a terminal emulator. This option can + be used in order to turn this feature off. +  File: annobin.info, Node: Notes, Next: Section-Size, Prev: Hardened, Up: Annocheck @@ -1908,27 +1990,27 @@ Tag Table: Node: Top705 Node: Introduction1785 Node: Plugins3834 -Node: Examining11477 -Node: The Version Encoding14206 -Node: The STACK Encoding14597 -Node: The PIC Encoding15195 -Node: The GOW Encoding15823 -Node: The CF Encoding17626 -Node: The ENUM Encoding18698 -Node: The INSTRUMENT Encoding19087 -Node: Annocheck20461 -Node: Built-By22990 -Node: Hardened24520 -Node: Notes30244 -Node: Section-Size30888 -Node: Timing33042 -Node: Configure Options33689 -Node: Legacy Scripts35974 -Node: Who Built Me36749 -Node: ABI Checking39509 -Node: Hardening Checks41623 -Node: Checking Archives45709 -Node: GNU FDL48131 +Node: Examining12275 +Node: The Version Encoding15004 +Node: The STACK Encoding16244 +Node: The PIC Encoding16842 +Node: The GOW Encoding17470 +Node: The CF Encoding19273 +Node: The ENUM Encoding20345 +Node: The INSTRUMENT Encoding20734 +Node: Annocheck22108 +Node: Built-By24637 +Node: Hardened26167 +Node: Notes32866 +Node: Section-Size33510 +Node: Timing35664 +Node: Configure Options36311 +Node: Legacy Scripts38596 +Node: Who Built Me39371 +Node: ABI Checking42131 +Node: Hardening Checks44245 +Node: Checking Archives48331 +Node: GNU FDL50753  End Tag Table diff --git a/doc/annobin.texi b/doc/annobin.texi index ce936c5..33d2f54 100644 --- a/doc/annobin.texi +++ b/doc/annobin.texi @@ -1,13 +1,13 @@ \input texinfo @c -*-texinfo-*- @setfilename annobin.info -@c Copyright (C) 2018-2020 Red Hat. +@c Copyright (C) 2018-2021 Red Hat. @settitle Annobin @setchapternewpage odd @c man begin INCLUDE @set VERSION 9.0 @set VERSION_PACKAGE (Annobin) -@set UPDATED November 2020 +@set UPDATED Januray 2021 @c man end @ifnottex @@ -23,7 +23,7 @@ This file documents the annobin plugin on the Fedora system. @c man begin COPYRIGHT -Copyright @copyright{} 2018 - 2020 Red Hat. +Copyright @copyright{} 2018 - 2021 Red Hat. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 @@ -280,13 +280,20 @@ same executable, which can be useful for debugging and build testing. @item active-checks @item no-active-checks -The @option{active-checks} option enables compile time checking by -the annobin plugin. The plugin will actively examine the gcc command -line and generate errors if required security options are missing or -have the wrong value. The default is not to perform these checkes. +The annobin plugin will normally generate a warning message if it +detects that the @option{-D_FORTIFY_SOURCE=2} has not been provided on +the command line and @option{-flto} has been enabled. This is because +LTO compilation hides preprocessor options, so information about them +cannot be passed on to the @command{annocheck} tool. -Note - this option is currently under development, and is not yet -fully functional. +The @option{active-checks} option changes the warning message into an +error message, just as if @option{-Werror} had been specified. + +The @option{no-active-checks} option disables the warning message +entirely. + +Note - in the future the annobin plugin might be extended to produce +warning messages for other missing command line options. @item dynamic-notes @itemx no-dynamic-notes @@ -294,6 +301,16 @@ fully functional. @itemx no-static-notes These options are deprecated. +@item ppc64-nops +@itemx no-ppc64-nops +This option either enables or disables the insertion of NOP +instructions in the some of the code sections of PowerPC64 binaries. +This is necessary to avoid problems with the @code{elflint} program +which will complain about binaries built without this option enabled. +The option is enabled by default, but since it does increase the size +of compiled programs by a small amount, the @option{no-ppc64-nops} is +provided in order to turn it off. + @end table @c man end @@ -301,7 +318,7 @@ The plugins record information appropriate to the compiler that is running them. So the @code{gcc} plugin records information about the following options: @table @code -@item -D_FORTIFY_SOURCE=2 +@item -D_FORTIFY_SOURCE=[2|3] @item -D_GLIBCXX_ASSERTIONS @item -O @item -Wall @@ -346,7 +363,7 @@ LLVM plugin. The @code{LLVM} plugin records information on the following command line options: @table @code -@item -D_FORTIFY_SOURCE=2 +@item -D_FORTIFY_SOURCE=[2|3] @item -O @item -Wall @item -flto @@ -422,7 +439,41 @@ included in the table below. The @code{version} note encodes the version of the Watermark specification used and the version of the tool used to generate the notes. Typically the protocol version will be 3 and the plugin -version will be 5. +version will be 9. It also encodes the tool used to generate the +notes as a single character. The following characters are used: + +@table @code +@item L +The notes have been produced by the Clang plugin. + +@item V +The notes have been produced by the LLVM plugin. + +@item a +The notes have been produced by the assembler. + +@item c +The notes have been produced by the gcc plugin for the .text.cold section. + +@item e +The notes have been produced by the gcc plugin for the .text.exit section. + +@item g +The notes have been produced by the gcc plugin when running in LTO mode. + +@item h +The notes have been produced by the gcc plugin for the .text.hot section. + +@item l +The notes have been produced by the linker. + +@item p +The notes have been produced by the gcc plugin. + +@item s +The notes have been produced by the gcc plugin for the .text.startup section. + +@end table @c ----------------------------------------------------------------- @node The STACK Encoding @@ -803,6 +854,8 @@ annocheck [@b{--test-all}] [@b{--test-future}] [@b{--ignore-gaps}] + [@b{--fixed-format-messages}] + [@b{--disable-colour}] [@b{--disable-hardened}] [@b{--enable-hardened}] @var{file}@dots{} @@ -857,7 +910,7 @@ Disabled by @option{--ignore-gaps}. @item Strong stack protection The program must have been compiled with the @option{-fstack-protector-strong} option enabled, and with -@option{-D_FORTIFY_SOURCE=2} specified. It must also have been +@option{-D_FORTIFY_SOURCE=[2|3]} specified. It must also have been compiled at at least optimization level 2. Disabled by @option{--skip-stack-prot}. @@ -902,7 +955,7 @@ Disabled by @option{--skip-stack-realign}. @item Source fortification The program must have been compiled with the -@option{-D_FORTIFY_SOURCE=2} command line option specified. +@option{-D_FORTIFY_SOURCE=[2|3]} command line option specified. Disabled by @option{--skip-fortify}. @item Optimization @@ -958,6 +1011,34 @@ default. @item --disable-hardened Disable the tool. +@item --ignore-gaps +Do not complain about gaps in the note data. + +@item --fixed-format-messages +Display messages in a fixed, machine parseable format. The format is: + +@smallexample +Hardened: : test: file: +@end smallexample + +Where @code{} is @emph{PASS} or @emph{FAIL} and +@code{} is the name of the test, which is the same as the +name used in the @option{--test-} option. The +@code{} is the name of the input file, but with any special +characters replaced so that it always fits on one line. + +Here is an example: + +@smallexample + Hardened: FAIL: test: pie file: a.out. +@end smallexample + +@item --disable-colour +Do not use colour to enhance FAIL, MAYB and WARN messages. By default +annocheck will add colour to these messages so that they stand out +when displayed by a terminal emulator. This option can be used in +order to turn this feature off. + @end table @c man end diff --git a/gcc-plugin/annobin.cc b/gcc-plugin/annobin.cc index 417971c..a11390d 100644 --- a/gcc-plugin/annobin.cc +++ b/gcc-plugin/annobin.cc @@ -1,5 +1,5 @@ /* annobin - a gcc plugin for annotating binary files. - Copyright (c) 2017 - 2020 Red Hat. + Copyright (c) 2017 - 2021 Red Hat. Created by Nick Clifton. This is free software; you can redistribute it and/or modify it @@ -25,13 +25,17 @@ struct gcc_options * annobin_global_options = & global_options; #define global_options ANNOBIN_ILLEGAL_GLOBAL_OPTIONS /* Version number. */ +#define xstr(s) str(s) +#define str(s) #s static unsigned int annobin_version = ANNOBIN_VERSION; -#define VER_STRING(VER) N_("Version " #VER) -static const char * version_string = VER_STRING (ANNOBIN_VERSION); +static const char * version_string = "Version " xstr(ANNOBIN_VERSION); /* Prefix used to isolate annobin symbols from program symbols. */ #define ANNOBIN_SYMBOL_PREFIX ".annobin_" +/* Filename to use when in LTO mode and the original filename is unavailable. */ +#define ANNOBIN_LTO_FIXED_NAME "lto" + /* Suffix used to turn a section name into a group name. */ #define ANNOBIN_GROUP_NAME ".group" @@ -56,6 +60,10 @@ int plugin_is_GPL_compatible; targets that disable the plugin because they do not want it. */ static bool enabled = true; +/* Enable a workaround for problems building elfutils for the PPC64. + Inserts extra NOP instructions into the generated code. */ +static bool enable_ppc64_nops = true; + /* True if the symbols used to map addresses to file names should be global. On some architectures these symbols have to be global so that they will be preserved in object files. But doing so can prevent the build-id @@ -85,8 +93,10 @@ bool annobin_is_64bit = false; /* True if the creation of function specific notes should be reported. */ static bool annobin_function_verbose = false; -/* True if annobin should generate gcc errors if gcc command line options are wrong. */ -static bool annobin_active_checks = false; +/* 1 if annobin should generate gcc warnings if gcc command line options are wrong. + 2 if it should generate errors. + 0 if it should do nothing. */ +static uint annobin_active_checks = 1; enum attach_type { @@ -129,11 +139,12 @@ static const char * help_string = N_("Supported options:\n\ version Print out the version of the plugin\n\ verbose Be talkative about what is going on\n\ function-verbose Report the creation of function specific notes\n\ - [no-]global-file-syms Create global [or local] file name symbols (default: local)\n\ - [no-]stack-size-notes Do [do not] create stack size notes (default: do not)\n\ + [no-]active-checks Do [do not] generate errors if gcc command line options are wrong. (Default: warn)\n\ [no-]attach Do [do not] attempt to attach function sections to group sections\n\ + [no-]global-file-syms Create global [or local] file name symbols (default: local)\n\ [no-]link-order Do [do not] attempt to join note sections to code sections using link_order attributes\n\ - [no-]active-checks Do [do not] generate errors if gcc command line options are wrong. (Default: do not)\n\ + [no-]ppc64-nops Do [do not] insert NOP instructions into some PPC64 sections. (Default: do not)\n\ + [no-]stack-size-notes Do [do not] create stack size notes (default: do not)\n\ rename Add a prefix to the filename symbols so that two annobin plugins can be active at the same time\n\ stack-threshold=N Only create function specific stack size notes when the size is > N."); @@ -175,27 +186,44 @@ ice (const char * text) annobin_inform (INFORM_ALWAYS, "ICE: Please contact the annobin maintainer with details of this problem"); } +static inline bool +in_lto (void) +{ + /* Testing in_lto_p does not appear to be reliable. Unsure why. */ + if (streq (progname, "lto1")) + return true; + + return GET_INT_OPTION_BY_NAME (in_lto_p) != 0; +} + /* Determine the (main) input file name. */ static bool init_annobin_input_filename (void) { + const char * f = NULL; + + /* In LTO mode the compiler will use a random string for the + filename. In order to allow for reproducible compilation + however we must ensure that we use a fixed name. */ + if (in_lto ()) + f = ANNOBIN_LTO_FIXED_NAME; + /* Unfortunately we cannot rely upon 'main_input_filename' since if the input is preprocessed, this will have been set to the original un-preprocessed filename (foo.c) based upon the "# " comments in the preprocessed input (foo.i). Also main_input_filename is stored in the global_options array, where its offset cannot be safely determined. */ - if (num_in_fnames) - { - if ((annobin_input_filename = in_fnames[0]) != NULL) - return true; - } + if (f == NULL && num_in_fnames > 0) + f = in_fnames[0]; - /* This might fail, if annobin is out of sync with gcc. */ - annobin_input_filename = GET_STR_OPTION_BY_NAME(main_input_filename); + if (f == NULL) + /* This might fail, if annobin is out of sync with gcc. */ + f = GET_STR_OPTION_BY_NAME (main_input_filename); - return annobin_input_filename != NULL; + annobin_input_filename = f; + return f != NULL; } /* Create a symbol name to represent the sources we are annotating. @@ -814,12 +842,6 @@ annobin_get_optimize_debug (void) return GET_INT_OPTION_BY_NAME (optimize_debug); } -static inline bool -annobin_in_lto_p (void) -{ - return GET_INT_OPTION_BY_NAME (in_lto_p) != 0; -} - /* Compute a numeric value representing the settings/levels of the -O and -g options, and some -W options. This is to help verify the recommended hardening options for binaries. @@ -914,8 +936,7 @@ compute_GOWall_options (void) if (GET_INT_OPTION_BY_NAME (warn_format_security)) val|= (1 << 15); - if (annobin_in_lto_p () - || GET_STR_OPTION_BY_NAME(flag_lto) != NULL) + if (in_lto () || GET_STR_OPTION_BY_NAME(flag_lto) != NULL) val |= (1 << 16); else /* We record the negative so that annocheck can detect that we definitely have recorded something for this feature. */ @@ -1326,7 +1347,7 @@ annobin_create_function_notes (void * gcc_data, void * user_data) } else if (startup) { - if (! annobin_in_lto_p () && ! GET_INT_OPTION_BY_INDEX (OPT_fprofile_values)) + if (! in_lto () && ! GET_INT_OPTION_BY_INDEX (OPT_fprofile_values)) current_func.section_name = concat (STARTUP_SECTION, NULL); } else if (exit) @@ -1336,7 +1357,7 @@ annobin_create_function_notes (void * gcc_data, void * user_data) else if (likely) { /* FIXME: Never seen this one, either. */ - if (! annobin_in_lto_p () && ! GET_INT_OPTION_BY_INDEX (OPT_fprofile_values)) + if (! in_lto () && ! GET_INT_OPTION_BY_INDEX (OPT_fprofile_values)) current_func.section_name = concat (HOT_SECTION, NULL); } } @@ -1705,6 +1726,12 @@ annobin_emit_start_sym_and_version_note (const char * suffix, when the symbol's address is being used to compute a range for the notes. */ fprintf (asm_out_file, "\t.set %s%s, . + %d\n", annobin_output_filesym, suffix, target_start_sym_bias); + + /* FIXME: A workaround for BZ 1880634. + Ensure that we do not have empty special text sections so that the + annobin start symbols are never beyond the end of the sections. */ + if (suffix && enable_ppc64_nops) + annobin_emit_asm ("nop", "Inserted by the annobin plugin. Disable with -fplugin-arg-annobin-no-ppc64-nops"); } else fprintf (asm_out_file, "\t.equiv %s%s, .\n", annobin_output_filesym, suffix); @@ -1967,6 +1994,21 @@ ends_with (const char * string, const char * terminator) } static void +annobin_active_check (const char * message) +{ + // FIXME - for some reason the prototype of warning() in diagnostic-core.h + // does not match the implementation. So we use our own prototype here. + extern bool warning (int, const char *, ...); + + if (annobin_active_checks == 1) + // FIXME: We should find an OPT_ value to use here so + // that users can disable these warnings if they need to. + warning (0, "%s", message); + else if (annobin_active_checks == 2) + error ("%s", message); +} + +static void annobin_create_global_notes (void * gcc_data, void * user_data) { if (asm_out_file == NULL) @@ -2011,10 +2053,12 @@ annobin_create_global_notes (void * gcc_data, void * user_data) global_stack_clash_option = GET_INT_OPTION_BY_INDEX (OPT_fstack_clash_protection); #endif +#if 0 #ifdef flag_cf_protection global_cf_option = GET_INT_OPTION_BY_INDEX (OPT_fcf_protection_); - if (annobin_active_checks && ((global_cf_option & CF_FULL) == 0)) - error ("-fcf-protection=full needed"); + if ((global_cf_option & CF_FULL) == 0) + annobin_active_check error ("-fcf-protection=full needed"); +#endif #endif global_stack_prot_option = GET_INT_OPTION_BY_INDEX (OPT_fstack_protector); @@ -2023,10 +2067,11 @@ annobin_create_global_notes (void * gcc_data, void * user_data) global_GOWall_options = compute_GOWall_options (); global_omit_frame_pointer = GET_INT_OPTION_BY_INDEX (OPT_fomit_frame_pointer); - if (annobin_active_checks - && annobin_get_optimize () < 2 +#if 0 + if (annobin_get_optimize () < 2 && ! annobin_get_optimize_debug ()) - error ("optimization level is too low!"); + annobin_active_check ("optimization level is too low!"); +#endif /* Look for -D _FORTIFY_SOURCE= and -D_GLIBCXX_ASSERTIONS on the original gcc command line. Scan backwards so that we record the @@ -2132,17 +2177,12 @@ annobin_create_global_notes (void * gcc_data, void * user_data) if (global_fortify_level == -1) { - if (annobin_in_lto_p ()) + if (in_lto ()) { /* In LTO mode the preprocessed options are not passed on. - For now, assume that they were present when the original object - files were compiled. - - FIXME: What we should do is examine the input object files and - extract the fortify and glibcxx notes from them. But I do not - know if one plugin can access the data in another one... */ - global_fortify_level = 2; - annobin_inform (INFORM_VERY_VERBOSE, "Assuming -D_FORTIFY_SOURCE=2 for LTO compilation"); + Siganl this to annocheck so that it can decide what to do. */ + global_fortify_level = -2; + annobin_inform (INFORM_VERBOSE, "Setting -D_FORTIFY_SOURCE to unknown-because-of-LTO"); } /* BZ 1862718: We have no reliable way to determine if the input file was preprocessed before being passed to gcc. Plus we do not have @@ -2163,7 +2203,7 @@ annobin_create_global_notes (void * gcc_data, void * user_data) /* A simplified version of the above if() statement, but for GLIBCXX_ASSERTIONS. */ if (global_glibcxx_assertions == -1 - && (annobin_in_lto_p () + && (in_lto () || ends_with (annobin_input_filename, ".i") || ends_with (annobin_input_filename, ".ii"))) { @@ -2171,7 +2211,7 @@ annobin_create_global_notes (void * gcc_data, void * user_data) annobin_inform (INFORM_VERY_VERBOSE, "Assuming -D_GLIBCXX_ASSERTIONS for LTO/preprocessed input"); } - if (!annobin_in_lto_p () + if (! in_lto () && GET_STR_OPTION_BY_NAME(flag_lto) != NULL) { bool warned = false; @@ -2181,12 +2221,12 @@ annobin_create_global_notes (void * gcc_data, void * user_data) generate a warning message for the user. We do not do this for all input however as there is no way for a plugin to distinguish between preprocessed input and non-preprocessed input.*/ - if (global_fortify_level != 2) + if (global_fortify_level < 2) { if (global_fortify_level == -1) - annobin_inform (INFORM_ALWAYS, _("Warning: -D_FORTIFY_SOURCE not defined")); + annobin_active_check ("-D_FORTIFY_SOURCE not defined"); else - annobin_inform (INFORM_ALWAYS, _("Warning: -D_FORTIFY_SOURCE defined as %d"), global_fortify_level); + annobin_active_check ("-D_FORTIFY_SOURCE defined but value is too low"); warned = true; } @@ -2214,31 +2254,32 @@ annobin_create_global_notes (void * gcc_data, void * user_data) Nevertheless we generate this symbol in the .text section as at this point we cannot know which section(s) will be used by compiled code. */ - annobin_emit_start_sym_and_version_note ("", ANNOBIN_TOOL_ID_GCC); + char producer_char = in_lto () ? ANNOBIN_TOOL_ID_GCC_LTO : ANNOBIN_TOOL_ID_GCC; + annobin_emit_start_sym_and_version_note ("", producer_char); emit_global_notes (""); /* GCC does not provide any way for a plugin to detect if hot/cold partitioning will be performed on a function, and hence a .text.hot and/or .text.unlikely section will be created. So instead we create global notes to cover these two sections. */ - annobin_emit_start_sym_and_version_note (HOT_SUFFIX, ANNOBIN_TOOL_ID_GCC_HOT); + annobin_emit_start_sym_and_version_note (HOT_SUFFIX, producer_char); queue_attachment (HOT_SECTION, concat (HOT_SECTION, ANNOBIN_GROUP_NAME, NULL)); // We have to emit notes for these other sections too, as we do not know // which one(s) will actually end up containing any code. Annocheck will // ignore empty note ranges. emit_global_notes (HOT_SUFFIX); - annobin_emit_start_sym_and_version_note (COLD_SUFFIX, ANNOBIN_TOOL_ID_GCC_COLD); + annobin_emit_start_sym_and_version_note (COLD_SUFFIX, producer_char); queue_attachment (COLD_SECTION, concat (COLD_SECTION, ANNOBIN_GROUP_NAME, NULL)); emit_global_notes (COLD_SUFFIX); /* *sigh* As of gcc 9, a .text.startup section can also be created. */ - annobin_emit_start_sym_and_version_note (STARTUP_SUFFIX, ANNOBIN_TOOL_ID_GCC_STARTUP); + annobin_emit_start_sym_and_version_note (STARTUP_SUFFIX, producer_char); queue_attachment (STARTUP_SECTION, concat (STARTUP_SECTION, ANNOBIN_GROUP_NAME, NULL)); emit_global_notes (STARTUP_SUFFIX); /* Presumably a .text.exit section can also be created, although I have not seen that yet. */ - annobin_emit_start_sym_and_version_note (EXIT_SUFFIX, ANNOBIN_TOOL_ID_GCC_EXIT); + annobin_emit_start_sym_and_version_note (EXIT_SUFFIX, producer_char); queue_attachment (EXIT_SECTION, concat (EXIT_SECTION, ANNOBIN_GROUP_NAME, NULL)); emit_global_notes (EXIT_SUFFIX); } @@ -2274,7 +2315,7 @@ annobin_emit_end_symbol (const char * suffix) section. */ if (target_start_sym_bias == 0 #if GCCPLUGIN_VERSION_MAJOR >= 10 - || annobin_in_lto_p () + || in_lto () #endif ) { @@ -2311,7 +2352,7 @@ annobin_emit_end_symbol (const char * suffix) had to place the end symbol into a different section. */ if (target_start_sym_bias #if GCCPLUGIN_VERSION_MAJOR >= 10 - && ! annobin_in_lto_p () + && ! in_lto () #endif ) { @@ -2420,10 +2461,15 @@ parse_args (unsigned argc, struct plugin_argument * argv) annobin_attach_type = none; else if (streq (key, "active-checks")) - annobin_active_checks = true; + annobin_active_checks = 2; else if (streq (key, "no-active-checks")) - annobin_active_checks = false; + annobin_active_checks = 0; + else if (streq (key, "ppc64-nops")) + enable_ppc64_nops = true; + else if (streq (key, "no-ppc64-nops")) + enable_ppc64_nops = false; + else if (streq (key, "stack-threshold")) { stack_threshold = strtoul (argv[argc].value, NULL, 0); diff --git a/gcc-plugin/x86_64.annobin.cc b/gcc-plugin/x86_64.annobin.cc index 98afbb2..a945a16 100644 --- a/gcc-plugin/x86_64.annobin.cc +++ b/gcc-plugin/x86_64.annobin.cc @@ -15,8 +15,10 @@ #include "annobin-global.h" #include "annobin.h" +#ifndef GNU_PROPERTY_X86_ISA_1_USED #define GNU_PROPERTY_X86_ISA_1_USED 0xc0000000 #define GNU_PROPERTY_X86_ISA_1_NEEDED 0xc0000001 +#endif #define GNU_PROPERTY_X86_ISA_1_486 (1U << 0) #define GNU_PROPERTY_X86_ISA_1_586 (1U << 1) diff --git a/tests/Makefile.am b/tests/Makefile.am index 5ac34e3..dd708f8 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -11,6 +11,7 @@ TESTS=compile-test \ active-checks-test \ assembler-gap-test \ function-sections-test \ + glibc-notes-test \ hardening-fail-test \ hardening-test \ instrumentation-test \ diff --git a/tests/Makefile.in b/tests/Makefile.in index e7ca9dc..4d4a8a3 100644 --- a/tests/Makefile.in +++ b/tests/Makefile.in @@ -475,9 +475,10 @@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ TESTS = compile-test abi-test active-checks-test assembler-gap-test \ - function-sections-test hardening-fail-test hardening-test \ - instrumentation-test lto-test missing-notes-test objcopy-test \ - section-size-test $(am__append_1) + function-sections-test glibc-notes-test hardening-fail-test \ + hardening-test instrumentation-test lto-test \ + missing-notes-test objcopy-test section-size-test \ + $(am__append_1) XFAIL_TESTS = hardening-fail-test \ missing-notes-test \ active-checks-test \ @@ -705,6 +706,13 @@ function-sections-test.log: function-sections-test --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) +glibc-notes-test.log: glibc-notes-test + @p='glibc-notes-test'; \ + b='glibc-notes-test'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) hardening-fail-test.log: hardening-fail-test @p='hardening-fail-test'; \ b='hardening-fail-test'; \ diff --git a/tests/active-checks-test b/tests/active-checks-test index 8e5be87..77eb59c 100755 --- a/tests/active-checks-test +++ b/tests/active-checks-test @@ -19,8 +19,6 @@ PLUGIN=${PLUGIN:-../gcc-plugin/.libs/annobin.so} PLUGIN_OPTS="-fplugin-arg-annobin-no-attach -fplugin-arg-annobin-active-checks" -OPTS="-c" - -$GCC -fplugin=$PLUGIN $PLUGIN_OPTS -c $OPTS $srcdir/hello.c +$GCC -fplugin=$PLUGIN $PLUGIN_OPTS -c $srcdir/hello.c -Werror -flto # FIXME: Add regexps to check for the expected failure messages diff --git a/tests/assembler-gap-test b/tests/assembler-gap-test index fb2e010..2d8dd75 100755 --- a/tests/assembler-gap-test +++ b/tests/assembler-gap-test @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2018-2020 Red Hat. +# Copyright (c) 2018-2021 Red Hat. # # This is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published @@ -41,15 +41,15 @@ $GCC -fplugin=$PLUGIN $PLUGIN_OPTS -c -O2 -fPIC $srcdir/hello.c $GCC -fplugin=$PLUGIN $PLUGIN_OPTS -c -O2 -fPIC $srcdir/hello2.c $GCC -fplugin=$PLUGIN $PLUGIN_OPTS -c -O2 -fPIC $srcdir/hello3.c -# Put our assemblt file between C files, so that the gap will be noticed. +# Put our assembled file between C files, so that the gap will be noticed. $GCC -nostartfiles -e 0 hello.o hello2.o gap1.o hello3.o -o assembler-gap-test1.exe \ -Wl,--defsym,extern_func3=0 \ -Wl,-z,now -pie -$ANNOCHECK -v -v --enable-builtby --all assembler-gap-test1.exe > assembler-gap-test1.out +$ANNOCHECK -v assembler-gap-test1.exe > assembler-gap-test1.out # FAIL if a gap is NOT reported -grep --silent -e "Gaps were detected" assembler-gap-test1.out +grep --silent -e "gaps were detected" assembler-gap-test1.out if [ $? != 0 ]; then echo "assembler-gap-test: FAIL: GAP *not* found in notes:" @@ -71,10 +71,10 @@ $GCC -nostartfiles -e 0 hello.o hello2.o gap2.o hello3.o -o assembler-gap-test2. -Wl,--defsym,extern_func3=0 \ -Wl,-z,now -pie -$ANNOCHECK -v assembler-gap-test2.exe > assembler-gap-test2.out +$ANNOCHECK -v assembler-gap-test2.exe --skip-all --test-notes > assembler-gap-test2.out # FAIL if a gap is NOT reported -grep --silent -e "No gaps found" assembler-gap-test2.out +grep --silent -e "no gaps found" assembler-gap-test2.out if [ $? != 0 ]; then echo "assembler-gap-test: FAIL: GAP found in notes:" diff --git a/tests/glibc-notes-test b/tests/glibc-notes-test new file mode 100755 index 0000000..ab9e639 --- /dev/null +++ b/tests/glibc-notes-test @@ -0,0 +1,42 @@ +#!/bin/bash + +# Copyright (c) 2017-2021 Red Hat. +# +# This is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published +# by the Free Software Foundation; either version 3, or (at your +# option) any later version. +# +# It is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# Mimics how glibc builds C sources without annotation. + +rm -f hello.o hello2.o hello3.o libhello.so glibc-notes.exe glibc-notes.out + +GCC=${GCC:-gcc} +ANNOCHECK=${ANNOCHECK:-../annocheck/annocheck} + +OPTS="-g -c -O2 -fpie" + +$GCC -Wl,-a,--generate-missing-build-notes=yes $OPTS $srcdir/hello.c +$GCC -Wl,-a,--generate-missing-build-notes=yes $OPTS $srcdir/hello2.c +$GCC -Wl,-a,--generate-missing-build-notes=yes $OPTS $srcdir/hello3.c +$GCC -Wl,-a,--generate-missing-build-notes=yes $OPTS -shared $srcdir/hello_lib.c -o libhello.so + +# Link without system files as these may not have been hardened. +$GCC -pie -Wl,-z,now hello.o hello2.o hello3.o -L. -lhello -o glibc-notes.exe + +# Run annocheck + +$ANNOCHECK glibc-notes.exe --skip-cf-protection --skip-property-note --ignore-gaps > glibc-notes.out +grep -e "PASS" glibc-notes.out +if [ $? != 0 ]; +then + echo "glibc-notes-test: FAIL: generating assembler notes did not hide lack of GCC notes" + cat glibc-notes.out + exit 1 +fi + diff --git a/tests/hardening-test b/tests/hardening-test index a4af379..8d29f07 100755 --- a/tests/hardening-test +++ b/tests/hardening-test @@ -73,3 +73,4 @@ $GCC -fplugin=$PLUGIN $PLUGIN_OPTS \ # The entry point test (on x86/x86_64) will fail with a MAYBE result because the entry point is 0... $ANNOCHECK -v --ignore-gaps --skip-entry hardening-test.exe --enable-timing + diff --git a/tests/lto-test b/tests/lto-test index f218025..3b5af6f 100755 --- a/tests/lto-test +++ b/tests/lto-test @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2017-2020 Red Hat. +# Copyright (c) 2017-2021 Red Hat. # # This is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published @@ -35,4 +35,12 @@ $GCC -fplugin=$PLUGIN $PLUGIN_OPTS \ # Run annocheck, but only enable the LTO test. -$ANNOCHECK -v --ignore-gaps --skip-all --test-lto lto-test.exe | grep -e "PASS: Compiled with LTO enabled" +$ANNOCHECK -v --ignore-gaps --skip-all --test-lto lto-test.exe --fixed-format-messages > lto-test.out + +grep -e "PASS: test: lto" lto-test.out +if [ $? != 0 ]; +then + echo "lto-test: FAIL: did not detect LTO compilation" + cat lto-test.out + exit 1 +fi diff --git a/tests/objcopy-test b/tests/objcopy-test index 0b65278..d1fe640 100755 --- a/tests/objcopy-test +++ b/tests/objcopy-test @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2017-2020 Red Hat. +# Copyright (c) 2017-2021 Red Hat. # # This is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published @@ -39,4 +39,12 @@ $OBJCOPY --merge-notes objcopy-test.exe objcopy-merged.exe # Run annocheck, but only enable the LTO test. -$ANNOCHECK -v --ignore-gaps --skip-all --test-optimization objcopy-merged.exe | grep -e "PASS: Compiled with sufficient optimization" +$ANNOCHECK -v --ignore-gaps --skip-all --test-optimization objcopy-merged.exe > objcopy-test.out +grep -e "PASS: optimization test" objcopy-test.out +if [ $? != 0 ]; +then + echo "objcopy-test: FAIL: did not confirm sufficient optimization" + cat objcopy-test.out + exit 1 +fi +