]> sourceware.org Git - annobin.git/commitdiff
9.41: annocheck: detect and handle gimple compiled binaries.
authorNick Clifton <nickc@redhat.com>
Tue, 10 Nov 2020 16:50:19 +0000 (16:50 +0000)
committerNick Clifton <nickc@redhat.com>
Tue, 10 Nov 2020 16:50:19 +0000 (16:50 +0000)
annobin-global.h
annocheck/hardened.c
tests/lto-test

index f694b8e92b74f944703c119bd0bd6cc5e7285704..d689e92c81f91f719f3061a686d62a51813f9858 100644 (file)
@@ -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 940
+#define ANNOBIN_VERSION 941
 
 /* The version of the annotation specification supported.  */
 #define SPEC_VERSION  3
index 1003ffbc4057311f331fd244d108e01882aeebee..ed702689cf8cafe562daa2647c0465e83527b535 100644 (file)
@@ -34,12 +34,13 @@ enum tool
 {
   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
 };
 
@@ -77,7 +78,8 @@ static struct per_file
   uint        version;
 
   enum lang   lang;
-  
+
+  bool        has_gimple;
   bool        warned_producer;
   bool        warned_about_instrumentation;
   bool        warned_version_mismatch;
@@ -201,7 +203,7 @@ static test tests [TEST_MAX] =
 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
@@ -228,6 +230,12 @@ built_by_mixed (void)
   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)
 {
@@ -242,182 +250,6 @@ info (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)
 {
@@ -488,82 +320,21 @@ 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";
     }
 }
@@ -612,13 +383,16 @@ set_producer (annocheck_data *     data,
              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)
     {
@@ -644,6 +418,17 @@ set_producer (annocheck_data *     data,
          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 ())
@@ -652,31 +437,506 @@ set_producer (annocheck_data *     data,
        {
          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)
 {
@@ -1452,11 +1712,12 @@ walk_build_notes (annocheck_data *     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);
@@ -2341,237 +2602,6 @@ check_seg (annocheck_data *    data,
   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)
 {
@@ -3049,7 +3079,9 @@ show_WARNINGS (annocheck_data * data, test * results)
 {
   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");
@@ -3060,8 +3092,10 @@ show_WARNINGS (annocheck_data * data, test * results)
     }
   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");
     }
@@ -3697,10 +3731,6 @@ finish (annocheck_data * data)
   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
index c2ed003e997057ab7c86144ebbd7d7fad5c0eaac..f21802538f5978a1ed7792d054e37a6ebf740cf0 100755 (executable)
@@ -19,7 +19,7 @@ ANNOCHECK=${ANNOCHECK:-../annocheck/annocheck}
 PLUGIN=${PLUGIN:-../gcc-plugin/.libs/annobin.so}
 
 PLUGIN_OPTS="-fplugin-arg-annobin-no-attach"
-OPTS="-g -c -O2 -fpie -flto"
+OPTS="-g -c -O2 -fpie -flto -D_FORTIFY_SOURCE=2"
 
 $GCC -fplugin=$PLUGIN $PLUGIN_OPTS $OPTS $srcdir/hello.c
 mv hello.o lto-hello.o
This page took 0.053022 seconds and 5 git commands to generate.