Blob Blame History Raw
/* Checks the hardened status of the given file.
   Copyright (c) 2018 - 2020 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.

  You should have received a copy of the GNU General Public
  License along with this program; see the file COPYING3. If not,
  see <http://www.gnu.org/licenses/>.

  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.  */

#include "annobin-global.h"
#include "annocheck.h"

typedef struct hardened_note_data
{
  ulong         start;
  ulong         end;
} hardened_note_data;

/* Set by the constructor.  */
static bool disabled = false;

/* Can be changed by a command line option.  */
static bool ignore_gaps = false;

enum tool
{
  TOOL_UNKNOWN = 0,
  TOOL_MIXED,
  TOOL_CLANG,
  TOOL_FORTRAN,
  TOOL_GAS,
  TOOL_GCC,
  TOOL_GIMPLE,
  TOOL_GO,
  TOOL_LLVM,
  TOOL_RUST
};

enum lang
{
  LANG_UNKNOWN = 0,
  LANG_C,
  LANG_CXX,
  LANG_OTHER
};

/* The contents of this structure are used on a per-input-file basis.
   The fields are initialised by start().  */
static struct per_file
{
  Elf64_Half  e_type;
  Elf64_Half  e_machine;
  Elf64_Addr  e_entry;
  ulong       text_section_name_index;
  ulong       text_section_alignment;
  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;

  enum tool   tool;
  uint        tool_version;

  uint        note_source[256];

  enum lang   lang;

  bool        has_gimple;
  bool        warned_producer;
  bool        warned_about_instrumentation;
  bool        warned_version_mismatch;
  bool        warned_command_line;
  bool        other_language;
  bool        also_written;
} per_file;

static hardened_note_data *  ranges = NULL;
static unsigned              num_allocated_ranges = 0;
static unsigned              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.  */

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;
  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_BIND_NOW,
  TEST_BRANCH_PROTECTION,
  TEST_CF_PROTECTION,
  TEST_DYNAMIC_SEGMENT,
  TEST_DYNAMIC_TAGS,
  TEST_ENTRY,
  TEST_FORTIFY,
  TEST_GLIBCXX_ASSERTIONS,
  TEST_GNU_RELRO,
  TEST_GNU_STACK,
  TEST_LTO,
  TEST_OPTIMIZATION,
  TEST_PIC,
  TEST_PIE,
  TEST_PROPERTY_NOTE,
  TEST_RUN_PATH,
  TEST_RWX_SEG,
  TEST_SHORT_ENUM,
  TEST_STACK_CLASH,
  TEST_STACK_PROT,
  TEST_STACK_REALIGN,
  TEST_TEXTREL,
  TEST_THREADS,
  TEST_WARNINGS,
  TEST_WRITEABLE_GOT,

  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 TEST(name,upper,description) \
  [ TEST_##upper ] = { true, 0, 0, 0, #name, description, show_ ## upper }

/* 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 (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)"),
  TEST (dynamic-segment,    DYNAMIC_SEGMENT,    "There is at most one dynamic segment/section"),
  TEST (dynamic-tags,       DYNAMIC_TAGS,       "Dynamic tags for PAC & BTI present (AArch64 only)"),
  TEST (entry,              ENTRY,              "The first instruction is ENDBR (x86 only)"),
  TEST (fortify,            FORTIFY,            "Compiled with -D_FORTIFY_SOURCE=2"),
  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 (lto,                LTO,                "Compiled with -flto"),
  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"),
  TEST (property-note,      PROPERTY_NOTE,      "Correctly formatted GNU Property notes (x86_64, aarch64, PowerPC)"),
  TEST (run-path,           RUN_PATH,           "All runpath entries are under /usr"),
  TEST (rwx-seg,            RWX_SEG,            "There are no segments that are both writeable and executable"),
  TEST (short-enum,         SHORT_ENUM,         "Compiled with consistent use of -fshort-enum"),
  TEST (stack-clash,        STACK_CLASH,        "Compiled with -fstack-clash-protection (not ARM)"),
  TEST (stack-prot,         STACK_PROT,         "Compiled with -fstack-protector-strong"),
  TEST (stack-realign,      STACK_REALIGN,      "Compiled with -mstackrealign (i686 only)"),
  TEST (textrel,            TEXTREL,            "There are no text relocations in the binary"),
  TEST (threads,            THREADS,            "Compiled with -fexceptions"),
  TEST (warnings,           WARNINGS,           "Compiled with -Wall"),
  TEST (writeable-got,      WRITEABLE_GOT,      "The .got section is not writeable"),
};

#ifdef DISABLE_FUTURE_FAIL
static bool report_future_fail = false;
#else
static bool report_future_fail = true;
#endif

static inline bool
is_compiler (enum tool tool)
{
  return tool == TOOL_GCC || tool == TOOL_CLANG || tool == TOOL_LLVM || tool == TOOL_GIMPLE;
}

static inline bool
built_by_compiler (void)
{
  return is_compiler (per_file.tool);
}

static inline bool
built_by_gcc (void)
{
  return per_file.tool == TOOL_GCC;
}

static inline bool
built_by_clang (void)
{
  return per_file.tool == TOOL_CLANG;
}

static inline bool
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)
{
  /* We use the VERBOSE setting rather than WARN because that way
     we not get a prefix.  */
  einfo (VERBOSE, "%s: WARN: %s", data->filename, message);
}

static void
info (annocheck_data * data, const char * message)
{
  einfo (VERBOSE, "%s: info: %s", data->filename, message);
}

static bool
skip_check (enum test_index check, const char * component_name)
{
  if (check < TEST_MAX && ! tests[check].enabled)
    return true;

  if (component_name == NULL)
    return false;

  if (const_strneq (component_name, "component: "))
    component_name += strlen ("component: ");

  if (streq (component_name, "elf_init.c")
      || streq (component_name, "init.c"))
    {
      if (check < TEST_MAX)
	einfo (VERBOSE2, "skipping test %s for component %s", tests[check].name, component_name);
      return true;
    }

  const static struct ignore
  {
    const char *     func_name;
    enum test_index  test_indicies[4];
  }
  skip_these_funcs[] =
  {
    /* We know that some glibc startup functions cannot be compiled
       with stack protection enabled.  So do not complain about them.  */
    { "_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 } },

    /* FIXME: Not sure about these two - they need some tests skipping
       but I do not think that they were stack tests...  */
    { "static_reloc.c", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } },
    { "_dl_relocate_static_pie", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } },

    /* The stack overflow support code does not need stack protection.  */
    { "__stack_chk_fail_local", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } },
    { "stack_chk_fail_local.c", { TEST_STACK_PROT, TEST_STACK_CLASH, TEST_STACK_REALIGN, TEST_MAX } },

    /* Also the atexit function in libiberty is only compiled with -fPIC not -fPIE.  */
    { "atexit", { TEST_PIC, TEST_PIE, TEST_MAX, 0 } }
  };

  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 (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;
      }

  return false;
}

static const char *
get_tool_name (enum tool tool)
{
  switch (tool)
    {
    default:           return "<unrecognised>";
    case TOOL_UNKNOWN: return "<unknown>";
    case TOOL_MIXED:   return "<mixed>";
    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";
    }
}

static const char *
get_lang_name (enum lang lang)
{
  switch (lang)
    {
    default:
    case LANG_UNKNOWN: return "unknown";
    case LANG_C: return "C";
    case LANG_CXX: return "C++";
    case LANG_OTHER: return "other";
    }
}

static void
set_lang (annocheck_data *  data,
	  enum lang         lang,
	  const char *      source)
{
  if (per_file.lang == LANG_UNKNOWN)
    {
      einfo (VERBOSE2, "%s: info: Written in %s (source: %s)",
	     data->filename, get_lang_name (lang), source);

      per_file.lang = lang;
    }
  else if (per_file.lang == lang)
    ;
  else
    {
      if (! per_file.also_written)
	{
	  einfo (VERBOSE, "%s: ALSO written in %s (source: %s)",
		 data->filename, get_lang_name (lang), source);
	  per_file.also_written = true;
	}
      /* 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)
	per_file.lang = lang;
    }
}

static void
set_producer (annocheck_data *     data,
	      enum tool            tool,
	      unsigned int         version,
	      const char *         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.tool_version = 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)
    {
      if (per_file.tool_version != version)
	{
	  if (! per_file.warned_producer)
	    {
	      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;
	    }

	  if (per_file.tool_version < version)
	    per_file.tool_version = version;
	}
    }
  else if ((per_file.tool == TOOL_GAS && is_compiler (tool))
	   || (built_by_compiler () && tool == TOOL_GAS))
    {
      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;
    }
  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 ())
    {
      if (! per_file.warned_producer)
	{
	  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 != TOOL_CLANG && tool != TOOL_CLANG)
	{
	  per_file.tool = TOOL_MIXED;
	  per_file.tool_version = 0;
	}
      else if (tool == TOOL_CLANG)
	{
	  per_file.tool = TOOL_CLANG;
	  per_file.tool_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:
      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);
	      break;
	    }
	  per_file.other_language = true;
	}
      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)
{
  if (disabled)
    return false;

  /* (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);
}

static ulong
get_4byte_value (const unsigned char * data)
{
  if (per_file.is_little_endian)
    return  data[0]
      | (((ulong) data[1]) << 8)
      | (((ulong) data[2]) << 16)
      | (((ulong) data[3]) << 24);
  else
    return data[3]
      | (((ulong) data[2]) << 8)
      | (((ulong) data[1]) << 16)
      | (((ulong) data[0]) << 24);
}

static void
report_note_producer (annocheck_data * data,
		      unsigned char    producer,
		      const char *     source,
		      unsigned int     version)
{
  if (! BE_VERBOSE)
    return;

  if (per_file.note_source[producer] == version)
    return;

  per_file.note_source[producer] = version;

  einfo (PARTIAL, "Hardened: %s: info: Notes produced by %s plugin ", data->filename, source);

  if (version == 0)
    einfo (PARTIAL, "(version unknown)\n");
  else if (version > 99 && version < 1000)
    einfo (PARTIAL, "version %u.%02u\n", version / 100, version % 100);
  else
    einfo (PARTIAL, "version %u\n", version);
}

static bool
walk_build_notes (annocheck_data *     data,
		  annocheck_section *  sec,
		  GElf_Nhdr *          note,
		  size_t               name_offset,
		  size_t               data_offset,
		  void *               ptr)
{
  bool                  prefer_func_name;
  hardened_note_data *  note_data;

  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);
      return false;
    }

  prefer_func_name = note->n_type == NT_GNU_BUILD_ATTRIBUTE_FUNC;
  note_data = (hardened_note_data *) ptr;

  if (note->n_namesz < 3)
    {
      einfo (FAIL, "%s: Corrupt annobin note, name size: %x", data->filename, note->n_namesz);
      return false;
    }

  if (note->n_descsz > 0)
    {
      ulong start = 0;
      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;
	  int shift;

	  if (per_file.is_little_endian)
	    {
	      for (shift = i = 0; i < 8; i++)
		{
		  ulong byte = descdata[i];

		  start |= byte << shift;
		  byte = descdata[i + 8];
		  end |= byte << shift;

		  shift += 8;
		}
	    }
	  else
	    {
	      for (shift = 0, i = 7; i >= 0; i--)
		{
		  ulong byte = descdata[i];

		  start |= byte << shift;
		  byte = descdata[i + 8];
		  end |= byte << shift;

		  shift += 8;
		}
	    }
	}
      else if (note->n_descsz == 8)
	{
	  start = get_4byte_value (descdata);
	  end   = get_4byte_value (descdata + 4);
	}
      else
	{
	  einfo (FAIL, "%s: Corrupt annobin note, desc size: %x",
		 data->filename, note->n_descsz);
	  return false;
	}

      if (start > end)
	{
	  if (per_file.e_machine == EM_PPC64 && (start - end) <= 4)
	    /* On the PPC64, start symbols are biased by 4, but end symbols are not...  */
	    start = end;
	  else
	    {
	      /* We ignore the case where the end address is 0, because this
		 happens when the linker discards a code section but does not
		 discard the notes.  (Eg because annobin is being run with -no-attach
		 enabled).  In such situations the notes should be ignored,
		 because they refer to code that has been discarded.  */
	      if (end == 0)
		return true;

	      einfo (FAIL, "%s: Corrupt annobin note, start address %#lx > end address %#lx",
		     data->filename, start, end);
	      return true;
	    }
	}

      note_data->start = start;
      note_data->end   = end;

      if (per_file.e_type != ET_REL && ! 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;
    }

  const char *  namedata = sec->data->d_buf + name_offset;
  uint          pos = (namedata[0] == 'G' ? 3 : 1);
  char          attr_type = namedata[pos - 1];
  const char *  attr = namedata + pos;

  /* Advance pos to the attribute's value.  */
  if (! isprint (* attr))
    pos ++;
  else
    pos += strlen (namedata + pos) + 1;

  const char *  string = namedata + pos;
  uint          value = -1;

  switch (attr_type)
    {
    case GNU_BUILD_ATTRIBUTE_TYPE_NUMERIC:
      {
	uint shift = 0;
	int bytes = (namedata + note->n_namesz) - string;

	value = 0;
	if (bytes > 0)
	  bytes --;
	while (bytes --)
	  {
	    uint byte = (* string ++) & 0xff;

	    /* Note - the watermark protocol dictates that numeric values are
	       always stored in little endian format, even if the target uses
	       big-endian.  */
	    value |= byte << shift;
	    shift += 8;
	  }
      }
      break;
    case GNU_BUILD_ATTRIBUTE_TYPE_STRING:
      break;
    case GNU_BUILD_ATTRIBUTE_TYPE_BOOL_TRUE:
      value = 1;
      break;
    case GNU_BUILD_ATTRIBUTE_TYPE_BOOL_FALSE:
      value = 0;
      break;
    default:
      einfo (VERBOSE, "ICE:  Unrecognised annobin note type %d", attr_type);
      return true;
    }

  switch (* attr)
    {
    case GNU_BUILD_ATTRIBUTE_VERSION:
      if (value != -1)
	{
	  einfo (VERBOSE, "ICE:  The version note should have a string attribute");
	  break;
	}

      /* Check the Watermark protocol revision.  */
      ++ attr;
      if (* attr <= '0')
	{
	  einfo (VERBOSE, "ICE:  The version contains an invalid specification number: %d", * attr - '0');
	  break;
	}

      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",
	       data->filename, SPEC_VERSION, * attr - '0');

      /* Check the note per_file.  */
      ++ attr;
      char producer = * attr;
      ++ attr;
      unsigned int version = 0;
      if (* attr != 0)
	version = strtod (attr, NULL);
      const char * name;
      switch (producer)
	{
	case ANNOBIN_TOOL_ID_ASSEMBLER:
	  name = "assembler";
	  break;

	case ANNOBIN_TOOL_ID_LINKER:
	  name = "linker";
	  break;

	case ANNOBIN_TOOL_ID_GCC_HOT:
	case ANNOBIN_TOOL_ID_GCC_COLD:
	case ANNOBIN_TOOL_ID_GCC_STARTUP:
	case ANNOBIN_TOOL_ID_GCC_EXIT:
	case ANNOBIN_TOOL_ID_GCC:
	  name = "gcc";
	  producer = ANNOBIN_TOOL_ID_GCC;
	  /* FIXME: Add code to check that the version of the
	     note producer is not greater than our version.  */

	  /* 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;
	  break;

	case ANNOBIN_TOOL_ID_LLVM:
	  name = "LLVM";
	  per_file.compiled_code_seen = true;
	  break;

	case ANNOBIN_TOOL_ID_CLANG:
	  name = "Clang";
	  per_file.compiled_code_seen = true;
	  break;

	default:
	  warn (data, "Unrecognised annobin note producer");
	  name = "unknown";
	  break;
	}

      report_note_producer (data, producer, name, version);
      break;

    case GNU_BUILD_ATTRIBUTE_TOOL:
      if (value != -1)
	{
	  einfo (VERBOSE, "ICE:  The tool note should have a string attribute");
	  break;
	}

      /* Parse the tool attribute looking for the version of gcc used to build the component.  */
      unsigned 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
	 compiler that ran the annobin plugin.  Look for these here.  Their
	 format is "annobin gcc X.Y.Z DATE" and "running gcc X.Y.Z DATE".  */
      static struct tool_string run_tool_strings [] =
	{
	 { "running gcc ", "gcc", TOOL_GCC },
	 { "running on clang version ", "clang", TOOL_CLANG },
	 { "running on LLVM version ", "llvm", TOOL_LLVM }
	};

      int i;
      for (i = ARRAY_SIZE (run_tool_strings); i--;)
	{
	  struct tool_string * t = run_tool_strings + i;

	  if (strncmp (attr + 1, t->lead_in, strlen (t->lead_in)) != 0)
	    continue;

	  if (sscanf (attr + 1 + strlen (t->lead_in), "%u.%u.%u", & major, & minor, & rel) != 3)
	    {
	      einfo (VERBOSE2, "lead in '%s' matched, but conversion failed.  Full string: '%s'", t->lead_in, attr + 1);
	      continue;
	    }

	  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);

	  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)",
		     data->filename, t->tool_name, per_file.run_major, major);
	      if (per_file.run_major < major)
		per_file.run_major = major;
	    }

	  if (per_file.anno_major != 0 && per_file.anno_major != per_file.run_major)
	    {
	      if (! per_file.warned_version_mismatch)
		{
		  einfo (INFO, "%s: ICE:  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;
		}
	    }

	  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))
	    {
	      einfo (VERBOSE, "%s: warn: Annobin plugin was built by %s %u.%u.%u but run on %s version %u.%u.%u",
		     data->filename, t->tool_name,
		     per_file.anno_major, per_file.anno_minor, per_file.anno_rel,
		     t->tool_name,
		     per_file.run_major, per_file.run_minor, per_file.run_rel);
	      einfo (VERBOSE, "%s: warn: If there are FAIL results that appear to be incorrect, it could be due to this discrepancy.",
		     data->filename);
	    }
	  break;
	}

      if (i >= 0)
	break;

      static struct tool_string build_tool_strings [] =
	{
	 { "annobin gcc ", "gcc", TOOL_GCC },
	 { "annobin built by clang version ", "clang", TOOL_CLANG },
	 { "annobin built by llvm version ", "llvm", TOOL_LLVM }
	};

      for (i = ARRAY_SIZE (build_tool_strings); i--;)
	{
	  struct tool_string * t = build_tool_strings + i;

	  if (strncmp (attr + 1, t->lead_in, strlen (t->lead_in)) != 0)
	    continue;

	  if (sscanf (attr + 1 + strlen (t->lead_in), "%u.%u.%u", & major, & minor, & rel) != 3)
	    {
	      einfo (VERBOSE2, "lead in '%s' matched, but conversion failed.  Full string: '%s'", t->lead_in, attr + 1);
	      continue;
	    }

	  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)
	    {
	      per_file.anno_major = major;
	    }
	  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)",
		     data->filename, t->tool_name, per_file.anno_major, major);
	      if (per_file.anno_major < major)
		per_file.anno_major = major;
	    }

	  if (per_file.run_major != 0 && per_file.run_major != per_file.anno_major)
	    {
	      if (! per_file.warned_version_mismatch)
		{
		  einfo (INFO, "%s: ICE:  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;
		}
	    }

	  per_file.anno_minor = minor;
	  per_file.anno_rel = rel;
	  if ((per_file.run_minor != 0 && per_file.run_minor != minor)
	      || (per_file.run_rel != 0 && per_file.run_rel != rel))
	    {
	      einfo (VERBOSE, "%s: warn: Annobin plugin was built by %s %u.%u.%u but run on %s version %u.%u.%u",
		     data->filename, t->tool_name, per_file.anno_major, per_file.anno_minor, per_file.anno_rel,
		     t->tool_name, per_file.run_major, per_file.run_minor, per_file.run_rel);
	      einfo (VERBOSE, "%s: warn: If there are FAIL results that appear to be incorrect, it could be due to this discrepancy.",
		     data->filename);
	    }

	  break;
	}
      if (i >= 0)
	break;

      /* Otherwise look for the normal BUILD_ATTRIBUTE_TOOL string.  */
      const char * gcc = strstr (attr + 1, "gcc");

      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);

	  set_producer (data, TOOL_GCC, version, "GNU Build Attribute");
	}
      else
	report_s (VERBOSE, "%s: (%s) unable to parse tool attribute: %s",
		  data, sec, note_data, prefer_func_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)))
	break;

      /* Convert the pic value into a pass/fail result.  */
      switch (value)
	{
	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 ++;
	  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 ++;
	  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 ++;
	    }
	  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 ++;
	  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)))
	break;

      /* We can get stack protection notes without tool notes.  See BZ 1703788 for an example.  */
      if (value != 2 && value != 3 && ! built_by_compiler ())
	{
	  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));
	  break;
	}

      switch (value)
	{
	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 ++;
	  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 ++;
	  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 ++;
	  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 ++;
	  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 ++;

	  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 ++;
	}
      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
	  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;
	    }
	  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 ++;
	    }
	  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 ++;
	    }
	  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;
	    }
	  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;
	    }
	}
      else
	einfo (VERBOSE2, "Unsupport 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)
	    break;

	  if (value != 4 && value != 8
	      && skip_check (TEST_CF_PROTECTION, get_component_name (data, sec, note_data, prefer_func_name)))
	    break;

	  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 ++;
	      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 ++;
	      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 ++;
	      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 ++;
	      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 ++;
	      break;
	    }
	}
      else
	einfo (VERBOSE2, "Unsupport 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)))
	    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 ++;
	      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 ++;
		}
	      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 ++;
		}
	      
	      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 ++;
	      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 ++;
	      break;
	    }
	}
      else
	einfo (VERBOSE2, "Unsupport annobin note '%s' - ignored", attr);
      break;

    case 'G':
      if (streq (attr, "GOW"))
	{
	  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 ++;
	    }
	  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;

		  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 (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_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);
		    }
		}
	    }
	}
      else if (streq (attr, "GLIBCXX_ASSERTIONS"))
	{
	  if (value != 1
	      && skip_check (TEST_GLIBCXX_ASSERTIONS, get_component_name (data, sec, note_data, prefer_func_name)))
	    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 ++;
		}
	      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);
		}
	      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 ++;
	      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 ++;
	      break;
	    }
	}
      else
	einfo (VERBOSE2, "Unsupported annobin note '%s' - ignored", attr);
      break;

    case 'I':
      if (const_strneq (attr, "INSTRUMENT:"))
	{
	  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;

	      if (BE_VERBOSE)
		{
		  unsigned int 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);
		    }
		  else
		    {
		      einfo (VERBOSE, "%s: info: (%s):  Details: -fsanitize=...: %s",
			     data->filename, get_component_name (data, sec, note_data, prefer_func_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");
		      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");
		      einfo (VERBOSE, "%s: info: (%s):  Details: -fprofile-arcs: %s",
			     data->filename, get_component_name (data, sec, note_data, prefer_func_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);
	    }
	}
      else
	einfo (VERBOSE2, "Unsupported annobin note '%s' - ignored", attr);
      break;

    case 's':
      if (streq (attr, "stack_clash"))
	{
	  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)))
	    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 ++;
	      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 ++;
	      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 ++;
	      break;
	    }
	}
      else if (streq (attr, "stack_realign"))
	{
	  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)))
	    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 ++;
	      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 ++;
	      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 ++;
	      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 ++;
	    }
	  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 ++;
	    }
	  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 ++;
	    }
	  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 ++;
	    }
	  break;
	}
      else
	einfo (VERBOSE2, "Unsupport annobin note '%s' - ignored", attr);
      break;

    case 'o':
      if (streq (attr, "omit_frame_pointer"))
	/* FIXME: Do Something! */
	break;
      /* Fall through.  */

    default:
      einfo (VERBOSE2, "Unsupport annobin note '%s' - ignored", attr);
      break;

    case GNU_BUILD_ATTRIBUTE_RELRO:
    case GNU_BUILD_ATTRIBUTE_ABI:
    case GNU_BUILD_ATTRIBUTE_STACK_SIZE:
      break;
    }

  return true;
}

static void
ffail (annocheck_data * data, const char * message, int level)
{
  if (! report_future_fail)
    level = VERBOSE2;

  einfo (level, "%s: look: %s", data->filename, message);
  einfo (level, "%s: ^^^^:  This test is not yet enabled, but if it was enabled, it would fail...",
	 data->filename);
}

static void
future_fail (annocheck_data * data, const char * message)
{
  ffail (data, message, INFO);
}

static void
vfuture_fail (annocheck_data * data, const char * message)
{
  ffail (data, message, VERBOSE);
}

static bool
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;
}

static bool
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
#define GNU_PROPERTY_AARCH64_FEATURE_1_AND	0xc0000000
#define GNU_PROPERTY_AARCH64_FEATURE_1_BTI	(1U << 0)
#define GNU_PROPERTY_AARCH64_FEATURE_1_PAC	(1U << 1)
#endif
  
  if (type != GNU_PROPERTY_AARCH64_FEATURE_1_AND)
    {
      einfo (VERBOSE2, "%s: Ignoring property note type %lx", data->filename, type);
      return true;
    }

  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",
	     (long)(notedata - (const unsigned char *) sec->data->d_buf), size);
      return false;
    }

  ulong property = get_4byte_value (notedata);

  if ((property & GNU_PROPERTY_AARCH64_FEATURE_1_BTI) == 0)
    {
      vfuture_fail (data, "The BTI property is not enabled");
      einfo (VERBOSE2, "debugging: property bits = %lx", property);
      return false;
    }

  if ((property & GNU_PROPERTY_AARCH64_FEATURE_1_PAC) == 0)
    {
      vfuture_fail (data, "The PAC property is not enabled");
      einfo (VERBOSE2, "debugging: property bits = %lx", property);
      return false;
    }

  einfo (INFO, "%s: PASS: Both the BTI and PAC properties are present in the GNU Property note", data->filename);
  return true;
}

static bool
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.  */
#ifndef GNU_PROPERTY_X86_FEATURE_1_AND
#define GNU_PROPERTY_X86_UINT32_AND_LO		0xc0000002
#define GNU_PROPERTY_X86_FEATURE_1_AND          (GNU_PROPERTY_X86_UINT32_AND_LO + 0)
#define GNU_PROPERTY_X86_FEATURE_1_IBT		(1U << 0)
#define GNU_PROPERTY_X86_FEATURE_1_SHSTK	(1U << 1)
#endif

  if (type != GNU_PROPERTY_X86_FEATURE_1_AND)
    {
      einfo (VERBOSE2, "%s: Ignoring property note type %lx", data->filename, type);
      return true;
    }

  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",
	     (long)(notedata - (const unsigned char *) sec->data->d_buf), size);
      return false;
    }

  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;
    }

  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, "%s: PASS: Both the IBT and SHSTK properties are present in the GNU Property note", data->filename);
  return true;
}

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))
    return true;

  bool (* handler) (annocheck_data *, annocheck_section *, ulong, ulong, const unsigned char *);
  switch (per_file.e_machine)
    {
    case EM_X86_64:
    case EM_386:
      handler = handle_x86_property_note;
      break;

    case EM_AARCH64:
      handler = handle_aarch64_property_note;
      break;

    case EM_PPC64:
      handler = handle_ppc64_property_note;
      break;

    default:
      /* FIXME: ICE here ?  */
      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;
    }

  if (per_file.e_type == ET_EXEC || per_file.e_type == ET_DYN)
    {
      /* More than one note in an executable is an error.  */
      if (tests[TEST_PROPERTY_NOTE].num_pass)
	{
	  /* 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);
	  goto fail;
	}
    }

  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,
	     (int) strlen (ELF_NOTE_GNU), (char *) sec->data->d_buf + name_offset);
      goto fail;
    }

  unsigned int 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",
	     expected_quanta, note->n_descsz);
      goto fail;
    }

  unsigned int remaining = note->n_descsz;
  const unsigned char * notedata = sec->data->d_buf + data_offset;
  while (remaining)
    {
      ulong type = get_4byte_value (notedata);
      ulong size = get_4byte_value (notedata + 4);

      remaining -= 8;
      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",
		 (long)(notedata - (const unsigned char *) sec->data->d_buf), size, remaining);
	  goto fail;
	}

      if (! handler (data, sec, type, size, notedata))
	goto fail;

      notedata  += ((size + (expected_quanta - 1)) & ~ (expected_quanta - 1));
      remaining -= ((size + (expected_quanta - 1)) & ~ (expected_quanta - 1));
    }

  tests[TEST_PROPERTY_NOTE].num_pass ++;
  return true;

 fail:
  tests[TEST_PROPERTY_NOTE].num_fail ++;
  return false;
}

static bool
supports_property_notes (int e_machine)
{
  return e_machine == EM_X86_64
    || e_machine == EM_AARCH64
    || e_machine == EM_PPC64
    || e_machine == EM_386;
}

static bool
check_note_section (annocheck_data *    data,
		    annocheck_section * sec)
{
  if (sec->shdr.sh_addralign != 4 && sec->shdr.sh_addralign != 8)
    {
      einfo (WARN, "%s: 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;

      per_file.build_notes_seen = true;
      return annocheck_walk_notes (data, sec, walk_build_notes, (void *) & hard_data);
    }

  if (supports_property_notes (per_file.e_machine)
      && streq (sec->secname, ".note.gnu.property"))
    {
      return annocheck_walk_notes (data, sec, walk_property_notes, NULL);
    }

  if (streq (sec->secname, ".note.go.buildid"))
    {
      set_producer (data, TOOL_GO, 0, ".note.go.buildid");
    }

  return true;
}

static bool
check_string_section (annocheck_data *    data,
		      annocheck_section * sec)
{
  /* Check the string table to see if it contains "__pthread_register_cancel".
     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 ++;

  return true;
}

/* Returns TRUE iff STR contains a search path that does not start with /usr.
   We also allow $ORIGIN as that is allowed for non-suid binaries.  The
   $LIB and $PLATFORM pseudo-variables should always be used with a /usr
   prefix, so we do not need to check for them.  */

static bool
not_rooted_at_usr (const char * str)
{
  while (str)
    {
      if (! const_strneq (str, "/usr") && ! const_strneq (str, "$ORIGIN"))
	return true;
      str = strchr (str, ':');
      if (str)
	str++;
    }
  return false;
}

static bool
check_dynamic_section (annocheck_data *    data,
		       annocheck_section * sec)
{
  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 ++;
    }

  size_t num_entries = sec->shdr.sh_size == 0 / sec->shdr.sh_entsize;

  /* Walk the dynamic tags.  */
  while (num_entries --)
    {
      GElf_Dyn   dynmem;
      GElf_Dyn * dyn = gelf_getdyn (sec->data, num_entries, & dynmem);

      if (dyn == NULL)
	break;

      switch (dyn->d_tag)
	{
	case DT_BIND_NOW:
	  tests[TEST_BIND_NOW].num_pass ++;
	  break;

	case DT_FLAGS:
	  if (dyn->d_un.d_val & DF_BIND_NOW)
	    tests[TEST_BIND_NOW].num_pass ++;
	  break;

	case DT_RELSZ:
	case DT_RELASZ:
	  if (dyn->d_un.d_val > 0)
	    tests[TEST_BIND_NOW].num_maybe ++;
	  break;

	case DT_TEXTREL:
	  tests[TEST_TEXTREL].num_fail ++;
	  break;

	case DT_RPATH:
	case DT_RUNPATH:
	  {
	    if (skip_check (TEST_RUN_PATH, NULL))
	      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 ++;
	      }
	  }
	  break;

	case DT_AARCH64_BTI_PLT:
	  if (per_file.e_machine == EM_AARCH64)
	    tests[TEST_DYNAMIC_TAGS].num_pass |= 1;
	  break;

	case DT_AARCH64_PAC_PLT:
	  if (per_file.e_machine == EM_AARCH64)
	    tests[TEST_DYNAMIC_TAGS].num_pass |= 2;
	  break;

	default:
	  break;
	}
    }

  return true;
}

static bool
check_code_section (annocheck_data *     data,
		    annocheck_section *  sec)
{
  /* At the moment we are only interested in the .comment section.  */
  if (sec->data->d_size <= 11 || ! streq (sec->secname, ".comment"))
    return true;

  const char * tool = (const char *) sec->data->d_buf;
  const char * tool_end = tool + sec->data->d_size;

  if (tool[0] == 0)
    tool ++; /* Not sure why this can happen, but it does.  */

  /* Note - it is possible to have multiple builder IDs in the .comment section.
     eg:  GCC: (GNU) 8.3.1 20191121 (Red Hat 8.3.1-5)\0GCC: (GNU) 9.2.1 20191120 (Red Hat 9.2.1-2).
     so we keep scanning until we do not find any more.  */
  while (tool < tool_end)
    {
      static const char * gcc_prefix = "GCC: (GNU) ";
      static const char * clang_prefix = "clang version ";
      static const char * lld_prefix = "Linker: LLD ";
      unsigned int 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");
	  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");
	  einfo (VERBOSE2, "%s: built by clang version %u (extracted from '%s' in comment section)",
		 data->filename, version, where);
	}
      else if (strstr (tool, lld_prefix) != NULL)
	{
	  einfo (VERBOSE2, "ignoring linker version string found in .comment section");
	}
      else if (*tool)
	{
	  einfo (VERBOSE2, "unrecognised component in .comment section: %s", tool);
	}

      tool += strlen (tool) + 1;
    }

  return true;
}

static bool
check_sec (annocheck_data *     data,
	   annocheck_section *  sec)
{
  if (disabled)
    return false;

  /* Note - the types checked here should correspond to the types
     selected in interesting_sec().  */
  switch (sec->shdr.sh_type)
    {
    case SHT_NOTE:     return check_note_section (data, sec);
    case SHT_STRTAB:   return check_string_section (data, sec);
    case SHT_DYNAMIC:  return check_dynamic_section (data, sec);
    case SHT_PROGBITS: return check_code_section (data, sec);
    default:           return true;
    }
}

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 (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
	{
	  einfo (VERBOSE, "%s: FAIL: seg %d has Read, Write and eXecute flags\n",
		 data->filename, seg->number);
	  tests[TEST_RWX_SEG].num_fail ++;
	}
    }

  switch (seg->phdr->p_type)
    {
    case PT_INTERP:
      tests[TEST_ENTRY].num_maybe ++;
      break;

    case PT_GNU_RELRO:
      tests[TEST_GNU_RELRO].num_pass ++;
      break;

    case PT_GNU_STACK:
      if ((seg->phdr->p_flags & (PF_W | PF_R)) != (PF_W | PF_R))
	{
	  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 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 ++;
	}
      break;

    case PT_NOTE:
      if (skip_check (TEST_PROPERTY_NOTE, NULL))
	break;
      /* We 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)
	  && 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))
	return true;
      break;

    default:
      break;
    }

  return false;
}

static bool
check_seg (annocheck_data *    data,
	   annocheck_segment * seg)
{
  if (disabled)
    return false;

  if (seg->phdr->p_type == PT_LOAD)
    {
      Elf64_Addr entry_point = per_file.e_entry - seg->phdr->p_vaddr;

      /* We are checking the entry point instruction.  We should
	 only have reached this point if the requirements for the
	 check have already been met, so we do not need to test
	 them again.  */
      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)
	{
	  /* 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 ++;
	  else
	    tests[TEST_ENTRY].num_fail ++;
	}
      else /* per_file.e_machine == EM_X86_64 */
	{
	  /* Look for ENDBR64: 0xf3 0x0f 0x1e 0xfa.  */
	  if (   entry_bytes[0] == 0xf3
	      && entry_bytes[1] == 0x0f
	      && entry_bytes[2] == 0x1e
	      && entry_bytes[3] == 0xfa)
	    tests[TEST_ENTRY].num_pass ++;
	  else
	    tests[TEST_ENTRY].num_fail ++;
	}

      return true;
    }

  if (per_file.e_machine != EM_X86_64)
    return true;

  /* FIXME: Only run these checks if the note section is missing ??  */

  GElf_Nhdr  note;
  size_t     name_off;
  size_t     data_off;
  size_t     offset = 0;

  offset = gelf_getnote (seg->data, offset, & note, & name_off, & data_off);

  if (seg->phdr->p_align != 8)
    {
      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 ++;
	}

      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 ++;
	}
    }

  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 ++;
	}
      else
	tests[TEST_PROPERTY_NOTE].num_pass ++;
    }

  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)
{
  Elf_Scn * addr1_scn = NULL;
  Elf_Scn * addr2_scn = NULL;
  Elf_Scn * scn = NULL;
  ulong     scn_end = 0;
  ulong     scn_name = 0;

  /* These tests should be redundant, but just in case...  */
  if (ignore_gaps)
    return true;
  if (gap->start == gap->end)
    return true;
  if (gap->start > gap->end)
    {
      einfo (VERBOSE2, "gap ignored - start after end!");
      return true;
    }

  /* Gaps narrower than the alignment of the .text section are assumed
     to be padding between functions, and so can be ignored.  In theory
     there could be executable code in such gaps, and so we should also
     check that they are filled with NOP instructions.  But that is
     overkill at the moment.  Plus at the moment the default x86_64
     linker map does not appear to fill gaps with NOPs... */
  if ((gap->end - gap->start) < per_file.text_section_alignment)
    {
      einfo (VERBOSE2, "gap ignored - smaller than text section alignment");
      return true;
    }

  /* FIXME: The linker can create fill regions in the map that are larger
     than the text section alignment.  Not sure why, but it does happen.
     (cf lconvert in the qt5-qttools package which has a gap of 0x28 bytes
     between the end of .obj/main.o and the start of .obj/numerus.o).

     At the moment we have no way of determinining if a gap is because
     of linker filling or missing notes.  (Other than examining a linker
     map).  So we use a heuristic to allow for linker fill regions.
     0x2f is the largest such gap that I have seen so far...  */
  if ((gap->end - gap->start) <= 0x2f)
    {
      einfo (VERBOSE2, "gap ignored - probably linker padding");
      return true;
    }

  /* Find out where the gap starts and ends.  */
  if (data->is_32bit)
    {
      while ((scn = elf_nextscn (data->elf, scn)) != NULL)
	{
	  Elf32_Shdr * shdr = elf32_getshdr (scn);

	  if (addr1_scn == NULL
	      && shdr->sh_addr <= gap->start && ((shdr->sh_addr + shdr->sh_size) >= gap->start))
	    addr1_scn = scn;

	  if (addr2_scn == NULL)
	    {
	      scn_end = shdr->sh_addr + shdr->sh_size;
	      scn_name = shdr->sh_name;

	      if (shdr->sh_addr <= gap->end && scn_end >= gap->end)
		addr2_scn = scn;
	    }
	}
    }
  else
    {
      while ((scn = elf_nextscn (data->elf, scn)) != NULL)
	{
	  Elf64_Shdr * shdr = elf64_getshdr (scn);

	  if (addr1_scn == NULL
	      && shdr->sh_addr <= gap->start && ((shdr->sh_addr + shdr->sh_size) >= gap->start))
	    addr1_scn = scn;

	  if (addr2_scn == NULL)
	    {
	      scn_end = shdr->sh_addr + shdr->sh_size;
	      scn_name = shdr->sh_name;

	      if (shdr->sh_addr <= gap->end && scn_end >= gap->end)
		addr2_scn = scn;
	    }
	}
    }

  /* If the gap is not inside one or more sections, then something funny has gone on...  */
  if (addr2_scn == NULL)
    return false;

  /* If the gap starts in one section, but ends in a different section then we ignore it.  */
  if (addr1_scn != addr2_scn)
    {
      einfo (VERBOSE2, "gap ignored - crosses section boundary");
      return true;
    }

  /* On the PowerPC64, the linker can insert PLT resolver stubs at the end of the .text section.
     These will be unannotated, but they can safely be ignored.

     We may not have the symbol table available however so check to see if the gap ends at the
     end of the .text section.  */
  if (per_file.e_machine == EM_PPC64
      && align (gap->end, 8) == align (scn_end, 8)
      && scn_name == per_file.text_section_name_index)
    {
      const char * sym = annocheck_find_symbol_for_address_range (data, NULL, gap->start + 8, gap->end - 8, false);

      if (sym)
	{
	  if (strstr (sym, "glink_PLTresolve") || strstr (sym, "@plt"))
	    {
	      einfo (VERBOSE2, "Ignoring gap %lx..%lx at end of ppc64 .text section - it contains PLT stubs",
		     gap->start, gap->end);
	      return true;
	    }
	  else
	    einfo (VERBOSE2, "Potential PLT stub gap contains the symbol '%s', so the gap is not ignored", sym);
	}
      else
	{
	  /* Without symbol information we cannot be sure, but it is a reasonable supposition.  */
	  einfo (VERBOSE2, "Ignoring gap %lx..%lx at end of ppc64 .text section - it will contain PLT stubs",
		 gap->start, gap->end);
	  return true;
	}
    }

  return false;
}

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;

  if (n1->end < n2->start)
    return -1;

  if (n1->start > n2->end)
    return 1;

  /* Overlap - we should merge the two ranges.  */
  if (n1->start < n2->start)
    return -1;

  if (n1->end > n2->end)
    return 1;

  /* N1 is wholly covered by N2:
       n2->start <= n1->start <  n2->end
       n2->start <= n1->end   <= n2->end.
     We adjust its range so that the gap detection code does not get confused.  */
  n1->start = n2->start;
  n1->end   = n2->end;
  assert (n1->start < n1->end);
  return 0;
}

/* Certain symbols can indicate that a gap can be safely ignored.  */

static bool
skip_gap_sym (const char * sym)
{
  /* 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;

  /* 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;

  if (per_file.e_machine == EM_386)
    {
      if (const_strneq (sym, "__x86.get_pc_thunk")
	  || const_strneq (sym, "_x86_indirect_thunk_"))
	return true;
    }
  else if (per_file.e_machine == EM_PPC64)
    {
      if (const_strneq (sym, "_savegpr")
	  || const_strneq (sym, "_restgpr")
	  || const_strneq (sym, "_savefpr")
	  || const_strneq (sym, "_restfpr")
	  || const_strneq (sym, "_savevr")
	  || const_strneq (sym, "_restvr"))
	return true;

      /* The linker can also generate long call stubs.  They have the form:
         NNNNNNNN.<stub_name>.<func_name>.  */
      const size_t len = strlen (sym);
      if (   (len > 8 + 10 && const_strneq (sym + 8, ".plt_call."))
	  || (len > 8 + 12 && const_strneq (sym + 8, ".plt_branch."))
	  || (len > 8 + 13 && const_strneq (sym + 8, ".long_branch.")))
	return true;

      /* The gdb server program contains special assembler stubs that
	 are unannotated.  See BZ 1630564 for more details.  */
      if (const_strneq (sym, "start_bcax_"))
	return true;
    }

  return false;
}

static void
check_for_gaps (annocheck_data * data)
{
  assert (! ignore_gaps);

  if (next_free_range < 2)
    return;

  /* Sort the ranges array.  */
  qsort (ranges, next_free_range, sizeof ranges[0], compare_range);

  hardened_note_data current = ranges[0];

  /* Scan the ranges array.  */
  bool gap_found = false;
  unsigned i;
  for (i = 1; i < next_free_range; i++)
    {
      if (ranges[i].start <= current.end)
	{
	  if (ranges[i].start < current.start)
	    current.start = ranges[i].start;

	  if (ranges[i].end > current.end)
	    /* ranges[i] overlaps current.  */
	    current.end = ranges[i].end;
	}
      else if (ranges[i].start <= align (current.end, 16))
	{
	  /* Append ranges[i].  */
	  assert (ranges[i].end >= current.end);
	  current.end = ranges[i].end;
	}
      else
	{
	  hardened_note_data gap;

	  gap.start = current.end;
	  gap.end   = ranges[i].start;

	  /* We have found a gap, so reset the current range.  */
	  current = ranges[i];

	  if (ignore_gap (data, & gap))
	    continue;

	  const char * sym = annocheck_find_symbol_for_address_range (data, NULL, gap.start, gap.end, false);
	  if (sym && skip_gap_sym (sym))
	    {
	      einfo (VERBOSE2, "gap ignored - special symbol: %s", sym);

	      /* FIXME: Really we should advance the gap start to the end of the address
		 range covered by the symbol and then check for gaps again.  But this will
		 probably causes us more problems than we want to handle right now.  */
	      continue;
	    }

	  /* If the start of the range was not aligned to a function boundary
	     then try again, this time with an aligned start symbol.
	     FIXME: 16 is suitable for x86_64, but not necessarily other architectures.  */
	  if (gap.start != align (gap.start, 16))
	    {
	      const char * sym2;

	      sym2 = annocheck_find_symbol_for_address_range (data, NULL, align (gap.start, 16), gap.end, false);
	      if (sym2 != NULL
		  && (sym == NULL || ! streq (sym, sym2))
		  && strstr (sym2, ".end") == NULL)
		{
		  if (skip_gap_sym (sym2))
		    {
		      einfo (VERBOSE2, "gap ignored - special symbol: %s", sym2);
		      /* See comment above.  */
		      continue;
		    }

		  gap.start = align (gap.start, 16);
		  sym = sym2;
		}
	    }

	  /* Finally, give it one more go, looking for a symbol half way through the gap.  */
	  if (gap.end - gap.start > 32)
	    {
	      const char * sym2;
	      ulong start = align (gap.start + (gap.end - gap.start) / 2, 32);

	      sym2 = annocheck_find_symbol_for_address_range (data, NULL, start, start + 32, false);

	      if (sym2 != NULL
		  && (sym == NULL || ! streq (sym, sym2))
		  && strstr (sym2, ".end") == NULL)
		{
		  if (skip_gap_sym (sym2))
		    {
		      einfo (VERBOSE2, "gap ignored - special symbol: %s", sym2);
		      /* See comment above.  */
		      continue;
		    }
		}
	    }

	  gap_found = true;
	  if (! BE_VERBOSE)
	    break;

	  if (sym)
	    {
	      const char * cpsym = NULL;

	      if (sym[0] == '_' && sym[1] == 'Z')
		{
		  cpsym = cplus_demangle (sym, DMGL_PARAMS | DMGL_ANSI | DMGL_VERBOSE);
		  if (cpsym != NULL)
		    sym = cpsym;
		}

	      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",
		   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)");
  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+)");

  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) branch protection");
	  else
	    fail (data, "Parts of the binary were compiled without branch protection.  Run with -v to see where");
	}
      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)
    {
      pass (data, "Compiled with sufficient -mbranch-protection");
    }
  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");  */
      look (data, "The -mbranch-protection setting was not recorded");
    }
}

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)
{
  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");
}

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 (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");
    }
  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)");

  else if (results->num_fail > 0)
    {
      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)");
      else
	fail (data, "Bad GNU Property note(s).  Run with -v to see what is wrong");
    }

  else if (results->num_maybe > 0)
    maybe (data, "Corrupt GNU Property note");

  else if (results->num_pass > 0)
    pass (data, "Good GNU Property note");

  else
    {
      switch (per_file.e_machine)
	{
	case EM_X86_64:
	case EM_386:
	  if (tests[TEST_CF_PROTECTION].enabled && tests[TEST_CF_PROTECTION].num_pass > 0)
	    {
	      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");
	      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;

	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");
	      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;

	case EM_PPC64:
	  look (data, "Missing GNU Property note (specific to PowerPC)");
	  break;

	default:
	  fail (data, "ICE: property notes for this architecture not handled");
	  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");
}

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");
}

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");
}

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");
}

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");
}

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");
}

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");
}

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)");

  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");
	}
      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 (tests[i].enabled)
	{
	  tests[i].show_result (data, tests + i);
	  einfo (VERBOSE2, " Use --skip-%s to disable this test", tests[i].name);
	}
      else
	einfo (VERBOSE, "%s: skip: %s", data->filename, tests[i].description);
    }

  if (per_file.num_fails > 0)
    return false;

  if (per_file.num_maybes > 0)
    return false; /* FIXME: Add an option to ignore MAYBE results ? */

  return einfo (INFO, "%s: PASS", data->filename);
}

static void
version (void)
{
  einfo (INFO, "Version 1.3");
}

static void
usage (void)
{
  einfo (INFO, "Hardening/Security checker.  By default all relevant tests are run.");
  einfo (INFO, "  To disable an individual test use the following options:");

  int i;
  for (i = 0; i < TEST_MAX; i++)
    einfo (INFO, "    --skip-%-19sDisables: %s", tests[i].name, tests[i].description);

  einfo (INFO, "    --skip-%-19sDisables all tests", "all");
  einfo (INFO, "  To enable a disabled test use --test-<name>");
  
  einfo (INFO, "  The tool will also report missing annobin data unless:");
  einfo (INFO, "    --ignore-gaps             Ignore missing annobin data");

  einfo (INFO, "  The tool is enabled by default.  This can be changed by:");
  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");
}

static bool
process_arg (const char * arg, const char ** argv, const uint argc, uint * next)
{
  if (const_strneq (arg, "--skip-"))
    {
      arg += strlen ("--skip-");

      int i;

      if (streq (arg, "all"))
	{
	  for (i = 0; i < TEST_MAX; i++)
	    tests[i].enabled = false;
	  return true;
	}
      
      if (streq (arg, "future"))
	{
	  report_future_fail = false;
	  return true;
	}
      
      for (i = 0; i < TEST_MAX; i++)
	{
	  if (streq (arg, tests[i].name))
	    {
	      tests[i].enabled = false;
	      return true;
	    }
	}

      return false;
    }

  if (const_strneq (arg, "--test-"))
    {
      arg += strlen ("--test-");

      int i;

      if (streq (arg, "all"))
	{
	  for (i = 0; i < TEST_MAX; i++)
	    tests[i].enabled = true;
	  return true;
	}
      
      if (streq (arg, "future"))
	{
	  report_future_fail = true;
	  return true;
	}
      
      for (i = 0; i < TEST_MAX; i++)
	{
	  if (streq (arg, tests[i].name))
	    {
	      tests[i].enabled = true;
	      return true;
	    }
	}

      return false;
    }
  
  if (streq (arg, "--enable-hardened"))
    {
      disabled = false;
      return true;
    }

  if (streq (arg, "--disable-hardened"))
    {
      disabled = true;
      return true;
    }

  if (streq (arg, "--ignore-gaps"))
    {
      ignore_gaps = true;
      return true;
    }

  return false;
}


struct checker hardened_checker =
{
  "Hardened",
  start,
  interesting_sec,
  check_sec,
  interesting_seg,
  check_seg,
  finish,
  process_arg,
  usage,
  version,
  NULL, /* start_scan */
  NULL, /* end_scan */
  NULL, /* internal */
};

static __attribute__((constructor)) void
register_checker (void)
{
  if (! annocheck_add_checker (& hardened_checker, ANNOBIN_VERSION / 100))
    disabled = true;
}