Skip to content

[LLD][ELF][RISCV][Zicfilp] Handle .note.gnu.property sections for Zicfilp/Zicfiss features #127193

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 6, 2025

Conversation

mylai-mtk
Copy link
Contributor

@mylai-mtk mylai-mtk commented Feb 14, 2025

  • When all relocatable files contain a .note.gnu.property section (with NT_GNU_PROPERTY_TYPE_0 notes) which contains a GNU_PROPERTY_RISCV_FEATURE_1_AND property in which the GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED/GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG/GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_SS bit is set:
    • The output file will contain a .note.gnu.property section with the bit set
    • A PT_GNU_PROPERTY program header is created to encompass the .note.gnu.property section
  • If -z zicfilp-unlabeled-report=[warning|error]/-z zicfilp-func-sig-report=[warning|error]/-z zicfiss-report=[warning|error] is specified, the linker will report a warning or error for any relocatable file lacking the feature bit

RISC-V Zicfilp/Zicfiss features indicate their adoptions as bits in the .note.gnu.property section of ELF files. This patch enables LLD to process the information correctly by parsing, checking and merging the bits from all input ELF files and writing the merged result to the output ELF file.

These feature bits are encoded as a mask in each input ELF files and intended to be "and"-ed together to check that all input files support a particular feature.

For RISC-V Zicfilp features, there are 2 conflicting bits allocated: GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED and
GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG. They represent the adoption of the forward edge protection of control-flow integrity with the "unlabeled" or "func-sig" policy. Since these 2 policies conflicts with each other, these 2 bits also conflict with each other. This patch adds the -z zicfilp-unlabeled-report=[none|warning|error] and -z zicfilp-func-sig-report=[none|warning|error] commandline options to make LLD report files that do not have the expected bits toggled on.

For RISC-V Zicfiss feature, there's only one bit allocated: GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS. This bit indicates that the ELF file supports Zicfiss-based shadow stack. This patch adds the -z zicfiss-report=[none|warning|error] commandline option to make LLD report files that do not have the expected bit toggled on.

The adoption of the .note.gnu.property section for RISC-V targets can be found in the psABI PR riscv-non-isa/riscv-elf-psabi-doc#417 (CFI_LP_UNLABELED and CFI_SS) and PR riscv-non-isa/riscv-elf-psabi-doc#434 (CFI_LP_FUNC_SIG).

@llvmbot
Copy link
Member

llvmbot commented Feb 14, 2025

@llvm/pr-subscribers-lld

@llvm/pr-subscribers-lld-elf

Author: Ming-Yi Lai (mylai-mtk)

Changes

RISC-V Zicfilp/Zicfiss features indicate their adoptions as bits in the .note.gnu.property section of ELF files. This patch enables LLD to process the information correctly by parsing, checking and merging the bits from all input ELF files and writing the merged result to the output ELF file.

These feature bits are encoded as a mask in each input ELF files and intended to be "and"-ed together to check that all input files support a particular feature.

For RISC-V Zicfilp features, there are 2 conflicting bits allocated: GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED and
GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG. They represent the adoption of the forward edge protection of control-flow integrity with the "unlabeled" or "func-sig" policy. Since these 2 policies conflicts with each other, these 2 bits also conflict with each other. This patch adds the -z zicfilp-unlabeled-report=none|warning|error and -z zicfilp-func-sig-report=none|warning|error commandline options to make LLD report files that do not have the expected bits toggled on.

For RISC-V Zicfiss feature, there's only one bit allocated: GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS. This bit indicates that the ELF file supports Zicfiss-based shadow stack. This patch adds the -z zicfiss-report=none|warning|error commandline option to make LLD report files that do not have the expected bit toggled on.


Patch is 32.06 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/127193.diff

7 Files Affected:

  • (modified) lld/ELF/Config.h (+3)
  • (modified) lld/ELF/Driver.cpp (+46-4)
  • (modified) lld/ELF/InputFiles.cpp (+21-8)
  • (modified) lld/ELF/SyntheticSections.cpp (+18-4)
  • (added) lld/test/ELF/riscv-feature-zicfilp-func-sig.s (+201)
  • (added) lld/test/ELF/riscv-feature-zicfilp-unlabeled.s (+254)
  • (added) lld/test/ELF/riscv-feature-zicfiss.s (+201)
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index f132b11b20c63..e452d27828779 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -229,6 +229,9 @@ struct Config {
   StringRef zCetReport = "none";
   StringRef zPauthReport = "none";
   StringRef zGcsReport = "none";
+  StringRef zZicfilpUnlabeledReport = "none";
+  StringRef zZicfilpFuncSigReport = "none";
+  StringRef zZicfissReport = "none";
   bool ltoBBAddrMap;
   llvm::StringRef ltoBasicBlockSections;
   std::pair<llvm::StringRef, llvm::StringRef> thinLTOObjectSuffixReplace;
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 7d14180a49926..2500f58df55cc 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -414,8 +414,18 @@ static void checkOptions(Ctx &ctx) {
           << "--pcrel-optimize is only supported on PowerPC64 targets";
   }
 
-  if (ctx.arg.relaxGP && ctx.arg.emachine != EM_RISCV)
-    ErrAlways(ctx) << "--relax-gp is only supported on RISC-V targets";
+  if (ctx.arg.emachine != EM_RISCV) {
+    if (ctx.arg.relaxGP)
+      ErrAlways(ctx) << "--relax-gp is only supported on RISC-V targets";
+    if (ctx.arg.zZicfilpUnlabeledReport != "none")
+      ErrAlways(ctx) << "-z zicfilip-unlabeled-report is only supported on "
+                        "RISC-V targets";
+    if (ctx.arg.zZicfilpFuncSigReport != "none")
+      ErrAlways(ctx) << "-z zicfilip-func-sig-report is only supported on "
+                        "RISC-V targets";
+    if (ctx.arg.zZicfissReport != "none")
+      ErrAlways(ctx) << "-z zicfiss-report is only supported on RISC-V targets";
+  }
 
   if (ctx.arg.emachine != EM_386 && ctx.arg.emachine != EM_X86_64 &&
       ctx.arg.zCetReport != "none")
@@ -1623,7 +1633,12 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
   auto reports = {std::make_pair("bti-report", &ctx.arg.zBtiReport),
                   std::make_pair("cet-report", &ctx.arg.zCetReport),
                   std::make_pair("gcs-report", &ctx.arg.zGcsReport),
-                  std::make_pair("pauth-report", &ctx.arg.zPauthReport)};
+                  std::make_pair("pauth-report", &ctx.arg.zPauthReport),
+                  std::make_pair("zicfilp-unlabeled-report",
+                                 &ctx.arg.zZicfilpUnlabeledReport),
+                  std::make_pair("zicfilp-func-sig-report",
+                                 &ctx.arg.zZicfilpFuncSigReport),
+                  std::make_pair("zicfiss-report", &ctx.arg.zZicfissReport)};
   for (opt::Arg *arg : args.filtered(OPT_z)) {
     std::pair<StringRef, StringRef> option =
         StringRef(arg->getValue()).split('=');
@@ -2796,7 +2811,7 @@ static void redirectSymbols(Ctx &ctx, ArrayRef<WrappedSymbol> wrapped) {
 // ones can be allowed (see -z pauth-report).
 static void readSecurityNotes(Ctx &ctx) {
   if (ctx.arg.emachine != EM_386 && ctx.arg.emachine != EM_X86_64 &&
-      ctx.arg.emachine != EM_AARCH64)
+      ctx.arg.emachine != EM_AARCH64 && ctx.arg.emachine != EM_RISCV)
     return;
 
   ctx.arg.andFeatures = -1;
@@ -2852,6 +2867,33 @@ static void readSecurityNotes(Ctx &ctx) {
         << ": -z cet-report: file does not have "
            "GNU_PROPERTY_X86_FEATURE_1_SHSTK property";
 
+    if (ctx.arg.emachine == EM_RISCV) {
+      reportUnless(ctx.arg.zZicfilpUnlabeledReport,
+                   features & GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED)
+          << f
+          << ": -z zicfilp-unlabeled-report: file does not have "
+             "GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED property";
+
+      reportUnless(ctx.arg.zZicfilpFuncSigReport,
+                   features & GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG)
+          << f
+          << ": -z zicfilp-func-sig-report: file does not have "
+             "GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG property";
+
+      if ((features & GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED) &&
+          (features & GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG))
+        Err(ctx) << f
+                 << ": file has conflicting properties: "
+                    "GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED and "
+                    "GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG";
+
+      reportUnless(ctx.arg.zZicfissReport,
+                   features & GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS)
+          << f
+          << ": -z zicfiss-report: file does not have "
+             "GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS property";
+    }
+
     if (ctx.arg.zForceBti && !(features & GNU_PROPERTY_AARCH64_FEATURE_1_BTI)) {
       features |= GNU_PROPERTY_AARCH64_FEATURE_1_BTI;
       if (ctx.arg.zBtiReport == "none")
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index d43de8ce6dfef..060ea38112fd0 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -920,7 +920,7 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
 
 // Read the following info from the .note.gnu.property section and write it to
 // the corresponding fields in `ObjFile`:
-// - Feature flags (32 bits) representing x86 or AArch64 features for
+// - Feature flags (32 bits) representing x86, AArch64 or RISC-V features for
 //   hardware-assisted call flow control;
 // - AArch64 PAuth ABI core info (16 bytes).
 template <class ELFT>
@@ -929,6 +929,22 @@ static void readGnuProperty(Ctx &ctx, const InputSection &sec,
   using Elf_Nhdr = typename ELFT::Nhdr;
   using Elf_Note = typename ELFT::Note;
 
+  uint32_t featureAndType;
+  switch (ctx.arg.emachine) {
+  case EM_386:
+  case EM_X86_64:
+    featureAndType = GNU_PROPERTY_X86_FEATURE_1_AND;
+    break;
+  case EM_AARCH64:
+    featureAndType = GNU_PROPERTY_AARCH64_FEATURE_1_AND;
+    break;
+  case EM_RISCV:
+    featureAndType = GNU_PROPERTY_RISCV_FEATURE_1_AND;
+    break;
+  default:
+    return;
+  }
+
   ArrayRef<uint8_t> data = sec.content();
   auto err = [&](const uint8_t *place) -> ELFSyncStream {
     auto diag = Err(ctx);
@@ -949,10 +965,6 @@ static void readGnuProperty(Ctx &ctx, const InputSection &sec,
       continue;
     }
 
-    uint32_t featureAndType = ctx.arg.emachine == EM_AARCH64
-                                  ? GNU_PROPERTY_AARCH64_FEATURE_1_AND
-                                  : GNU_PROPERTY_X86_FEATURE_1_AND;
-
     // Read a body of a NOTE record, which consists of type-length-value fields.
     ArrayRef<uint8_t> desc = note.getDesc(sec.addralign);
     while (!desc.empty()) {
@@ -1050,9 +1062,10 @@ InputSectionBase *ObjFile<ELFT>::createInputSection(uint32_t idx,
     }
 
     // Object files that use processor features such as Intel Control-Flow
-    // Enforcement (CET) or AArch64 Branch Target Identification BTI, use a
-    // .note.gnu.property section containing a bitfield of feature bits like the
-    // GNU_PROPERTY_X86_FEATURE_1_IBT flag. Read a bitmap containing the flag.
+    // Enforcement (CET), AArch64 Branch Target Identification BTI or RISC-V
+    // Zicfilp/Zicfiss extensions, use a .note.gnu.property section containing
+    // a bitfield of feature bits like the GNU_PROPERTY_X86_FEATURE_1_IBT flag.
+    // Read a bitmap containing the flag.
     //
     // Since we merge bitmaps from multiple object files to create a new
     // .note.gnu.property containing a single AND'ed bitmap, we discard an input
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index b03c4282ab1aa..a63368597238e 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -325,15 +325,29 @@ GnuPropertySection::GnuPropertySection(Ctx &ctx)
                        ctx.arg.wordsize) {}
 
 void GnuPropertySection::writeTo(uint8_t *buf) {
+  uint32_t featureAndType;
+  switch (ctx.arg.emachine) {
+  case EM_386:
+  case EM_X86_64:
+    featureAndType = GNU_PROPERTY_X86_FEATURE_1_AND;
+    break;
+  case EM_AARCH64:
+    featureAndType = GNU_PROPERTY_AARCH64_FEATURE_1_AND;
+    break;
+  case EM_RISCV:
+    featureAndType = GNU_PROPERTY_RISCV_FEATURE_1_AND;
+    break;
+  default:
+    Err(ctx) << "(e_machine) " << ctx.arg.emachine
+             << ": e_machine does not support .note.gnu.property section";
+    return;
+  }
+
   write32(ctx, buf, 4);                          // Name size
   write32(ctx, buf + 4, getSize() - 16);         // Content size
   write32(ctx, buf + 8, NT_GNU_PROPERTY_TYPE_0); // Type
   memcpy(buf + 12, "GNU", 4);               // Name string
 
-  uint32_t featureAndType = ctx.arg.emachine == EM_AARCH64
-                                ? GNU_PROPERTY_AARCH64_FEATURE_1_AND
-                                : GNU_PROPERTY_X86_FEATURE_1_AND;
-
   unsigned offset = 16;
   if (ctx.arg.andFeatures != 0) {
     write32(ctx, buf + offset + 0, featureAndType);      // Feature type
diff --git a/lld/test/ELF/riscv-feature-zicfilp-func-sig.s b/lld/test/ELF/riscv-feature-zicfilp-func-sig.s
new file mode 100644
index 0000000000000..21cd348b3403e
--- /dev/null
+++ b/lld/test/ELF/riscv-feature-zicfilp-func-sig.s
@@ -0,0 +1,201 @@
+# REQUIRES: riscv
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc --filetype=obj --triple=riscv32-unknown-linux-gnu rv32-func1-zicfilp.s -o rv32-func1-zicfilp.o
+# RUN: llvm-mc --filetype=obj --triple=riscv32-unknown-linux-gnu func2.s -o rv32-func2.o
+# RUN: llvm-mc --filetype=obj --triple=riscv32-unknown-linux-gnu rv32-func2-zicfilp.s -o rv32-func2-zicfilp.o
+# RUN: llvm-mc --filetype=obj --triple=riscv32-unknown-linux-gnu func3.s -o rv32-func3.o
+# RUN: llvm-mc --filetype=obj --triple=riscv32-unknown-linux-gnu rv32-func3-zicfilp.s -o rv32-func3-zicfilp.o
+# RUN: llvm-mc --filetype=obj --triple=riscv64-unknown-linux-gnu rv64-func1-zicfilp.s -o rv64-func1-zicfilp.o
+# RUN: llvm-mc --filetype=obj --triple=riscv64-unknown-linux-gnu func2.s -o rv64-func2.o
+# RUN: llvm-mc --filetype=obj --triple=riscv64-unknown-linux-gnu rv64-func2-zicfilp.s -o rv64-func2-zicfilp.o
+# RUN: llvm-mc --filetype=obj --triple=riscv64-unknown-linux-gnu func3.s -o rv64-func3.o
+# RUN: llvm-mc --filetype=obj --triple=riscv64-unknown-linux-gnu rv64-func3-zicfilp.s -o rv64-func3-zicfilp.o
+
+## ZICFILP-func-sig should be enabled when it's enabled in all inputs
+# RUN: ld.lld rv32-func1-zicfilp.o rv32-func2-zicfilp.o rv32-func3-zicfilp.o -o - | llvm-readelf -n - | FileCheck --check-prefix ZICFILP-FUNC-SIG %s
+# RUN: ld.lld rv32-func1-zicfilp.o rv32-func3-zicfilp.o --shared -o - | llvm-readelf -n - | FileCheck --check-prefix ZICFILP-FUNC-SIG %s
+# RUN: ld.lld rv64-func1-zicfilp.o rv64-func2-zicfilp.o rv64-func3-zicfilp.o -o - | llvm-readelf -n - | FileCheck --check-prefix ZICFILP-FUNC-SIG %s
+# RUN: ld.lld rv64-func1-zicfilp.o rv64-func3-zicfilp.o --shared -o - | llvm-readelf -n - | FileCheck --check-prefix ZICFILP-FUNC-SIG %s
+# ZICFILP-FUNC-SIG: Properties: RISC-V feature: ZICFILP-func-sig
+
+## ZICFILP-func-sig should not be enabled if it's not enabled in at least one input.
+# RUN: ld.lld rv32-func1-zicfilp.o rv32-func2.o rv32-func3-zicfilp.o -o - | llvm-readelf -n - | count 0
+# RUN: ld.lld rv32-func2-zicfilp.o rv32-func3.o --shared -o - | llvm-readelf -n - | count 0
+# RUN: ld.lld rv64-func1-zicfilp.o rv64-func2.o rv64-func3-zicfilp.o -o - | llvm-readelf -n - | count 0
+# RUN: ld.lld rv64-func2-zicfilp.o rv64-func3.o --shared -o - | llvm-readelf -n - | count 0
+
+## zicfilp-func-sig-report should report any input files that don't have the zicfilp-func-sig property.
+# RUN: ld.lld rv32-func1-zicfilp.o rv32-func2.o rv32-func3-zicfilp.o -o /dev/null -z zicfilp-func-sig-report=warning 2>&1 | FileCheck --check-prefix=MISS-LP-WARN %s
+# RUN: not ld.lld rv32-func2-zicfilp.o rv32-func3.o --shared -o /dev/null -z zicfilp-func-sig-report=error 2>&1 | FileCheck --check-prefix=MISS-LP-ERROR %s
+# RUN: ld.lld rv32-func1-zicfilp.o rv32-func2-zicfilp.o rv32-func3-zicfilp.o -o /dev/null -z zicfilp-func-sig-report=warning 2>&1 | count 0
+# RUN: ld.lld rv32-func1-zicfilp.o rv32-func2-zicfilp.o rv32-func3-zicfilp.o -o /dev/null -z zicfilp-func-sig-report=error 2>&1 | count 0
+# RUN: ld.lld rv64-func1-zicfilp.o rv64-func2.o rv64-func3-zicfilp.o -o /dev/null -z zicfilp-func-sig-report=warning 2>&1 | FileCheck --check-prefix=MISS-LP-WARN %s
+# RUN: not ld.lld rv64-func2-zicfilp.o rv64-func3.o --shared -o /dev/null -z zicfilp-func-sig-report=error 2>&1 | FileCheck --check-prefix=MISS-LP-ERROR %s
+# RUN: ld.lld rv64-func1-zicfilp.o rv64-func2-zicfilp.o rv64-func3-zicfilp.o -o /dev/null -z zicfilp-func-sig-report=warning 2>&1 | count 0
+# RUN: ld.lld rv64-func1-zicfilp.o rv64-func2-zicfilp.o rv64-func3-zicfilp.o -o /dev/null -z zicfilp-func-sig-report=error 2>&1 | count 0
+# MISS-LP-WARN: warning: rv{{32|64}}-func2.o: -z zicfilp-func-sig-report: file does not have GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG property
+# MISS-LP-ERROR: error: rv{{32|64}}-func3.o: -z zicfilp-func-sig-report: file does not have GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG property
+
+## An invalid -z zicfilp-func-sig-report option should give an error
+# RUN: not ld.lld rv32-func1-zicfilp.o rv32-func2-zicfilp.o rv32-func3-zicfilp.o -z zicfilp-func-sig-report=nonsense 2>&1 | FileCheck --check-prefix=INVALID-REPORT %s
+# RUN: not ld.lld rv64-func1-zicfilp.o rv64-func2-zicfilp.o rv64-func3-zicfilp.o -z zicfilp-func-sig-report=nonsense 2>&1 | FileCheck --check-prefix=INVALID-REPORT %s
+# INVALID-REPORT: error: -z zicfilp-func-sig-report= parameter nonsense is not recognized
+
+#--- rv32-func1-zicfilp.s
+
+.section ".note.gnu.property", "a"
+.balign 4
+.4byte 4
+.4byte (ndesc_end - ndesc_begin)
+.4byte 0x5        // NT_GNU_PROPERTY_TYPE_0
+.asciz "GNU"
+ndesc_begin:
+.balign 4
+.4byte 0xc0000000 // GNU_PROPERTY_RISCV_FEATURE_1_AND
+.4byte 4
+.4byte 4          // GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG
+.balign 4
+ndesc_end:
+
+.text
+.globl _start
+.type func1,%function
+func1:
+  call func2
+  ret
+
+#--- rv64-func1-zicfilp.s
+
+.section ".note.gnu.property", "a"
+.balign 8
+.4byte 4
+.4byte (ndesc_end - ndesc_begin)
+.4byte 0x5        // NT_GNU_PROPERTY_TYPE_0
+.asciz "GNU"
+ndesc_begin:
+.balign 8
+.4byte 0xc0000000 // GNU_PROPERTY_RISCV_FEATURE_1_AND
+.4byte 4
+.4byte 4          // GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG
+.balign 8
+ndesc_end:
+
+.text
+.globl _start
+.type func1,%function
+func1:
+  call func2
+  ret
+
+#--- func2.s
+
+.text
+.globl func2
+.type func2,@function
+func2:
+  .globl func3
+  .type func3, @function
+  call func3
+  ret
+
+#--- rv32-func2-zicfilp.s
+
+.section ".note.gnu.property", "a"
+.balign 4
+.4byte 4
+.4byte (ndesc_end - ndesc_begin)
+.4byte 0x5        // NT_GNU_PROPERTY_TYPE_0
+.asciz "GNU"
+ndesc_begin:
+.balign 4
+.4byte 0xc0000000 // GNU_PROPERTY_RISCV_FEATURE_1_AND
+.4byte 4
+.4byte 4          // GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG
+.balign 4
+ndesc_end:
+
+.text
+.globl func2
+.type func2,@function
+func2:
+  .globl func3
+  .type func3, @function
+  call func3
+  ret
+
+#--- rv64-func2-zicfilp.s
+
+.section ".note.gnu.property", "a"
+.balign 8
+.4byte 4
+.4byte (ndesc_end - ndesc_begin)
+.4byte 0x5        // NT_GNU_PROPERTY_TYPE_0
+.asciz "GNU"
+ndesc_begin:
+.balign 8
+.4byte 0xc0000000 // GNU_PROPERTY_RISCV_FEATURE_1_AND
+.4byte 4
+.4byte 4          // GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG
+.balign 8
+ndesc_end:
+
+.text
+.globl func2
+.type func2,@function
+func2:
+  .globl func3
+  .type func3, @function
+  call func3
+  ret
+
+#--- func3.s
+
+.text
+.globl func3
+.type func3,@function
+func3:
+  ret
+
+#--- rv32-func3-zicfilp.s
+
+.section ".note.gnu.property", "a"
+.balign 4
+.4byte 4
+.4byte (ndesc_end - ndesc_begin)
+.4byte 0x5        // NT_GNU_PROPERTY_TYPE_0
+.asciz "GNU"
+ndesc_begin:
+.balign 4
+.4byte 0xc0000000 // GNU_PROPERTY_RISCV_FEATURE_1_AND
+.4byte 4
+.4byte 4          // GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG
+.balign 4
+ndesc_end:
+
+.text
+.globl func3
+.type func3,@function
+func3:
+  ret
+
+#--- rv64-func3-zicfilp.s
+
+.section ".note.gnu.property", "a"
+.balign 8
+.4byte 4
+.4byte (ndesc_end - ndesc_begin)
+.4byte 0x5        // NT_GNU_PROPERTY_TYPE_0
+.asciz "GNU"
+ndesc_begin:
+.balign 8
+.4byte 0xc0000000 // GNU_PROPERTY_RISCV_FEATURE_1_AND
+.4byte 4
+.4byte 4          // GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG
+.balign 8
+ndesc_end:
+
+.text
+.globl func3
+.type func3,@function
+func3:
+  ret
diff --git a/lld/test/ELF/riscv-feature-zicfilp-unlabeled.s b/lld/test/ELF/riscv-feature-zicfilp-unlabeled.s
new file mode 100644
index 0000000000000..5c244e8e44500
--- /dev/null
+++ b/lld/test/ELF/riscv-feature-zicfilp-unlabeled.s
@@ -0,0 +1,254 @@
+# REQUIRES: riscv
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc --filetype=obj --triple=riscv32-unknown-linux-gnu rv32-func1-zicfilp.s -o rv32-func1-zicfilp.o
+# RUN: llvm-mc --filetype=obj --triple=riscv32-unknown-linux-gnu rv32-func1-zicfilp-conflict.s -o rv32-func1-zicfilp-conflict.o
+# RUN: llvm-mc --filetype=obj --triple=riscv32-unknown-linux-gnu func2.s -o rv32-func2.o
+# RUN: llvm-mc --filetype=obj --triple=riscv32-unknown-linux-gnu rv32-func2-zicfilp.s -o rv32-func2-zicfilp.o
+# RUN: llvm-mc --filetype=obj --triple=riscv32-unknown-linux-gnu func3.s -o rv32-func3.o
+# RUN: llvm-mc --filetype=obj --triple=riscv32-unknown-linux-gnu rv32-func3-zicfilp.s -o rv32-func3-zicfilp.o
+# RUN: llvm-mc --filetype=obj --triple=riscv64-unknown-linux-gnu rv64-func1-zicfilp.s -o rv64-func1-zicfilp.o
+# RUN: llvm-mc --filetype=obj --triple=riscv64-unknown-linux-gnu rv64-func1-zicfilp-conflict.s -o rv64-func1-zicfilp-conflict.o
+# RUN: llvm-mc --filetype=obj --triple=riscv64-unknown-linux-gnu func2.s -o rv64-func2.o
+# RUN: llvm-mc --filetype=obj --triple=riscv64-unknown-linux-gnu rv64-func2-zicfilp.s -o rv64-func2-zicfilp.o
+# RUN: llvm-mc --filetype=obj --triple=riscv64-unknown-linux-gnu func3.s -o rv64-func3.o
+# RUN: llvm-mc --filetype=obj --triple=riscv64-unknown-linux-gnu rv64-func3-zicfilp.s -o rv64-func3-zicfilp.o
+
+## ZICFILP-unlabeled should be enabled when it's enabled in all inputs
+# RUN: ld.lld rv32-func1-zicfilp.o rv32-func2-zicfilp.o rv32-func3-zicfilp.o -o - | llvm-readelf -n - | FileCheck --check-prefix ZICFILP-UNLABELED %s
+# RUN: ld.lld rv32-func1-zicfilp.o rv32-func3-zicfilp.o --shared -o - | llvm-readelf -n - | FileCheck --check-prefix ZICFILP-UNLABELED %s
+# RUN: ld.lld rv64-func1-zicfilp.o rv64-func2-zicfilp.o rv64-func3-zicfilp.o -o - | llvm-readelf -n - | FileCheck --check-prefix ZICFILP-UNLABELED %s
+# RUN: ld.lld rv64-func1-zicfilp.o rv64-func3-zicfilp.o --shared -o - | llvm-readelf -n - | FileCheck --check-prefix ZICFILP-UNLABELED %s
+# ZICFILP-UNLABELED: Properties: RISC-V feature: ZICFILP-unlabeled
+
+## ZICFILP-unlabeled should not be enabled if it's not enabled in at least one input.
+# RUN: ld.lld rv32-func1-zicfilp.o rv32-func2.o rv32-func3-zicfilp.o -o - | llvm-readelf -n - | count 0
+# RUN: ld.lld rv32-func2-zicfilp.o rv32-func3.o --shared -o - | llvm-readelf -n - | count 0
+# RUN: ld.lld rv64-func1-zicfilp.o rv64-func2.o rv64-func3-zicfilp.o -o - | llvm-readelf -n - | count 0
+# RUN: ld.lld rv64-func2-zicfilp.o rv64-func3.o --shared -o - | llvm-readelf -n - | count 0
+
+## ZICFILP-unlabeled and ZICFILP-func-sig should conflict with each other
+# RUN: not ld.lld rv32-func1-zicfilp-conflict.o rv32-func2-zicfilp.o rv32-func3-zicfilp.o -o /dev/null 2>&1 | FileCheck --check-prefix UNLABELED-FUNC-SIG-CONFLICT %s
+# RUN: not ld.lld rv64-func1-zicfilp-conflict.o rv64-func2-zicfilp.o rv64-func3-zicfilp.o -o /dev/null 2>&1 | FileCheck --check-prefix UNLABELED-FUNC-SIG-CONFLICT %s
+# UNLABELED-FUNC-SIG-CONFLICT: rv{{32|64}}-func1-zicfilp-conflict.o: file has conflicting properties: GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED and GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG
+
+## zicfilp-unlabeled-report should report any input files that don't have the zicfilp-unlabeled property.
+# RUN: ld.lld rv32-func1-zicfilp.o rv32-func2.o rv32-func3-zicfilp.o -o /dev/null -z zicfilp-unlabeled-report=warning 2>&1 | FileCheck --check-prefix=MISS-LP-WARN %s
+# RUN: not ld.lld rv32-func2-zicfilp.o rv32-func3.o --shared -o /dev/null -z zicfilp-unlabeled-report=error 2>&1 | FileCheck --check-prefix=MISS-LP-ERROR %s
+# RUN: ld.lld rv32-func1-zicfilp.o rv32-func2-zicfilp.o rv32-func3-zicfilp.o -o /dev/null -z zicfilp-unlabeled-report=warning 2>&1 | count 0
+# RUN: ld.lld rv32-func1-zicfilp.o rv32-func2-zicfilp.o rv32-func3-zicfilp.o -o /dev/null -z zicfilp-unlabeled-report=error 2>&1 | count 0
+# RUN: ld.lld rv64-func1-zicfilp.o rv64-func2.o rv64-func3-zicfilp.o -o /dev/null -z zicfilp-unlabeled-report=warning 2>&1 | FileCheck --check-p...
[truncated]

Copy link

github-actions bot commented Feb 14, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Member

@MaskRay MaskRay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The review comments on #90732 could be useful. Have you checked them?

featureAndType = GNU_PROPERTY_RISCV_FEATURE_1_AND;
break;
default:
Err(ctx) << "(e_machine) " << ctx.arg.emachine
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

llvm_unreachable (if this is indeed unreachable)

@@ -0,0 +1,201 @@
# REQUIRES: riscv
# RUN: rm -rf %t && split-file %s %t && cd %t
# RUN: llvm-mc --filetype=obj --triple=riscv32-unknown-linux-gnu rv32-func1-zicfilp.s -o rv32-func1-zicfilp.o
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-triple=riscv32 (make it clear that this is a generic ELF feature and make the triple shorter)

@mylai-mtk
Copy link
Contributor Author

mylai-mtk commented Feb 19, 2025

Hi MaskRay, thanks for the comments. I've gone through the comments in #90732 and applied those related to coding style to this patch. There are some other comments around the (force) GCS flag. Since this patch does not include an equivalent of the force flag, I'd just ignore them for now, but with this chance I would like to take this toolchain convention issue to a stakeholder @kito-cheng :

Should we follow the "force" style (-z force-zicfilp=, -z force-zicfiss), or use the "feature" style (-z zicfilp=, -z zicfiss) for the force flag?
(I'm good with both options.)

Thanks!


Updates: Address comments, beautify tests

@mylai-mtk
Copy link
Contributor Author

Update: Address comments

@kito-cheng
Copy link
Member

I would prefer use zicfilp= and zicfiss=, and it's my proposal for the option:
-z zicfilp=force-unlabeled|force-func-sig|warning, -z zicfiss=force|warning or
-z zicfilp=force-unlabeled|force-func-sig|report, -z zicfiss=force|report?

force|force-unlabeled|force-func-sig: Force set corresponding GNU property.
warning or report: Report which object missing the GNU property.

@mylai-mtk
Copy link
Contributor Author

mylai-mtk commented Feb 20, 2025

I would prefer use zicfilp= and zicfiss=, and it's my proposal for the option:
-z zicfilp=force-unlabeled|force-func-sig|warning, -z zicfiss=force|warning or
-z zicfilp=force-unlabeled|force-func-sig|report, -z zicfiss=force|report?

force|force-unlabeled|force-func-sig: Force set corresponding GNU property.
warning or report: Report which object missing the GNU property.

I can see that your proposal squashes together the conventional CFI flags of "reporting on something wrong" and "forcing the feature to be on/off", which is good as they are usually mutually exclusive. Though I have a few opinions:

  • I think report and warning do not state clearly the expected behavior. To be more specific, they do not state clearly what is to be reported/warned. To me they read like "report/warn on (enabled) Zicfilp/Zicfiss", which obviously doesn't make sense, so my next guess would be "report/warn on something wrong with enabled Zicfilp/Zicfiss", which is correct. However, IMHO it would be a bad thing that guesses and twists are needed to know what an option means
  • If warning or report is given to -z zicfilp, which labeling scheme is the user expecting? He can have 10 out of 10 files with the unlabeled scheme, but expects the func-sig scheme to be adopted
  • Why is it not error but report? Warnings can also count as "reports"

@kito-cheng
Copy link
Member

kito-cheng commented Feb 20, 2025

Oh, wait, I should read the PR description clearly before I leave comment, -z zicfilp-unlabeled-report=none|warning|error, -z zicfilp-func-sig-report=none|warning|error and -z zicfiss-report=none|warning|error which is your current proposal is apparently better.

Copy link
Member

@kito-cheng kito-cheng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but please make sure @MaskRay is also good with that before merge :)

Copy link
Member

@MaskRay MaskRay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes in case this lands before I reviewed. This patch is on the 2nd page of my "Involved PR list. It may take some time.

Can you change the description to link to the psABI change?


Q: what is GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG?

@mylai-mtk
Copy link
Contributor Author

Can you change the description to link to the psABI change?

Sure. The description has a new paragraph to indicate the involved psABI PRs.

Q: what is GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG?

A: The GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG bit represents the adoption of forward-edge control flow protection with the func-sig label scheme. The RISC-V Zicfilp ISA extension introduces the landing pad instruction to mark valid indirect jump targets in the form of lpad <label>. The lpad instruction has an additional (compared to Intel's ENDBR32/64 and AArch64's BTI instructions) 20-bit label that allows it to differentiate jumps coming from different sources: If an lpad has a non-zero label, it expects the same (not really, but sort of) value is presented in register X7, otherwise an exception is raised. Here the func-sig scheme means that the labels are generated from the signature of the function whose entry point is protected by the lpad instruction. (The detailed rules for generating labels from function signatures can be found at riscv-non-isa/riscv-elf-psabi-doc#434)

(p.s. The GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELD specifies a scheme in which the lpad instructions' labels are always 0. In this case, the lpad instruction does not check the content of register X7, and thus this scheme conflicts with the func-sig scheme.)


Updated: Update PR description

@kito-cheng
Copy link
Member

@MaskRay ping

@MaskRay
Copy link
Member

MaskRay commented May 13, 2025

Some AArch64 changes have updated similar BTI options. You'll need a rebase

…iss features

RISC-V Zicfilp/Zicfiss features indicate their adoptions as bits in the
`.note.gnu.property` section of ELF files. This patch enables LLD to process
the information correctly by parsing, checking and merging the bits from all
input ELF files and writing the merged result to the output ELF file.

These feature bits are encoded as a mask in each input ELF files and intended
to be "and"-ed together to check that all input files support a particular
feature.

For RISC-V Zicfilp features, there are 2 conflicting bits allocated:
GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_UNLABELED and
GNU_PROPERTY_RISCV_FEATURE_1_CFI_LP_FUNC_SIG. They represent the adoption of
the forward edge protection of control-flow integrity with the "unlabeled" or
"func-sig" policy. Since these 2 policies conflicts with each other, these 2
bits also conflicts with each other. This patch adds the `-z
zicfilp-unlabeled-report=none|warning|error` and `-z
zicfilp-func-sig-report=none|warning|error` commandline options to make LLD
report files that do not have the expected bits toggled on.

For RISC-V Zicfiss feature, there's only one bit allocated:
GNU_PROPERTY_RISCV_FEATURE_1_CFI_SS. This bit indicates that the ELF file
supports Zicfiss-based shadow stack. This patch adds the `-z
zicfiss-report=none|warning|error` commandline option to make LLD report files
that do not have the expected bit toggled on.
@mylai-mtk
Copy link
Contributor Author

Update: Rebase to main trunk

@mylai-mtk mylai-mtk changed the title [LLD][ELF][RISCV] Handle .note.gnu.property sections for Zicfilp/Zicfiss features [LLD][ELF][RISCV][Zicfilp] Handle .note.gnu.property sections for Zicfilp/Zicfiss features May 27, 2025
@MaskRay
Copy link
Member

MaskRay commented Jun 1, 2025

The first three paragraphs are dense but probably not so clear. Can you replace that with:

* When all relocatable files contain a `.note.gnu.property` section (with `NT_GNU_PROPERTY_TYPE_0` notes) containing the `GNU_PROPERTY_RISCV_FEATURE_1_GCS` bit, or if `-z zicfiss=always` is specified:
  + The output file will contain a `.note.gnu.property` section with the bit set.
  + A `PT_GNU_PROPERTY` program header is created to encompass the `.note.gnu.property` section.
* If `-z zicfiss-report={warning,error}` is specified, the linker will report a warning or error for any relocatable file lacking the feature bit.

@mylai-mtk
Copy link
Contributor Author

The first three paragraphs are dense but probably not so clear. Can you replace that with: ...

Your words explains the expected behavior of LLD, but does not explain the intention/meaning of that behavior, so I adapted it to be included in the PR message (the to-be commit msg) and chose to also keep my old msgs. Hope you like it. Note that the force flags. e.g. -z zicfiss=always, are not implemented by this PR, so I removed the relevant msgs.


Update: Address comments.

@mylai-mtk
Copy link
Contributor Author

Update: Address comment

@mylai-mtk mylai-mtk merged commit 1728405 into llvm:main Jun 6, 2025
11 checks passed
@mylai-mtk mylai-mtk deleted the lld-handle-notes branch June 6, 2025 02:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants