{
TOOL_UNKNOWN = 0,
TOOL_MIXED,
- TOOL_GCC,
- TOOL_GAS,
TOOL_CLANG,
- TOOL_LLVM,
TOOL_FORTRAN,
+ TOOL_GAS,
+ TOOL_GCC,
+ TOOL_GIMPLE,
TOOL_GO,
+ TOOL_LLVM,
TOOL_RUST
};
uint version;
enum lang lang;
-
+
+ bool has_gimple;
bool warned_producer;
bool warned_about_instrumentation;
bool warned_version_mismatch;
static inline bool
is_compiler (enum tool tool)
{
- return tool == TOOL_GCC || tool == TOOL_CLANG || tool == TOOL_LLVM;
+ return tool == TOOL_GCC || tool == TOOL_CLANG || tool == TOOL_LLVM || tool == TOOL_GIMPLE;
}
static inline bool
return per_file.tool == TOOL_MIXED;
}
+static inline bool
+built_with_gimple (void)
+{
+ return per_file.has_gimple;
+}
+
static void
warn (annocheck_data * data, const char * message)
{
einfo (VERBOSE, "%s: info: %s", data->filename, message);
}
-static bool
-start (annocheck_data * data)
-{
- /* (Re) Set the results for the tests. */
- int i;
-
- for (i = 0; i < TEST_MAX; i++)
- {
- tests [i].num_pass = 0;
- tests [i].num_fail = 0;
- tests [i].num_maybe = 0;
- }
-
- /* Initialise other per-file variables. */
- memset (& per_file, 0, sizeof per_file);
- per_file.text_section_name_index = -1;
-
- if (num_allocated_ranges)
- {
- free (ranges);
- ranges = NULL;
- next_free_range = num_allocated_ranges = 0;
- }
-
- if (data->is_32bit)
- {
- Elf32_Ehdr * hdr = elf32_getehdr (data->elf);
-
- per_file.e_type = hdr->e_type;
- per_file.e_machine = hdr->e_machine;
- per_file.e_entry = hdr->e_entry;
- per_file.is_little_endian = hdr->e_ident[EI_DATA] != ELFDATA2MSB;
- }
- else
- {
- Elf64_Ehdr * hdr = elf64_getehdr (data->elf);
-
- per_file.e_type = hdr->e_type;
- per_file.e_machine = hdr->e_machine;
- per_file.e_entry = hdr->e_entry;
- per_file.is_little_endian = hdr->e_ident[EI_DATA] != ELFDATA2MSB;
- }
-
- /* 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 ++;
-
- return true;
-}
-
-static bool
-interesting_sec (annocheck_data * data,
- annocheck_section * sec)
-{
- if (disabled)
- return false;
-
- /* .dwz files have a .gdb_index section. */
- if (streq (sec->secname, ".gdb_index"))
- per_file.debuginfo_file = true;
-
- if (streq (sec->secname, ".text"))
- {
- /* Separate debuginfo files have a .text section with a non-zero
- size but no contents! */
- 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;
- return false; /* We do not actually need to scan the contents of the .text section. */
- }
- else 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;
- else
- ++ tests[TEST_GNU_STACK].num_fail;
-
- return false;
- }
-
- /* Note the permissions on GOT/PLT relocation sections. */
- 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;
- return false;
- }
-
- if (sec->shdr.sh_size == 0)
- return false;
-
- if (streq (sec->secname, ".comment"))
- return true;
-
- if (streq (sec->secname, ".gnu.attributes"))
- return true;
-
- /* These types of section need further processing. */
- return sec->shdr.sh_type == SHT_DYNAMIC
- || sec->shdr.sh_type == SHT_NOTE
- || sec->shdr.sh_type == SHT_STRTAB;
-}
-
-static bool
-interesting_note_sec (annocheck_data * data,
- annocheck_section * sec)
-{
- if (disabled)
- return false;
-
- return sec->shdr.sh_type == SHT_NOTE;
-}
-
-static inline unsigned long
-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 char * buffer = NULL;
- const char * sym;
- int res;
-
- 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);
-
- if (sym == NULL)
- {
- if (note_data->start == note_data->end)
- res = asprintf (& buffer, "address: %#lx", note_data->start);
- else
- res = asprintf (& buffer, "addr range: %#lx..%#lx", note_data->start, note_data->end);
- }
- else
- res = asprintf (& buffer, "component: %s", sym);
-
- if (res > 0)
- return buffer;
- return NULL;
-}
-
-static const char *
-stack_prot_type (uint value)
-{
- switch (value)
- {
- 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 "<unknown>";
- }
-}
-
static bool
skip_check (enum test_index check, const char * component_name)
{
return false;
}
-static void
-record_range (ulong start, ulong end)
-{
- if (start == end)
- return;
-
- assert (start < end);
-
- if (next_free_range >= num_allocated_ranges)
- {
- num_allocated_ranges += RANGE_ALLOC_DELTA;
- size_t num = num_allocated_ranges * sizeof ranges[0];
-
- if (ranges == NULL)
- ranges = xmalloc (num);
- else
- ranges = xrealloc (ranges, num);
- }
-
- /* Nothing clever here. Just record the data. */
- ranges[next_free_range].start = start;
- ranges[next_free_range].end = 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 const char *
-get_producer_name (enum tool tool)
+get_tool_name (enum tool tool)
{
switch (tool)
{
default: return "<unrecognised>";
case TOOL_UNKNOWN: return "<unknown>";
case TOOL_MIXED: return "<mixed>";
- case TOOL_GCC: return "gcc";
- case TOOL_GAS: return "gas";
case TOOL_CLANG: return "clang";
- case TOOL_LLVM: return "llvm";
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";
}
}
unsigned int version,
const char * source)
{
- einfo (VERBOSE2, "info: Record producer %s version %u source %s", get_producer_name (tool), version, source);
+ 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)
{
per_file.tool = tool;
per_file.version = version;
- einfo (VERBOSE, "%s: info: Set binary producer to %s version %u", data->filename, get_producer_name (tool), version);
+ einfo (VERBOSE, "%s: info: Set binary producer to %s version %u", data->filename, get_tool_name (tool), version);
}
else if (per_file.tool == tool)
{
per_file.warned_producer = true;
}
+ per_file.tool = TOOL_GCC;
+ }
+ else if ((per_file.tool == TOOL_GIMPLE && is_compiler (tool))
+ || (built_by_compiler () && tool == TOOL_GIMPLE))
+ {
+ if (! per_file.warned_producer)
+ {
+ info (data, "Mixed Gimple and GCC detected - treating as pure GCC");
+ per_file.warned_producer = true;
+ }
+
per_file.tool = TOOL_GCC;
}
else if (! built_by_mixed ())
{
einfo (VERBOSE, "%s: info: This binary was built by more than one tool (%s and %s)",
data->filename,
- get_producer_name (per_file.tool),
- get_producer_name (tool));
+ get_tool_name (per_file.tool),
+ get_tool_name (tool));
per_file.warned_producer = true;
}
- if (per_file.tool != TOOL_CLANG && tool != TOOL_CLANG)
- {
- per_file.tool = TOOL_MIXED;
- per_file.version = 0;
- }
- else if (tool == TOOL_CLANG)
- {
- per_file.tool = TOOL_CLANG;
- per_file.version = version;
- }
- }
+ if (per_file.tool != TOOL_CLANG && tool != TOOL_CLANG)
+ {
+ per_file.tool = TOOL_MIXED;
+ per_file.version = 0;
+ }
+ else if (tool == TOOL_CLANG)
+ {
+ per_file.tool = TOOL_CLANG;
+ per_file.version = version;
+ }
+ }
+}
+
+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)
+{
+ Dwarf_Word val;
+
+ if (dwarf_formudata (attr, & val) != 0)
+ {
+ warn (data, "Unable to parse DW_AT_language attribute");
+ return;
+ }
+
+ einfo (VERBOSE2, "%s: DW_AT_language = %x", data->filename, (int) val);
+
+ switch (val)
+ {
+ case DW_LANG_C89:
+ case DW_LANG_C:
+ case DW_LANG_C99:
+ case DW_LANG_ObjC:
+ case DW_LANG_C11:
+ set_lang (data, LANG_C, "DW_AT_language");
+ break;
+
+ case DW_LANG_C_plus_plus:
+ case DW_LANG_ObjC_plus_plus:
+ 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");
+ break;
+
+ default:
+ einfo (VERBOSE, "%s: Written in a language other than C and CC++", data->filename);
+ set_lang (data, LANG_OTHER, "DW_AT_language");
+ break;
+ }
+}
+
+static void
+parse_dw_at_producer (annocheck_data * data, Dwarf_Attribute * attr)
+{
+ const char * string = dwarf_formstring (attr);
+
+ if (string == NULL)
+ {
+ unsigned int form = dwarf_whatform (attr);
+
+ if (form == DW_FORM_GNU_strp_alt)
+ warn (data, "DW_FORM_GNU_strp_alt not yet handled");
+ else
+ warn (data, "DWARF DW_AT_producer attribute uses non-string form");
+ /* Keep scanning - there may be another DW_AT_producer attribute. */
+ return;
+ }
+
+ einfo (VERBOSE2, "%s: DW_AT_producer = %s", data->filename, string);
+
+ /* 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;
+
+ 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);
+ break;
+ }
+
+ if (madeby == TOOL_UNKNOWN)
+ {
+ /* 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)
+ 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");
+
+ /* The DW_AT_producer string may also contain some of the command
+ line options that were used to compile the binary. This happens
+ when using the -grecord-gcc-switches option for example. So we
+ have an opportunity to check for producer-specific command line
+ 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);
+ }
+
+ 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 (! 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 (! 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 ((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)
+ {
+ warn (data, "Command line options not recorded by -grecord-gcc-switches");
+ per_file.warned_command_line = true;
+ }
+
+ break;
+ }
+}
+
+/* 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 attr;
+
+ if (dwarf_attr (die, DW_AT_language, & attr) != NULL)
+ parse_dw_at_language (data, & attr);
+
+ if (dwarf_attr (die, DW_AT_producer, & attr) != NULL)
+ parse_dw_at_producer (data, & attr);
+
+ /* Keep scanning. */
+ return true;
+}
+
+static bool
+start (annocheck_data * data)
+{
+ /* (Re) Set the results for the tests. */
+ int i;
+
+ for (i = 0; i < TEST_MAX; i++)
+ {
+ tests [i].num_pass = 0;
+ tests [i].num_fail = 0;
+ tests [i].num_maybe = 0;
+ }
+
+ /* Initialise other per-file variables. */
+ memset (& per_file, 0, sizeof per_file);
+ per_file.text_section_name_index = -1;
+
+ if (num_allocated_ranges)
+ {
+ free (ranges);
+ ranges = NULL;
+ next_free_range = num_allocated_ranges = 0;
+ }
+
+ if (data->is_32bit)
+ {
+ Elf32_Ehdr * hdr = elf32_getehdr (data->elf);
+
+ per_file.e_type = hdr->e_type;
+ per_file.e_machine = hdr->e_machine;
+ per_file.e_entry = hdr->e_entry;
+ per_file.is_little_endian = hdr->e_ident[EI_DATA] != ELFDATA2MSB;
+ }
+ else
+ {
+ Elf64_Ehdr * hdr = elf64_getehdr (data->elf);
+
+ per_file.e_type = hdr->e_type;
+ per_file.e_machine = hdr->e_machine;
+ per_file.e_entry = hdr->e_entry;
+ per_file.is_little_endian = hdr->e_ident[EI_DATA] != ELFDATA2MSB;
+ }
+
+ /* 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 ++;
+
+ /* Check to see if something other than gcc produced parts
+ of this binary. */
+ (void) annocheck_walk_dwarf (data, hardened_dwarf_walker, NULL);
+
+ return true;
+}
+
+static bool
+interesting_sec (annocheck_data * data,
+ annocheck_section * sec)
+{
+ if (disabled)
+ return false;
+
+ /* .dwz files have a .gdb_index section. */
+ if (streq (sec->secname, ".gdb_index"))
+ per_file.debuginfo_file = true;
+
+ if (streq (sec->secname, ".text"))
+ {
+ /* Separate debuginfo files have a .text section with a non-zero
+ size but no contents! */
+ 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;
+ return false; /* We do not actually need to scan the contents of the .text section. */
+ }
+ else 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;
+ else
+ ++ tests[TEST_GNU_STACK].num_fail;
+
+ return false;
+ }
+
+ /* Note the permissions on GOT/PLT relocation sections. */
+ 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;
+ return false;
+ }
+
+ if (sec->shdr.sh_size == 0)
+ return false;
+
+ if (streq (sec->secname, ".comment"))
+ return true;
+
+ if (streq (sec->secname, ".gnu.attributes"))
+ return true;
+
+ /* These types of section need further processing. */
+ return sec->shdr.sh_type == SHT_DYNAMIC
+ || sec->shdr.sh_type == SHT_NOTE
+ || sec->shdr.sh_type == SHT_STRTAB;
+}
+
+static bool
+interesting_note_sec (annocheck_data * data,
+ annocheck_section * sec)
+{
+ if (disabled)
+ return false;
+
+ return sec->shdr.sh_type == SHT_NOTE;
+}
+
+static inline unsigned long
+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 char * buffer = NULL;
+ const char * sym;
+ int res;
+
+ 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);
+
+ if (sym == NULL)
+ {
+ if (note_data->start == note_data->end)
+ res = asprintf (& buffer, "address: %#lx", note_data->start);
+ else
+ res = asprintf (& buffer, "addr range: %#lx..%#lx", note_data->start, note_data->end);
+ }
+ else
+ res = asprintf (& buffer, "component: %s", sym);
+
+ if (res > 0)
+ return buffer;
+ return NULL;
+}
+
+static const char *
+stack_prot_type (uint value)
+{
+ switch (value)
+ {
+ 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 "<unknown>";
+ }
+}
+
+static void
+record_range (ulong start, ulong end)
+{
+ if (start == end)
+ return;
+
+ assert (start < end);
+
+ if (next_free_range >= num_allocated_ranges)
+ {
+ num_allocated_ranges += RANGE_ALLOC_DELTA;
+ size_t num = num_allocated_ranges * sizeof ranges[0];
+
+ if (ranges == NULL)
+ ranges = xmalloc (num);
+ else
+ ranges = xrealloc (ranges, num);
+ }
+
+ /* Nothing clever here. Just record the data. */
+ ranges[next_free_range].start = start;
+ ranges[next_free_range].end = 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);
}
-struct tool_string
-{
- const char * lead_in;
- const char * tool_name;
- enum tool tool_id;
-};
-
static ulong
get_4byte_value (const unsigned char * data)
{
/* FIXME: At the moment the clang plugin is unable to detect -Wall.
for clang v9+. */
if (built_by_clang () && per_file.version > 8)
- break;
- if (built_by_mixed ())
- break;
-
- if (! skip_check (TEST_WARNINGS, get_component_name (data, sec, note_data, prefer_func_name)))
+ ;
+ 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);
return true;
}
-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 },
- { "Go cmd/compile", TOOL_GO },
- { "GNU AS", TOOL_GAS },
- { NULL, 0 }
-};
-
-static void
-parse_dw_at_language (annocheck_data * data, Dwarf_Attribute * attr)
-{
- Dwarf_Word val;
-
- if (dwarf_formudata (attr, & val) != 0)
- {
- warn (data, "Unable to parse DW_AT_language attribute");
- return;
- }
-
- einfo (VERBOSE2, "%s: DW_AT_language = %x", data->filename, (int) val);
-
- switch (val)
- {
- case DW_LANG_C89:
- case DW_LANG_C:
- case DW_LANG_C99:
- case DW_LANG_ObjC:
- case DW_LANG_C11:
- set_lang (data, LANG_C, "DW_AT_language");
- break;
-
- case DW_LANG_C_plus_plus:
- case DW_LANG_ObjC_plus_plus:
- 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");
- break;
-
- default:
- einfo (VERBOSE, "%s: Written in a language other than C and CC++", data->filename);
- set_lang (data, LANG_OTHER, "DW_AT_language");
- break;
- }
-}
-
-static void
-parse_dw_at_producer (annocheck_data * data, Dwarf_Attribute * attr)
-{
- const char * string = dwarf_formstring (attr);
-
- if (string == NULL)
- {
- unsigned int form = dwarf_whatform (attr);
-
- if (form == DW_FORM_GNU_strp_alt)
- warn (data, "DW_FORM_GNU_strp_alt not yet handled");
- else
- warn (data, "DWARF DW_AT_producer attribute uses non-string form");
- /* Keep scanning - there may be another DW_AT_producer attribute. */
- return;
- }
-
- einfo (VERBOSE2, "%s: DW_AT_producer = %s", data->filename, string);
-
- /* 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;
-
- 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);
- break;
- }
-
- if (madeby == TOOL_UNKNOWN)
- {
- /* 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)
- 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 && per_file.tool == TOOL_UNKNOWN)
- info (data, "Discovered non-gcc code producer, skipping gcc specific checks");
-
- set_producer (data, madeby, version, "DW_AT_producer");
-
- /* The DW_AT_producer string may also contain some of the command
- line options that were used to compile the binary. This happens
- when using the -grecord-gcc-switches option for example. So we
- have an opportunity to check for producer-specific command line
- 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);
- }
-
- 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 (! 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 (! 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
- einfo (VERBOSE2, "%s: MAYB: -Wall/-Wformat-security not found in DW_AT_producer string", data->filename);
- }
-
- 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)
- {
- warn (data, "Command line options not recorded by -grecord-gcc-switches");
- per_file.warned_command_line = true;
- }
-
- break;
- }
-}
-
-/* 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 attr;
-
- if (dwarf_attr (die, DW_AT_language, & attr) != NULL)
- parse_dw_at_language (data, & attr);
-
- if (dwarf_attr (die, DW_AT_producer, & attr) != NULL)
- parse_dw_at_producer (data, & attr);
-
- /* Keep scanning. */
- return true;
-}
-
static void
fail (annocheck_data * data, const char * message)
{
{
if (results->num_fail > 0)
{
- if (BE_VERBOSE)
+ 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_pass == 0)
{
- if (! built_by_compiler ())
- skip (data, "Checking for warning options (not built by gcc");
+ 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");
}
if (disabled || per_file.debuginfo_file)
return true;
- /* Check to see if something other than gcc produced parts
- of this binary. */
- (void) annocheck_walk_dwarf (data, hardened_dwarf_walker, NULL);
-
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