From cfeb25cd7e92d5e854aa92034f18da2e5fa3e27a Mon Sep 17 00:00:00 2001 From: John Brawn Date: Tue, 21 May 2024 17:34:17 +0100 Subject: [PATCH] [lld][AArch64] Add support for GCS (#90732) This adds the -z gcs and -z gcs-report options, which behave similarly to -z shtk and -z cet-report, except that -z gcs accepts a parameter: * -z gcs=implicit is the default behaviour, where the GCS bit is inferred from the input objects. * -z gcs=never clears the GCS bit, ignoring the input objects. * -z gcs=always sets the GCS bit, ignoring the input objects. This is so that there's a means of explicitly disabling GCS even when all input objects have the GCS bit set. --- lld/ELF/Config.h | 5 ++ lld/ELF/Driver.cpp | 36 ++++++++ lld/test/ELF/aarch64-feature-gcs.s | 134 +++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 lld/test/ELF/aarch64-feature-gcs.s diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h index dbb81412453a..f0dfe7f377de 100644 --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -102,6 +102,9 @@ enum class GnuStackKind { None, Exec, NoExec }; // For --lto= enum LtoKind : uint8_t {UnifiedThin, UnifiedRegular, Default}; +// For -z gcs= +enum class GcsPolicy { Implicit, Never, Always }; + struct SymbolVersion { llvm::StringRef name; bool isExternCpp; @@ -188,6 +191,7 @@ struct Config { StringRef zBtiReport = "none"; StringRef zCetReport = "none"; StringRef zPauthReport = "none"; + StringRef zGcsReport = "none"; bool ltoBBAddrMap; llvm::StringRef ltoBasicBlockSections; std::pair thinLTOObjectSuffixReplace; @@ -341,6 +345,7 @@ struct Config { UnresolvedPolicy unresolvedSymbols; UnresolvedPolicy unresolvedSymbolsInShlib; Target2Policy target2; + GcsPolicy zGcs; bool power10Stubs; ARMVFPArgKind armVFPArgs = ARMVFPArgKind::Default; BuildIdKind buildId = BuildIdKind::None; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp index 028cdcc83d2f..ddc574a11314 100644 --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -466,6 +466,10 @@ static void checkOptions() { error("-z bti-report only supported on AArch64"); if (config->zPauthReport != "none") error("-z pauth-report only supported on AArch64"); + if (config->zGcsReport != "none") + error("-z gcs-report only supported on AArch64"); + if (config->zGcs != GcsPolicy::Implicit) + error("-z gcs only supported on AArch64"); } if (config->emachine != EM_386 && config->emachine != EM_X86_64 && @@ -560,6 +564,25 @@ static uint8_t getZStartStopVisibility(opt::InputArgList &args) { return ret; } +static GcsPolicy getZGcs(opt::InputArgList &args) { + GcsPolicy ret = GcsPolicy::Implicit; + for (auto *arg : args.filtered(OPT_z)) { + std::pair kv = StringRef(arg->getValue()).split('='); + if (kv.first == "gcs") { + arg->claim(); + if (kv.second == "implicit") + ret = GcsPolicy::Implicit; + else if (kv.second == "never") + ret = GcsPolicy::Never; + else if (kv.second == "always") + ret = GcsPolicy::Always; + else + error("unknown -z gcs= value: " + kv.second); + } + } + return ret; +} + // Report a warning for an unknown -z option. static void checkZOptions(opt::InputArgList &args) { // This function is called before getTarget(), when certain options are not @@ -1438,6 +1461,7 @@ static void readConfigs(opt::InputArgList &args) { config->zCopyreloc = getZFlag(args, "copyreloc", "nocopyreloc", true); config->zForceBti = hasZOption(args, "force-bti"); config->zForceIbt = hasZOption(args, "force-ibt"); + config->zGcs = getZGcs(args); config->zGlobal = hasZOption(args, "global"); config->zGnustack = getZGnuStack(args); config->zHazardplt = hasZOption(args, "hazardplt"); @@ -1510,6 +1534,7 @@ static void readConfigs(opt::InputArgList &args) { auto reports = {std::make_pair("bti-report", &config->zBtiReport), std::make_pair("cet-report", &config->zCetReport), + std::make_pair("gcs-report", &config->zGcsReport), std::make_pair("pauth-report", &config->zPauthReport)}; for (opt::Arg *arg : args.filtered(OPT_z)) { std::pair option = @@ -2677,6 +2702,11 @@ static void readSecurityNotes() { toString(f) + ": -z bti-report: file does not have " "GNU_PROPERTY_AARCH64_FEATURE_1_BTI property"); + checkAndReportMissingFeature( + config->zGcsReport, features, GNU_PROPERTY_AARCH64_FEATURE_1_GCS, + toString(f) + ": -z gcs-report: file does not have " + "GNU_PROPERTY_AARCH64_FEATURE_1_GCS property"); + checkAndReportMissingFeature( config->zCetReport, features, GNU_PROPERTY_X86_FEATURE_1_IBT, toString(f) + ": -z cet-report: file does not have " @@ -2729,6 +2759,12 @@ static void readSecurityNotes() { // Force enable Shadow Stack. if (config->zShstk) config->andFeatures |= GNU_PROPERTY_X86_FEATURE_1_SHSTK; + + // Force enable/disable GCS + if (config->zGcs == GcsPolicy::Always) + config->andFeatures |= GNU_PROPERTY_AARCH64_FEATURE_1_GCS; + else if (config->zGcs == GcsPolicy::Never) + config->andFeatures &= ~GNU_PROPERTY_AARCH64_FEATURE_1_GCS; } static void initSectionsAndLocalSyms(ELFFileBase *file, bool ignoreComdats) { diff --git a/lld/test/ELF/aarch64-feature-gcs.s b/lld/test/ELF/aarch64-feature-gcs.s new file mode 100644 index 000000000000..7a08673dbb7e --- /dev/null +++ b/lld/test/ELF/aarch64-feature-gcs.s @@ -0,0 +1,134 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t && split-file %s %t && cd %t +# RUN: llvm-mc -filetype=obj -triple=aarch64-linux-gnu func1-gcs.s -o func1-gcs.o +# RUN: llvm-mc -filetype=obj -triple=aarch64-linux-gnu func2.s -o func2.o +# RUN: llvm-mc -filetype=obj -triple=aarch64-linux-gnu func2-gcs.s -o func2-gcs.o +# RUN: llvm-mc -filetype=obj -triple=aarch64-linux-gnu func3.s -o func3.o +# RUN: llvm-mc -filetype=obj -triple=aarch64-linux-gnu func3-gcs.s -o func3-gcs.o + +## GCS should be enabled when it's enabled in all inputs or when it's forced on. + +# RUN: ld.lld func1-gcs.o func2-gcs.o func3-gcs.o -o gcs +# RUN: llvm-readelf -n gcs | FileCheck --check-prefix GCS %s +# RUN: ld.lld func1-gcs.o func3-gcs.o --shared -o gcs.so +# RUN: llvm-readelf -n gcs.so | FileCheck --check-prefix GCS %s +# RUN: ld.lld func1-gcs.o func2.o func3-gcs.o -o force-gcs -z gcs=always +# RUN: llvm-readelf -n force-gcs | FileCheck --check-prefix GCS %s +# RUN: ld.lld func2-gcs.o func3.o --shared -o force-gcs.so -z gcs=always +# RUN: llvm-readelf -n force-gcs.so | FileCheck --check-prefix GCS %s +# RUN: ld.lld func2-gcs.o func3.o --shared -o force-gcs2.so -z gcs=never -z gcs=always +# RUN: llvm-readelf -n force-gcs2.so | FileCheck --check-prefix GCS %s + +# GCS: Properties: aarch64 feature: GCS + +## GCS should not be enabled if it's not enabled in at least one input. + +# RUN: ld.lld func1-gcs.o func2.o func3-gcs.o -o no-gcs +# RUN: llvm-readelf -n no-gcs | count 0 +# RUN: ld.lld func2-gcs.o func3.o --shared -o no-gcs.so + +## GCS should be disabled with gcs=never, even if GCS is present in all inputs. + +# RUN: ld.lld func1-gcs.o func2-gcs.o func3-gcs.o -z gcs=never -o never-gcs +# RUN: llvm-readelf -n never-gcs | count 0 +# RUN: ld.lld func1-gcs.o func2-gcs.o func3-gcs.o -z gcs=always -z gcs=never -o never-gcs2 +# RUN: llvm-readelf -n never-gcs2 | count 0 + +## gcs-report should report any input files that don't have the gcs property. + +# RUN: ld.lld func1-gcs.o func2.o func3-gcs.o -o /dev/null -z gcs-report=warning 2>&1 | FileCheck --check-prefix=REPORT-WARN %s +# RUN: ld.lld func1-gcs.o func2.o func3-gcs.o -o /dev/null -z gcs-report=warning -z gcs=always 2>&1 | FileCheck --check-prefix=REPORT-WARN %s +# RUN: ld.lld func1-gcs.o func2.o func3-gcs.o -o /dev/null -z gcs-report=warning -z gcs=never 2>&1 | FileCheck --check-prefix=REPORT-WARN %s +# RUN: not ld.lld func2-gcs.o func3.o --shared -o /dev/null -z gcs-report=error 2>&1 | FileCheck --check-prefix=REPORT-ERROR %s +# RUN: not ld.lld func2-gcs.o func3.o --shared -o /dev/null -z gcs-report=error -z gcs=always 2>&1 | FileCheck --check-prefix=REPORT-ERROR %s +# RUN: not ld.lld func2-gcs.o func3.o --shared -o /dev/null -z gcs-report=error -z gcs=never 2>&1 | FileCheck --check-prefix=REPORT-ERROR %s +# RUN: ld.lld func1-gcs.o func2-gcs.o func3-gcs.o -o /dev/null -z gcs-report=warning 2>&1 | count 0 +# RUN: ld.lld func1-gcs.o func2-gcs.o func3-gcs.o -o /dev/null -z gcs-report=warning -z gcs=always 2>&1 | count 0 +# RUN: ld.lld func1-gcs.o func2-gcs.o func3-gcs.o -o /dev/null -z gcs-report=warning -z gcs=never 2>&1 | count 0 + +# REPORT-WARN: warning: func2.o: -z gcs-report: file does not have GNU_PROPERTY_AARCH64_FEATURE_1_GCS property +# REPORT-ERROR: error: func3.o: -z gcs-report: file does not have GNU_PROPERTY_AARCH64_FEATURE_1_GCS property + +## An invalid gcs option should give an error +# RUN: not ld.lld func1-gcs.o func2-gcs.o func3-gcs.o -z gcs=nonsense 2>&1 | FileCheck --check-prefix=INVALID %s + +# INVALID: error: unknown -z gcs= value: nonsense + +#--- func1-gcs.s +.section ".note.gnu.property", "a" +.long 4 +.long 0x10 +.long 0x5 +.asciz "GNU" + +.long 0xc0000000 // GNU_PROPERTY_AARCH64_FEATURE_1_AND +.long 4 +.long 4 // GNU_PROPERTY_AARCH64_FEATURE_1_GCS +.long 0 + +.text +.globl _start +.type func1,%function +func1: + bl func2 + ret + +#--- func2.s + +.text +.globl func2 +.type func2,@function +func2: + .globl func3 + .type func3, @function + bl func3 + ret + +#--- func2-gcs.s + +.section ".note.gnu.property", "a" +.long 4 +.long 0x10 +.long 0x5 +.asciz "GNU" + +.long 0xc0000000 // GNU_PROPERTY_AARCH64_FEATURE_1_AND +.long 4 +.long 4 // GNU_PROPERTY_AARCH64_FEATURE_1_GCS +.long 0 + +.text +.globl func2 +.type func2,@function +func2: + .globl func3 + .type func3, @function + bl func3 + ret + +#--- func3.s + +.text +.globl func3 +.type func3,@function +func3: + ret + +#--- func3-gcs.s + +.section ".note.gnu.property", "a" +.long 4 +.long 0x10 +.long 0x5 +.asciz "GNU" + +.long 0xc0000000 // GNU_PROPERTY_AARCH64_FEATURE_1_AND +.long 4 +.long 4 // GNU_PROPERTY_AARCH64_FEATURE_1_GCS +.long 0 + +.text +.globl func3 +.type func3,@function +func3: + ret