]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
aarch64: GCS feature check in GNU note properties for input objects
authorSrinath Parvathaneni <srinath.parvathaneni@arm.com>
Mon, 28 Oct 2024 18:13:08 +0000 (18:13 +0000)
committerMatthieu Longo <matthieu.longo@arm.com>
Mon, 2 Dec 2024 15:18:41 +0000 (15:18 +0000)
This patch adds support for Guarded Control Stack in AArch64 linker.

This patch implements the following:
1) Defines GNU_PROPERTY_AARCH64_FEATURE_1_GCS bit for GCS in
GNU_PROPERTY_AARCH64_FEATURE_1_AND macro.

2) Adds readelf support to read and print the GCS feature in GNU
properties in AArch64.

Displaying notes found in: .note.gnu.property
[      ]+Owner[        ]+Data size[    ]+Description
  GNU                  0x00000010      NT_GNU_PROPERTY_TYPE_0
      Properties: AArch64 feature: GCS

3) Adds support for the "-z gcs" linker option and document all the values
allowed with this option (-z gcs[=always|never|implicit]) where "-z gcs" is
equivalent to "-z gcs=always". When '-z gcs' option is omitted from the
command line, it defaults to "implicit" and relies on the GCS feature
marking in GNU properties.

4) Adds support for the "-z gcs-report" linker option and document all the
values allowed with this option (-z gcs-report[=none|warning|error]) where
"-z gcs-report" is equivalent to "-z gcs-report=warning". When this option
is omitted from the command line, it defaults to "warning".

The ABI changes adding GNU_PROPERTY_AARCH64_FEATURE_1_GCS to the GNU
property GNU_PROPERTY_AARCH64_FEATURE_1_AND is merged into main and
can be found in [1].

[1] https://github.com/ARM-software/abi-aa/blob/main/sysvabi64/sysvabi64.rst

Co-authored-by: Matthieu Longo <matthieu.longo@arm.com>
Co-authored-by: Yury Khrustalev <yury.khrustalev@arm.com>
bfd/elfnn-aarch64.c
bfd/elfxx-aarch64.c
bfd/elfxx-aarch64.h
binutils/readelf.c
include/elf/common.h
ld/emultempl/aarch64elf.em
ld/ld.texi

index 3a364e946b8b205b210dba025122662612dd1266..701191470dc05e783dded06cbaff0db116ec91d3 100644 (file)
@@ -5020,8 +5020,24 @@ bfd_elfNN_aarch64_set_options (struct bfd *output_bfd,
     elf_aarch64_tdata (output_bfd)->gnu_property_aarch64_feature_1_and
       |= GNU_PROPERTY_AARCH64_FEATURE_1_BTI;
 
+  switch (sw_protections->gcs_type)
+    {
+    case GCS_ALWAYS:
+      elf_aarch64_tdata (output_bfd)->gnu_property_aarch64_feature_1_and
+       |= GNU_PROPERTY_AARCH64_FEATURE_1_GCS;
+      break;
+    case GCS_NEVER:
+      elf_aarch64_tdata (output_bfd)->gnu_property_aarch64_feature_1_and
+       &= ~GNU_PROPERTY_AARCH64_FEATURE_1_GCS;
+      break;
+    case GCS_IMPLICIT:
+      /* GCS feature on the output bfd will be deduced from input objects.  */
+      break;
+    }
+
   elf_aarch64_tdata (output_bfd)->sw_protections = *sw_protections;
   elf_aarch64_tdata (output_bfd)->n_bti_issues = 0;
+  elf_aarch64_tdata (output_bfd)->n_gcs_issues = 0;
 
   setup_plt_values (link_info, sw_protections->plt_type);
 }
@@ -10630,6 +10646,7 @@ elfNN_aarch64_merge_gnu_properties (struct bfd_link_info *info,
       const aarch64_protection_opts *sw_protections
        = &elf_aarch64_tdata (info->output_bfd)->sw_protections;
       aarch64_feature_marking_report bti_report = sw_protections->bti_report;
+      aarch64_feature_marking_report gcs_report = sw_protections->gcs_report;
 
       /* If output has been marked with BTI using command line argument, give
         out warning if necessary.  */
@@ -10641,6 +10658,18 @@ elfNN_aarch64_merge_gnu_properties (struct bfd_link_info *info,
          if (!bprop || !(bprop->u.number & GNU_PROPERTY_AARCH64_FEATURE_1_BTI))
            _bfd_aarch64_elf_check_bti_report (info, bbfd);
        }
+
+      /* If the output has been marked with GCS using '-z gcs' and the input is
+        missing GCS feature tag, throw a warning/error in accordance with
+        -z gcs-report=warning/error.  */
+      if ((outprop & GNU_PROPERTY_AARCH64_FEATURE_1_GCS)
+         && gcs_report != MARKING_NONE)
+       {
+         if (!aprop || !(aprop->u.number & GNU_PROPERTY_AARCH64_FEATURE_1_GCS))
+           _bfd_aarch64_elf_check_gcs_report (info, abfd);
+         if (!bprop || !(bprop->u.number & GNU_PROPERTY_AARCH64_FEATURE_1_GCS))
+           _bfd_aarch64_elf_check_gcs_report (info, bbfd);
+       }
     }
 
   return  _bfd_aarch64_elf_merge_gnu_properties (info, abfd, aprop,
index 41ff2cae940954d0fe140cf960eee0d87b3e69dd..e4d7fa5c7a5dec1268975d0acff1fead841f3e01 100644 (file)
@@ -765,6 +765,7 @@ _bfd_aarch64_report_summary_merge_issues (struct bfd_link_info *info)
   const struct elf_aarch64_obj_tdata * tdata
     = elf_aarch64_tdata (info->output_bfd);
   aarch64_feature_marking_report bti_report = tdata->sw_protections.bti_report;
+  aarch64_feature_marking_report gcs_report = tdata->sw_protections.gcs_report;
 
   if (tdata->n_bti_issues > GNU_PROPERTY_ISSUES_MAX
       && bti_report != MARKING_NONE)
@@ -777,6 +778,18 @@ _bfd_aarch64_report_summary_merge_issues (struct bfd_link_info *info)
            "BTI requirements.\n");
       info->callbacks->einfo (msg, tdata->n_bti_issues);
     }
+
+  if (tdata->n_gcs_issues > GNU_PROPERTY_ISSUES_MAX
+      && gcs_report != MARKING_NONE)
+    {
+      const char *msg
+       = (tdata->sw_protections.gcs_report == MARKING_ERROR)
+       ? _("%Xerror: found a total of %d inputs incompatible with "
+           "GCS requirements.\n")
+       : _("warning: found a total of %d inputs incompatible with "
+           "GCS requirements.\n");
+      info->callbacks->einfo (msg, tdata->n_gcs_issues);
+    }
 }
 
 /* Find the first input bfd with GNU property and merge it with GPROP.  If no
@@ -796,7 +809,7 @@ _bfd_aarch64_elf_link_setup_gnu_properties (struct bfd_link_info *info)
   /* If ebfd != NULL it is either an input with property note or the last input.
      Either way if we have an output GNU property that was provided, we should
      add it (by creating a section if needed).  */
-  if (ebfd != NULL && outprop)
+  if (ebfd != NULL)
     {
       /* If no GNU property node was found, create the GNU property note
         section.  */
@@ -817,8 +830,17 @@ _bfd_aarch64_elf_link_setup_gnu_properties (struct bfd_link_info *info)
           && !(prop->u.number & GNU_PROPERTY_AARCH64_FEATURE_1_BTI))
        _bfd_aarch64_elf_check_bti_report (info, ebfd);
 
+      if (tdata->sw_protections.gcs_type == GCS_NEVER)
+       prop->u.number &= ~GNU_PROPERTY_AARCH64_FEATURE_1_GCS;
+      else if ((outprop & GNU_PROPERTY_AARCH64_FEATURE_1_GCS)
+              && !(prop->u.number & GNU_PROPERTY_AARCH64_FEATURE_1_GCS))
+       _bfd_aarch64_elf_check_gcs_report (info, ebfd);
+
       prop->u.number |= outprop;
-      prop->pr_kind = property_number;
+      if (prop->u.number == 0)
+       prop->pr_kind = property_remove;
+      else
+       prop->pr_kind = property_number;
     }
 
   /* Set up generic GNU properties, and merge them with the backend-specific
@@ -843,7 +865,8 @@ _bfd_aarch64_elf_link_setup_gnu_properties (struct bfd_link_info *info)
            {
              outprop = (p->property.u.number
                         & (GNU_PROPERTY_AARCH64_FEATURE_1_BTI
-                         | GNU_PROPERTY_AARCH64_FEATURE_1_PAC));
+                         | GNU_PROPERTY_AARCH64_FEATURE_1_PAC
+                         | GNU_PROPERTY_AARCH64_FEATURE_1_GCS));
              break;
            }
        }
@@ -906,6 +929,19 @@ _bfd_aarch64_elf_merge_gnu_properties (struct bfd_link_info *info
     {
     case GNU_PROPERTY_AARCH64_FEATURE_1_AND:
       {
+       aarch64_gcs_type gcs_type
+         = elf_aarch64_tdata (info->output_bfd)->sw_protections.gcs_type;
+       /* OUTPROP does not contain GCS for GCS_NEVER. We only need to make sure
+        that APROP does not contain GCS as well.
+        Notes:
+         - if BPROP contains GCS and APROP is not null, it is zeroed by the
+           AND with APROP.
+         - if BPROP contains GCS and APROP is null, it is overwritten with
+           OUTPROP as the AND with APROP would have been equivalent to zeroing
+           BPROP.  */
+       if (gcs_type == GCS_NEVER && aprop != NULL)
+         aprop->u.number &= ~GNU_PROPERTY_AARCH64_FEATURE_1_GCS;
+
        if (aprop != NULL && bprop != NULL)
          {
            orig_number = aprop->u.number;
@@ -1005,4 +1041,27 @@ _bfd_aarch64_elf_check_bti_report (struct bfd_link_info *info, bfd *ebfd)
        "file lacks the necessary property note.\n");
 
   info->callbacks->einfo (msg, ebfd);
-}
\ No newline at end of file
+}
+
+void
+_bfd_aarch64_elf_check_gcs_report (struct bfd_link_info *info, bfd *ebfd)
+{
+  struct elf_aarch64_obj_tdata *tdata = elf_aarch64_tdata (info->output_bfd);
+
+  if (tdata->sw_protections.gcs_report == MARKING_NONE)
+    return;
+
+  ++tdata->n_gcs_issues;
+
+  if (tdata->n_gcs_issues > GNU_PROPERTY_ISSUES_MAX)
+    return;
+
+  const char *msg
+    = (tdata->sw_protections.gcs_report == MARKING_WARN)
+    ? _("%pB: warning: GCS is required by -z gcs, but this input object file "
+       "lacks the necessary property note.\n")
+    : _("%X%pB: error: GCS is required by -z gcs, but this input object file "
+       "lacks the necessary property note.\n");
+
+  info->callbacks->einfo (msg, ebfd);
+}
index 48a2847bc5f7d76e44ef4a7485f5adee88696094..4f8b2be6c04df1b5e174e2d3161a743de130661b 100644 (file)
@@ -53,6 +53,16 @@ typedef enum
                         markings.  */
 } aarch64_feature_marking_report;
 
+/* To indicate whether GNU_PROPERTY_AARCH64_FEATURE_1_GCS bit is
+   enabled/disabled on the output when -z gcs linker
+   command line option is passed.  */
+typedef enum
+{
+  GCS_NEVER    = 0,  /* gcs is disabled on output.  */
+  GCS_IMPLICIT  = 1,  /* gcs is deduced from input object.  */
+  GCS_ALWAYS   = 2,  /* gsc is enabled on output.  */
+} aarch64_gcs_type;
+
 /* A structure to encompass all information about software protections coming
    from BTI or PAC related command line options.  */
 struct aarch64_protection_opts
@@ -62,6 +72,12 @@ struct aarch64_protection_opts
 
   /* Report level for BTI issues.  */
   aarch64_feature_marking_report bti_report;
+
+  /* Look-up mode for GCS property.  */
+  aarch64_gcs_type gcs_type;
+
+  /* Report level for GCS issues.  */
+  aarch64_feature_marking_report gcs_report;
 };
 typedef struct aarch64_protection_opts aarch64_protection_opts;
 
@@ -87,6 +103,9 @@ struct elf_aarch64_obj_tdata
 
   /* Number of reported BTI issues.  */
   int n_bti_issues;
+
+  /* Number of reported GCS issues.  */
+  int n_gcs_issues;
 };
 
 #define elf_aarch64_tdata(bfd)                         \
@@ -196,6 +215,9 @@ _bfd_aarch64_elf_merge_gnu_properties (struct bfd_link_info *, bfd *,
 extern void
 _bfd_aarch64_elf_check_bti_report (struct bfd_link_info *, bfd *);
 
+extern void
+_bfd_aarch64_elf_check_gcs_report (struct bfd_link_info *, bfd *);
+
 extern void
 _bfd_aarch64_elf_link_fixup_gnu_properties (struct bfd_link_info *,
                                            elf_property_list **);
index 73163e0ee21b0fb8c2cd82789ff349465c535dae..64e461daf860515f293b821d50917aa0207544e1 100644 (file)
@@ -21239,6 +21239,10 @@ decode_aarch64_feature_1_and (unsigned int bitmask)
          printf ("PAC");
          break;
 
+       case GNU_PROPERTY_AARCH64_FEATURE_1_GCS:
+         printf ("GCS");
+         break;
+
        default:
          printf (_("<unknown: %x>"), bit);
          break;
index c9920e7731a7a4c23cb620310053da90a8619b66..c4eb33bf3082aaa90e02daea0266f3f54bbf6526 100644 (file)
 
 #define GNU_PROPERTY_AARCH64_FEATURE_1_BTI     (1U << 0)
 #define GNU_PROPERTY_AARCH64_FEATURE_1_PAC     (1U << 1)
+#define GNU_PROPERTY_AARCH64_FEATURE_1_GCS     (1U << 2)
 
 /* Values used in GNU .note.ABI-tag notes (NT_GNU_ABI_TAG).  */
 #define GNU_ABI_TAG_LINUX      0
index 726f3567cd74d138105aa9f0ccd50a694467e74a..6dcb296c671dbcd8baae2349ed80c5e68c8198ff 100644 (file)
@@ -37,6 +37,8 @@ static int no_apply_dynamic_relocs = 0;
 static aarch64_protection_opts sw_protections = {
   .plt_type = PLT_NORMAL,
   .bti_report = MARKING_WARN,
+  .gcs_type = GCS_IMPLICIT,
+  .gcs_report = MARKING_WARN,
 };
 
 #define COMPILE_TIME_STRLEN(s) \
@@ -351,29 +353,76 @@ aarch64_elf_create_output_section_statements (void)
   ldlang_add_file (stub_file);
 }
 
+static bool
+aarch64_parse_feature_report_option (const char *optarg,
+                                    const char *report_opt,
+                                    const size_t report_opt_len,
+                                    aarch64_feature_marking_report *level)
+{
+  if (strncmp (optarg, report_opt, report_opt_len) != 0)
+    return false;
+
+  if (strlen (optarg) == report_opt_len
+      || strcmp (optarg + report_opt_len, "=warning") == 0)
+    *level = MARKING_WARN;
+  else if (strcmp (optarg + report_opt_len, "=none") == 0)
+    *level = MARKING_NONE;
+  else if (strcmp (optarg + report_opt_len, "=error") == 0)
+    *level = MARKING_ERROR;
+  else
+    einfo (_("%X%P: error: unrecognized value '-z %s'\n"), optarg);
+
+  return true;
+}
+
 static bool
 aarch64_parse_bti_report_option (const char *optarg)
 {
   #define BTI_REPORT      "bti-report"
   #define BTI_REPORT_LEN  COMPILE_TIME_STRLEN (BTI_REPORT)
 
-  if (strncmp (optarg, BTI_REPORT, BTI_REPORT_LEN) != 0)
+  return aarch64_parse_feature_report_option (optarg, BTI_REPORT,
+    BTI_REPORT_LEN, &sw_protections.bti_report);
+
+  #undef BTI_REPORT
+  #undef BTI_REPORT_LEN
+}
+
+static bool
+aarch64_parse_gcs_report_option (const char *optarg)
+{
+  #define GCS_REPORT      "gcs-report"
+  #define GCS_REPORT_LEN  COMPILE_TIME_STRLEN (GCS_REPORT)
+
+  return aarch64_parse_feature_report_option (optarg, GCS_REPORT,
+    GCS_REPORT_LEN, &sw_protections.gcs_report);
+
+  #undef GCS_REPORT
+  #undef GCS_REPORT_LEN
+}
+
+static bool
+aarch64_parse_gcs_option (const char *optarg)
+{
+  #define GCS      "gcs"
+  #define GCS_LEN  COMPILE_TIME_STRLEN (GCS)
+
+  if (strncmp (optarg, GCS, GCS_LEN) != 0)
     return false;
 
-  if (strlen (optarg) == BTI_REPORT_LEN
-      || strcmp (optarg + BTI_REPORT_LEN, "=warning") == 0)
-    sw_protections.bti_report = MARKING_WARN;
-  else if (strcmp (optarg + BTI_REPORT_LEN, "=none") == 0)
-    sw_protections.bti_report = MARKING_NONE;
-  else if (strcmp (optarg + BTI_REPORT_LEN, "=error") == 0)
-    sw_protections.bti_report = MARKING_ERROR;
+  if (strcmp (optarg + GCS_LEN, "=always") == 0)
+    sw_protections.gcs_type = GCS_ALWAYS;
+  else if (strcmp (optarg + GCS_LEN, "=never") == 0)
+    sw_protections.gcs_type = GCS_NEVER;
+  else if (strcmp (optarg + GCS_LEN, "=implicit") == 0)
+    sw_protections.gcs_type = GCS_IMPLICIT;
   else
     einfo (_("%X%P: error: unrecognized value '-z %s'\n"), optarg);
 
   return true;
 
-  #undef BTI_REPORT
-  #undef BTI_REPORT_LEN
+  #undef GCS
+  #undef GCS_LEN
 }
 EOF
 
@@ -433,6 +482,18 @@ PARSE_AND_LIST_OPTIONS='
                                            and output has BTI marking.\n"));
   fprintf (file, _("\
   -z pac-plt                           Protect PLTs with Pointer Authentication.\n"));
+  fprintf (file, _("\
+  -z gcs=[always|never|implicit]       Controls whether the output supports the Guarded Control Stack (GCS) mechanism.\n\
+                                         implicit (default if '\''-z gcs'\'' is omitted): deduce GCS from input objects.\n\
+                                         always: always marks the output with GCS.\n\
+                                         never: never marks the output with GCS.\n"));
+  fprintf (file, _("\
+  -z gcs-report[=none|warning|error]   Emit warning/error on mismatch of GCS marking between input objects and ouput.\n\
+                                         none: Does not emit any warning/error messages.\n\
+                                         warning (default): Emit warning when the input objects are missing GCS markings\n\
+                                           and output have GCS marking.\n\
+                                         error: Emit error when the input objects are missing GCS markings\n\
+                                           and output have GCS marking.\n"));
 '
 
 PARSE_AND_LIST_ARGS_CASE_Z_AARCH64='
@@ -442,6 +503,10 @@ PARSE_AND_LIST_ARGS_CASE_Z_AARCH64='
        {}
      else if (strcmp (optarg, "pac-plt") == 0)
        sw_protections.plt_type |= PLT_PAC;
+     else if (aarch64_parse_gcs_report_option (optarg))
+       {}
+     else if (aarch64_parse_gcs_option (optarg))
+       {}
 '
 PARSE_AND_LIST_ARGS_CASE_Z="$PARSE_AND_LIST_ARGS_CASE_Z $PARSE_AND_LIST_ARGS_CASE_Z_AARCH64"
 
index ac6bd83b6e9140f049b3e4322eb56deebb0450ed..eb36eaf21d6f7bddb024d885d52477fd684a9fd4 100644 (file)
@@ -8219,6 +8219,36 @@ with the total number of issues will be displayed at the end.
 @cindex Protect PLTs with Returned Pointer Authentication
 The @samp{-z pac-plt} option enables the usage of pointer authentication in PLTs.
 
+@kindex -z gcs=[always|never|implicit]
+@cindex Controls whether the output object supports the Guarded Control Stack (GCS) mechanism.
+The @samp{-z gcs} option controls the verification of Guarded Control Stack (GCS)
+markings on input objects and marks the output with GCS if all conditions are
+validated.
+@itemize
+@item@samp{implicit} (default if @samp{-z gcs} is omitted) enables GCS marking
+on the output if, and only if, all input objects composing the link unit are
+marked with GCS.
+@item@samp{always} forces the marking of the output with GCS.
+@item@samp{never} ignores any GCS marking on the input objects, and does not
+mark the output with GCS.
+@end itemize
+
+@kindex -z gcs-report[=none|warning|error]
+@cindex Control warnings for missing GCS markings.
+The @samp{-z gcs-report[=none|warning|error]} specifies how to report the missing
+GCS markings on inputs, i.e. the GNU_PROPERTY_AARCH64_FEATURE_1_GCS property.
+By default, if the option is omitted and @samp{-z gcs} is provided, warnings are
+emitted.
+@itemize
+@item@samp{none} disables any warning messages.
+@item@samp{warning} (the default value) emits warning messages when input objects
+composing the link unit are missing GCS markings, or dynamic objects containing
+external symbols used in the link unit.
+@item@samp{error} turns the warning messages into errors.
+@end itemize
+If issues are found, a maximum of 20 messages will be emitted, and then a summary
+with the total number of issues will be displayed at the end.
+
 @ifclear GENERIC
 @lowersections
 @end ifclear