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>
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);
}
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. */
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,
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)
"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
/* 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. */
&& !(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
{
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;
}
}
{
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;
"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);
+}
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
/* 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;
/* Number of reported BTI issues. */
int n_bti_issues;
+
+ /* Number of reported GCS issues. */
+ int n_gcs_issues;
};
#define elf_aarch64_tdata(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 **);
printf ("PAC");
break;
+ case GNU_PROPERTY_AARCH64_FEATURE_1_GCS:
+ printf ("GCS");
+ break;
+
default:
printf (_("<unknown: %x>"), bit);
break;
#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
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) \
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
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='
{}
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"
@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