[PATCH v1 1/2] aarch64: check GCS feature in GNU properties of input dynamic objects
Matthieu Longo
matthieu.longo@arm.com
Fri Dec 6 15:37:45 GMT 2024
The Guarded Control Stack (GCS) feature requires that two things:
- at static link time, all the input objects of a link unit have to
be compatible with GCS.
- at runtime, the executable and the shared libraries which it
depends on have to be compatible with GCS.
Both of those criteria are checked with the GCS feature stored in
the GNU property note.
The previous patch, adding support for the GCS feature check in GNU
note properties for input objects, ignored the input dynamic objects.
Although this support was better than no check, it was still
delaying the detection of compatibility issues up to the runtime
linker.
In order to help the developer in detecting such an incompatibility
issue as early as possible, this patch adds a check for input dynamic
objects lacking the GCS marking. This check can be controlled via the
linker option '-z gcs-report-dynamic[=none|warning|error]'. By default,
if the option is omitted, it inherits the value from '-z gcs-report'.
However, the inherited value is capped to 'warning' as a user might
want to only report errors in the currently built module, and not the
shared dependencies. If a user also wants to error on GCS issues in
the shared libraries, '-z gcs-report-dynamic=error' will have to be
specified explicitly.
---
bfd/elfnn-aarch64.c | 14 +++++
bfd/elfxx-aarch64.c | 119 +++++++++++++++++++++++++++++++------
bfd/elfxx-aarch64.h | 13 +++-
ld/emultempl/aarch64elf.em | 34 +++++++++--
ld/ld.texi | 30 ++++++++--
5 files changed, 181 insertions(+), 29 deletions(-)
diff --git a/bfd/elfnn-aarch64.c b/bfd/elfnn-aarch64.c
index 701191470dc..d5f43a86cbe 100644
--- a/bfd/elfnn-aarch64.c
+++ b/bfd/elfnn-aarch64.c
@@ -5036,8 +5036,22 @@ bfd_elfNN_aarch64_set_options (struct bfd *output_bfd,
}
elf_aarch64_tdata (output_bfd)->sw_protections = *sw_protections;
+ /* Inherit the value from '-z gcs-report' if the option '-z gcs-report-dynamic'
+ was not set on the command line. However, the inheritance mechanism is
+ capped to avoid inheriting the error level from -g gcs-report as the user
+ might want to continue to build a module without rebuilding all the shared
+ libraries. If a user also wants to error GCS issues in the shared
+ libraries, '-z gcs-report-dynamic=error' will have to be specified
+ explicitly. */
+ if (sw_protections->gcs_report_dynamic == MARKING_UNSET)
+ elf_aarch64_tdata (output_bfd)->sw_protections.gcs_report_dynamic
+ = (sw_protections->gcs_report == MARKING_ERROR)
+ ? MARKING_WARN
+ : sw_protections->gcs_report;
+
elf_aarch64_tdata (output_bfd)->n_bti_issues = 0;
elf_aarch64_tdata (output_bfd)->n_gcs_issues = 0;
+ elf_aarch64_tdata (output_bfd)->n_gcs_dynamic_issues = 0;
setup_plt_values (link_info, sw_protections->plt_type);
}
diff --git a/bfd/elfxx-aarch64.c b/bfd/elfxx-aarch64.c
index e4d7fa5c7a5..ed35a58c1e4 100644
--- a/bfd/elfxx-aarch64.c
+++ b/bfd/elfxx-aarch64.c
@@ -764,11 +764,9 @@ _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)
+ && tdata->sw_protections.bti_report != MARKING_NONE)
{
const char *msg
= (tdata->sw_protections.bti_report == MARKING_ERROR)
@@ -780,7 +778,7 @@ _bfd_aarch64_report_summary_merge_issues (struct bfd_link_info *info)
}
if (tdata->n_gcs_issues > GNU_PROPERTY_ISSUES_MAX
- && gcs_report != MARKING_NONE)
+ && tdata->sw_protections.gcs_report != MARKING_NONE)
{
const char *msg
= (tdata->sw_protections.gcs_report == MARKING_ERROR)
@@ -790,6 +788,69 @@ _bfd_aarch64_report_summary_merge_issues (struct bfd_link_info *info)
"GCS requirements.\n");
info->callbacks->einfo (msg, tdata->n_gcs_issues);
}
+
+ if (tdata->n_gcs_dynamic_issues > GNU_PROPERTY_ISSUES_MAX
+ && tdata->sw_protections.gcs_report_dynamic != MARKING_NONE)
+ {
+ const char *msg
+ = (tdata->sw_protections.gcs_report_dynamic == MARKING_ERROR)
+ ? _("%Xerror: found a total of %d dynamically-linked objects"
+ "incompatible with GCS requirements.\n")
+ : _("warning: found a total of %d dynamically-linked objects"
+ "incompatible with GCS requirements.\n");
+ info->callbacks->einfo (msg, tdata->n_gcs_dynamic_issues);
+ }
+}
+
+/* Perform a look-up of a property in an unsorted list of properties. This is
+ useful when the list of properties of an object has not been sorted yet. */
+static uint32_t
+_bfd_aarch64_prop_linear_lookup (elf_property_list *properties,
+ unsigned int pr_type)
+{
+ for (elf_property_list *p = properties; p != NULL; p = p->next)
+ if (p->property.pr_type == pr_type)
+ return p->property.u.number;
+ return 0;
+}
+
+/* Compare the GNU properties of the current dynamic object, with the ones of
+ the output BFD. Today, we only care about GCS feature stored in
+ GNU_PROPERTY_AARCH64_FEATURE_1. */
+static void
+_bfd_aarch64_compare_dynamic_obj_prop_against_outprop(
+ struct bfd_link_info *info,
+ const uint32_t outprop,
+ bfd *pbfd)
+{
+ const uint32_t dyn_obj_aarch64_feature_prop =
+ _bfd_aarch64_prop_linear_lookup (elf_properties (pbfd),
+ GNU_PROPERTY_AARCH64_FEATURE_1_AND);
+ if ((outprop & GNU_PROPERTY_AARCH64_FEATURE_1_GCS)
+ && !(dyn_obj_aarch64_feature_prop & GNU_PROPERTY_AARCH64_FEATURE_1_GCS))
+ _bfd_aarch64_elf_check_gcs_report (info, pbfd);
+}
+
+/* Check compatibility between the GNU properties of the ouput BFD and the
+ linked dynamic objects. */
+static void
+_bfd_aarch64_elf_check_gnu_properties_linked_dynamic_objects (
+ struct bfd_link_info * info,
+ const uint32_t outprop)
+{
+ const struct elf_backend_data *bed = get_elf_backend_data (info->output_bfd);
+ const int elf_machine_code = bed->elf_machine_code;
+ const unsigned int elfclass = bed->s->elfclass;
+
+ for (bfd *pbfd = info->input_bfds; pbfd != NULL; pbfd = pbfd->link.next)
+ /* Ignore GNU properties from non-ELF objects or ELF objects with different
+ machine code. */
+ if ((pbfd->flags & DYNAMIC) != 0
+ && (bfd_get_flavour (pbfd) == bfd_target_elf_flavour)
+ && (get_elf_backend_data (pbfd)->elf_machine_code == elf_machine_code)
+ && (get_elf_backend_data (pbfd)->s->elfclass == elfclass))
+ _bfd_aarch64_compare_dynamic_obj_prop_against_outprop(info, outprop,
+ pbfd);
}
/* Find the first input bfd with GNU property and merge it with GPROP. If no
@@ -855,7 +916,7 @@ _bfd_aarch64_elf_link_setup_gnu_properties (struct bfd_link_info *info)
/* The property list is sorted in order of type. */
for (elf_property_list *p = elf_properties (pbfd);
(p != NULL)
- && (GNU_PROPERTY_AARCH64_FEATURE_1_AND <= p->property.pr_type);
+ && (GNU_PROPERTY_AARCH64_FEATURE_1_AND <= p->property.pr_type);
p = p->next)
{
/* This merge of features should happen only once as all the identical
@@ -872,9 +933,12 @@ _bfd_aarch64_elf_link_setup_gnu_properties (struct bfd_link_info *info)
}
}
+ tdata->gnu_property_aarch64_feature_1_and = outprop;
+
+ _bfd_aarch64_elf_check_gnu_properties_linked_dynamic_objects (info, outprop);
+
_bfd_aarch64_report_summary_merge_issues (info);
- tdata->gnu_property_aarch64_feature_1_and = outprop;
return pbfd;
}
@@ -1047,21 +1111,42 @@ 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);
+ bool dynamic_obj = (ebfd->flags & DYNAMIC) != 0;
- if (tdata->sw_protections.gcs_report == MARKING_NONE)
+ if ((!dynamic_obj && (tdata->sw_protections.gcs_report == MARKING_NONE))
+ || (tdata->sw_protections.gcs_report_dynamic == MARKING_NONE))
return;
- ++tdata->n_gcs_issues;
-
- if (tdata->n_gcs_issues > GNU_PROPERTY_ISSUES_MAX)
- return;
+ if (dynamic_obj)
+ {
+ ++tdata->n_gcs_dynamic_issues;
+ if (tdata->n_gcs_dynamic_issues > GNU_PROPERTY_ISSUES_MAX)
+ return;
+ }
+ else
+ {
+ ++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");
+ const char *msg;
+ if (dynamic_obj)
+ msg = (tdata->sw_protections.gcs_report_dynamic == MARKING_WARN)
+ ? _("%pB: warning: GCS is required by -z gcs, but this shared library "
+ "lacks the necessary property note. The dynamic loader might not "
+ "enable GCS or refuse to load the program unless all the shared "
+ "library dependencies have the GCS marking.\n")
+ : _("%X%pB: error: GCS is required by -z gcs, but this shared library "
+ "lacks the necessary property note. The dynamic loader might not "
+ "enable GCS or refuse to load the program unless all the shared "
+ "library dependencies have the GCS marking.\n");
+ else
+ 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);
}
diff --git a/bfd/elfxx-aarch64.h b/bfd/elfxx-aarch64.h
index 4f8b2be6c04..b1f19cdbbe5 100644
--- a/bfd/elfxx-aarch64.h
+++ b/bfd/elfxx-aarch64.h
@@ -51,6 +51,9 @@ typedef enum
MARKING_ERROR = 2, /* Emit error when the input objects are missing GNU
feature property markings, and the output has the
markings. */
+ MARKING_UNSET = 3, /* The only purpose of this value is to simulate an
+ optional to detect when the value was not initialized
+ from the command line. */
} aarch64_feature_marking_report;
/* To indicate whether GNU_PROPERTY_AARCH64_FEATURE_1_GCS bit is
@@ -64,7 +67,7 @@ typedef enum
} aarch64_gcs_type;
/* A structure to encompass all information about software protections coming
- from BTI or PAC related command line options. */
+ from BTI, PAC and GCS related command line options. */
struct aarch64_protection_opts
{
/* PLT type to use depending on the selected software proctections. */
@@ -78,6 +81,9 @@ struct aarch64_protection_opts
/* Report level for GCS issues. */
aarch64_feature_marking_report gcs_report;
+
+ /* Report level for GCS issues with dynamic inputs. */
+ aarch64_feature_marking_report gcs_report_dynamic;
};
typedef struct aarch64_protection_opts aarch64_protection_opts;
@@ -104,8 +110,11 @@ struct elf_aarch64_obj_tdata
/* Number of reported BTI issues. */
int n_bti_issues;
- /* Number of reported GCS issues. */
+ /* Number of reported GCS issues for non-dynamic objects. */
int n_gcs_issues;
+
+ /* Number of reported GCS issues for dynamic objects. */
+ int n_gcs_dynamic_issues;
};
#define elf_aarch64_tdata(bfd) \
diff --git a/ld/emultempl/aarch64elf.em b/ld/emultempl/aarch64elf.em
index b4c9016a3e5..7d1672ff9b3 100644
--- a/ld/emultempl/aarch64elf.em
+++ b/ld/emultempl/aarch64elf.em
@@ -39,6 +39,7 @@ static aarch64_protection_opts sw_protections = {
.bti_report = MARKING_WARN,
.gcs_type = GCS_IMPLICIT,
.gcs_report = MARKING_WARN,
+ .gcs_report_dynamic = MARKING_UNSET,
};
#define COMPILE_TIME_STRLEN(s) \
@@ -357,18 +358,20 @@ static bool
aarch64_parse_feature_report_option (const char *_optarg,
const char *report_opt,
const size_t report_opt_len,
+ bool allow_empty_value,
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)
+ if (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 if (allow_empty_value && strlen (_optarg) == report_opt_len)
+ *level = MARKING_WARN;
else
einfo (_("%X%P: error: unrecognized value '-z %s'\n"), _optarg);
@@ -382,7 +385,7 @@ aarch64_parse_bti_report_option (const char *_optarg)
#define BTI_REPORT_LEN COMPILE_TIME_STRLEN (BTI_REPORT)
return aarch64_parse_feature_report_option (_optarg, BTI_REPORT,
- BTI_REPORT_LEN, &sw_protections.bti_report);
+ BTI_REPORT_LEN, true, &sw_protections.bti_report);
#undef BTI_REPORT
#undef BTI_REPORT_LEN
@@ -395,12 +398,25 @@ aarch64_parse_gcs_report_option (const char *_optarg)
#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);
+ GCS_REPORT_LEN, true, &sw_protections.gcs_report);
#undef GCS_REPORT
#undef GCS_REPORT_LEN
}
+static bool
+aarch64_parse_gcs_report_dynamic_option (const char *_optarg)
+{
+ #define GCS_REPORT_DYNAMIC "gcs-report-dynamic"
+ #define GCS_REPORT_DYNAMIC_LEN COMPILE_TIME_STRLEN (GCS_REPORT_DYNAMIC)
+
+ return aarch64_parse_feature_report_option (_optarg, GCS_REPORT_DYNAMIC,
+ GCS_REPORT_DYNAMIC_LEN, false, &sw_protections.gcs_report_dynamic);
+
+ #undef GCS_REPORT_DYNAMIC
+ #undef GCS_REPORT_DYNAMIC_LEN
+}
+
static bool
aarch64_parse_gcs_option (const char *_optarg)
{
@@ -494,6 +510,14 @@ PARSE_AND_LIST_OPTIONS='
and output have GCS marking.\n\
error: Emit error when the input objects are missing GCS markings\n\
and output have GCS marking.\n"));
+ fprintf (file, _("\
+ -z gcs-report-dynamic=none|warning|error Emit warning/error on mismatch of GCS marking between the current link\n\
+ unit and input dynamic objects.\n\
+ none: Does not emit any warning/error messages.\n\
+ warning: 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='
@@ -503,6 +527,8 @@ 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_dynamic_option (optarg))
+ {}
else if (aarch64_parse_gcs_report_option (optarg))
{}
else if (aarch64_parse_gcs_option (optarg))
diff --git a/ld/ld.texi b/ld/ld.texi
index eb36eaf21d6..3e9628dab83 100644
--- a/ld/ld.texi
+++ b/ld/ld.texi
@@ -8235,15 +8235,33 @@ mark the output with GCS.
@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.
+The @samp{-z gcs-report[=none|warning|error]} option 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.
+composing the link unit are missing GCS markings.
+@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.
+
+@kindex -z gcs-report-dynamic=none|warning|error
+@cindex Control warnings for missing GCS markings on dynamic input objects.
+The @samp{-z gcs-report-dynamic=none|warning|error} option specifies how to
+report the missing GCS markings on dynamic input objects, i.e. the
+GNU_PROPERTY_AARCH64_FEATURE_1_GCS property. By default, if the option is
+omitted, it inherits the value of @samp{-z gcs-report}. However, the inherited
+value is capped to @samp{warning} as a user might want to only report errors in
+the currently built module, and not the shared dependencies. If a user also wants
+to error on GCS issues in the shared libraries, @samp{-z gcs-report-dynamic=error}
+will have to be specified explicitly.
+@itemize
+@item@samp{none} disables any warning messages.
+@item@samp{warning} emits warning messages when dynamic objects containing
+external symbols used in the link unit are missing GCS markings.
@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
--
2.47.1
More information about the Binutils
mailing list