From bdc508eb4e3f348ebbd80ebf79f76a9bccaddb07 Mon Sep 17 00:00:00 2001 From: Nick Clifton Date: Tue, 15 Jun 2021 12:23:03 +0100 Subject: [PATCH] 9.75: annocheck: better detection of GO compiler version --- annobin-global.h | 2 +- annocheck/annocheck.c | 46 ++++++++++++++++++++++++++++++++++++ annocheck/annocheck.h | 14 ++++++++--- annocheck/hardened.c | 55 ++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 110 insertions(+), 7 deletions(-) diff --git a/annobin-global.h b/annobin-global.h index c940c0c..335ef63 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 974 +#define ANNOBIN_VERSION 975 /* The version of the annotation specification supported. */ #define SPEC_VERSION 3 diff --git a/annocheck/annocheck.c b/annocheck/annocheck.c index e09bf1f..a0f1dcf 100644 --- a/annocheck/annocheck.c +++ b/annocheck/annocheck.c @@ -1096,6 +1096,52 @@ ends_with (const char * string, const char * ending, const size_t end_len) && streq (string + (len - end_len), ending)); } +bool +annocheck_find_symbol_by_name (annocheck_data * data, const char * name, + ulong * value_return, uint * section_return) +{ + /* Search for symbol sections. */ + Elf_Scn * sym_sec = NULL; + + while ((sym_sec = elf_nextscn (data->elf, sym_sec)) != NULL) + { + Elf64_Shdr sym_shdr; + + read_section_header (data, sym_sec, & sym_shdr); + + if ((sym_shdr.sh_type != SHT_SYMTAB) && (sym_shdr.sh_type != SHT_DYNSYM)) + continue; + + Elf_Data * sym_data; + + if ((sym_data = elf_getdata (sym_sec, NULL)) == NULL) + { + einfo (VERBOSE2, "Unable to load symbol section"); + /* FIXME: Warn ?? */ + continue; + } + + GElf_Sym sym; + uint symndx; + + for (symndx = 1; gelf_getsym (sym_data, symndx, & sym) != NULL; symndx++) + { + const char * symname = elf_strptr (data->elf, sym_shdr.sh_link, sym.st_name); + + if (streq (name, symname)) + { + if (value_return != NULL) + * value_return = sym.st_value; + if (section_return != NULL) + * section_return = sym.st_shndx; + return true; + } + } + } + + return false; +} + typedef struct find_symbol_return { const char * name; diff --git a/annocheck/annocheck.h b/annocheck/annocheck.h index 607bfc8..a2f095e 100644 --- a/annocheck/annocheck.h +++ b/annocheck/annocheck.h @@ -182,7 +182,7 @@ typedef bool (* note_walker) (annocheck_data * DATA, Stops if FUNC returns FALSE. Passes PTR to FNC along with a pointer to the note and the offsets to the name and desc data fields. Returns FALSE if it could not walk the notes. */ -extern bool annocheck_walk_notes (annocheck_data * DATA, annocheck_section * SEC, note_walker FNC, void * PTR); +extern bool annocheck_walk_notes (annocheck_data * DATA, annocheck_section * SEC, note_walker FNC, void * PTR); /* Type for the DWARF DIE walker. */ typedef bool (* dwarf_walker) (annocheck_data * DATA, Dwarf * DWARF, Dwarf_Die * DIE, void * PTR); @@ -191,13 +191,13 @@ typedef bool (* dwarf_walker) (annocheck_data * DATA, Dwarf * DWARF, Dwarf_Die Stops if FNC returns FALSE. Passes PTR to FUNC along with a pointer to the DIE. Returns FALSE if it could not walk the debug information. */ -extern bool annocheck_walk_dwarf (annocheck_data * DATA, dwarf_walker FNC, void * PTR); +extern bool annocheck_walk_dwarf (annocheck_data * DATA, dwarf_walker FNC, void * PTR); /* Called to register a checker. Returns FALSE if the checker could not be registered. Can be called from static constructors. The MAJOR version number is used to verify that the checker is compatible with the framework. */ -extern bool annocheck_add_checker (struct checker * CHECKER, uint MAJOR); +extern bool annocheck_add_checker (struct checker * CHECKER, uint MAJOR); /* Return the name of a symbol most appropriate for address START..END. Returns NULL if no symbol could be found. */ @@ -206,10 +206,18 @@ extern const char * annocheck_find_symbol_for_address_range /* Return the name of a symbol most appropriate for address START..END. Returns NULL if no symbol could be found. + If non-NULL SEC is examined first, if it is a symbol section. 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); +/* Look for symbol NAME. + Returns TRUE if found, FALSE otherwise. + If found returns the symbol's value in VALUE_RETURN (if non-NULL) + and the section index in SECTION_RETURN (again if non-NULL). */ +extern bool annocheck_find_symbol_by_name + (annocheck_data * DATA, const char * NAME, ulong * VALUE_RETURN, uint * SECTION_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/hardened.c b/annocheck/hardened.c index a8dda17..a888b17 100644 --- a/annocheck/hardened.c +++ b/annocheck/hardened.c @@ -39,6 +39,7 @@ #define SOURCE_SKIP_CHECKS "special case exceptions" #define SOURCE_STRING_SECTION "string section" #define SOURCE_COMMENT_SECTION "comment section" +#define SOURCE_RODATA_SECTION ".rodata section" #define GOLD_COLOUR "\e[33;40m" #define RED_COLOUR "\x1B[31;47m" @@ -616,7 +617,11 @@ add_producer (annocheck_data * data, { 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"); + { + /* This is not a MAYB result, because stripped GO binaries can trigger this result. */ + einfo (VERBOSE2, "%s: info: GO compilation detected, but version is unknown. Source: %s", + data->filename, source); + } } else if (version < MIN_GO_REVISION) { @@ -660,8 +665,22 @@ add_producer (annocheck_data * data, { if (per_file.tool_version != version && version > 0) { - if (per_file.tool_version < version) - per_file.tool_version = version; + if (per_file.tool_version == 0) + { + einfo (VERBOSE, "%s: info: set binary producer to %s version %u", get_filename (data), get_tool_name (tool), version); + per_file.tool_version = version; + } + else if (per_file.tool_version < version) + { + einfo (VERBOSE, "%s: info: change %s binary producer from version %u to version %u", + get_filename (data), get_tool_name (tool), per_file.tool_version, version); + per_file.tool_version = version; + } + else + { + einfo (VERBOSE2, "%s: info: ignore change in %s binary producer from version %u to version %u", + get_filename (data), get_tool_name (tool), per_file.tool_version, version); + } } } else @@ -1096,6 +1115,10 @@ interesting_sec (annocheck_data * data, if (streq (sec->secname, ".gnu.attributes")) return true; + if (streq (sec->secname, ".rodata")) + /* We might want to scan this section for a GO version string. */ + return true; + /* These types of section need further processing. */ return sec->shdr.sh_type == SHT_DYNAMIC || sec->shdr.sh_type == SHT_NOTE @@ -2698,6 +2721,16 @@ check_note_section (annocheck_data * data, if (streq (sec->secname, ".note.go.buildid")) { + /* The go buildid note does not contain version information. But + it does tell us that GO was used to build the binary. + + What we should now do is look for the "runtime.buildVersion" + symbol, find the relocation that sets its value, parse that + relocation, and then search at the resulting address in the + .rodata section in order to find the GO build version string. + But that is complex and target specific, so instead there is + a hack in check_code_section() to scan the .rodata section + directly. */ add_producer (data, TOOL_GO, 0, ".note.go.buildid", true); } @@ -2892,6 +2925,22 @@ static bool check_code_section (annocheck_data * data, annocheck_section * sec) { + if (per_file.current_tool == TOOL_GO && streq (sec->secname, ".rodata")) + { + /* Look for a GO compiler build version. See check_note_section() + for why we cannot use the .note.go.buildid section. */ + const char * go_version = memmem (sec->data->d_buf, sec->data->d_size, "go1.", 4); + + if (go_version != NULL) + { + uint version, revision; + + if (sscanf (go_version + 4, "%u.%u", & version, & revision) == 2) + add_producer (data, TOOL_GO, version, SOURCE_RODATA_SECTION, false); + } + return true; + } + /* At the moment we are only interested in the .comment section. */ if (sec->data->d_size <= 11 || ! streq (sec->secname, ".comment")) return true; -- 2.43.5