Skip to content

[LLD][COFF] Add more variety of CET and hotpatch flags #150761

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

Open
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

kkent030315
Copy link
Contributor

@kkent030315 kkent030315 commented Jul 26, 2025

Those are all MS link.exe compatible flags.

CET (Control-flow Enforcement Technology) family

  • Added LLD test that covers /cetcompat[:no]
  • Added /cetcompatstrict[:no] flag in LLD/COFF
  • Added /cetipvalidationrelaxed[:no] flag in LLD/COFF
  • Added /cetdynamicapisinproc[:no] flag in LLD/COFF

Misc

  • Added /hotpatchcompatible[:no] flag in LLD/COFF
    • This flag requires at least 6 bytes of function padding (/functionpadmin:#) as per link.exe

@llvmbot
Copy link
Member

llvmbot commented Jul 26, 2025

@llvm/pr-subscribers-platform-windows

@llvm/pr-subscribers-lld-coff

Author: None (kkent030315)

Changes

Those are all MS link.exe compatible flags.

CET (Control-flow Enforcement Technology) family

  • Added /cetcompatstrict[:no] flag in LLD/COFF
  • Added /cetipvalidationrelaxed[:no] flag in LLD/COFF
  • Added /cetdynamicapisinproc[:no] flag in LLD/COFF

Misc

  • Added /hotpatchcompatible[:no] flag in LLD/COFF

Full diff: https://github.com/llvm/llvm-project/pull/150761.diff

11 Files Affected:

  • (modified) lld/COFF/Config.h (+4)
  • (modified) lld/COFF/Driver.cpp (+7)
  • (modified) lld/COFF/Options.td (+8)
  • (modified) lld/COFF/Writer.cpp (+24-2)
  • (modified) lld/test/COFF/options.test (+40)
  • (modified) llvm/include/llvm/BinaryFormat/COFF.h (+16-1)
  • (added) llvm/test/tools/llvm-readobj/COFF/cetcompatstrict.test (+16)
  • (added) llvm/test/tools/llvm-readobj/COFF/cetdynamicapisinproc.test (+16)
  • (added) llvm/test/tools/llvm-readobj/COFF/cetipvalidationrelaxed.test (+16)
  • (added) llvm/test/tools/llvm-readobj/COFF/hotpatchcompatible.test (+16)
  • (modified) llvm/tools/llvm-readobj/COFFDumper.cpp (+8-1)
diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h
index 91b6e632fa7ed..95491c51bb7cd 100644
--- a/lld/COFF/Config.h
+++ b/lld/COFF/Config.h
@@ -307,6 +307,10 @@ struct Configuration {
   bool dynamicBase = true;
   bool allowBind = true;
   bool cetCompat = false;
+  bool cetCompatStrict = false;
+  bool cetCompatIpValidationRelaxed = false;
+  bool cetCompatDynamicApisInProcOnly = false;
+  bool hotpatchCompat = false;
   bool nxCompat = true;
   bool allowIsolation = true;
   bool terminalServerAware = true;
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 83040b534be9c..8cdadb2068ccd 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -2145,6 +2145,13 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   config->integrityCheck =
       args.hasFlag(OPT_integritycheck, OPT_integritycheck_no, false);
   config->cetCompat = args.hasFlag(OPT_cetcompat, OPT_cetcompat_no, false);
+  config->cetCompatStrict = args.hasFlag(OPT_cetcompatstrict, OPT_cetcompatstrict_no, false);
+  config->cetCompatIpValidationRelaxed =
+      args.hasFlag(OPT_cetipvalidationrelaxed, OPT_cetipvalidationrelaxed_no, false);
+  config->cetCompatDynamicApisInProcOnly =
+      args.hasFlag(OPT_cetdynamicapisinproc, OPT_cetdynamicapisinproc_no, false);
+  config->hotpatchCompat =
+    args.hasFlag(OPT_hotpatchcompatible, OPT_hotpatchcompatible_no, false);
   config->nxCompat = args.hasFlag(OPT_nxcompat, OPT_nxcompat_no, true);
   for (auto *arg : args.filtered(OPT_swaprun))
     parseSwaprun(arg->getValue());
diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td
index 2a82fb5cd8845..da2e4706fa606 100644
--- a/lld/COFF/Options.td
+++ b/lld/COFF/Options.td
@@ -196,6 +196,14 @@ defm appcontainer : B<"appcontainer",
                       "Image can run outside an app container (default)">;
 defm cetcompat : B<"cetcompat", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack",
                    "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack (default)">;
+defm cetcompatstrict : B<"cetcompatstrict", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack in strict mode",
+                         "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack in strict mode (default)">;
+defm cetipvalidationrelaxed : B<"cetipvalidationrelaxed", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack with relaxed context IP validation",
+                                "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack with relaxed context IP validation (default)">;
+defm cetdynamicapisinproc : B<"cetdynamicapisinproc", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack in such a way that dynamic APIs allowed in process",
+                              "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack with dynamic APIs allowed in process (default)">;
+defm hotpatchcompatible : B<"hotpatchcompatible", "Mark executable image as compatible with hotpatch",
+                            "Don't mark executable image as compatible with hotpatch (default)">;
 defm dynamicbase : B<"dynamicbase", "Enable ASLR (default unless /fixed)",
                      "Disable ASLR (default when /fixed)">;
 defm fixed : B<"fixed", "Disable base relocations",
diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp
index 076561807af47..5c72cca522feb 100644
--- a/lld/COFF/Writer.cpp
+++ b/lld/COFF/Writer.cpp
@@ -1216,7 +1216,9 @@ void Writer::createMiscChunks() {
   // Create Debug Information Chunks
   debugInfoSec = config->mingw ? buildidSec : rdataSec;
   if (config->buildIDHash != BuildIDHash::None || config->debug ||
-      config->repro || config->cetCompat) {
+      config->repro || config->cetCompat || config->cetCompatStrict ||
+      config->cetCompatIpValidationRelaxed ||
+      config->cetCompatDynamicApisInProcOnly || config->hotpatchCompat) {
     debugDirectory =
         make<DebugDirectoryChunk>(ctx, debugRecords, config->repro);
     debugDirectory->setAlignment(4);
@@ -1237,10 +1239,30 @@ void Writer::createMiscChunks() {
     });
   }
 
+  uint16_t ex_characteristics_flags = 0;
   if (config->cetCompat) {
+    ex_characteristics_flags |= IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT;
+  }
+  if (config->cetCompatStrict) {
+    ex_characteristics_flags |=
+        IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE;
+  }
+  if (config->cetCompatIpValidationRelaxed) {
+    ex_characteristics_flags |= 
+        IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE;
+  }
+  if (config->cetCompatDynamicApisInProcOnly) {
+    ex_characteristics_flags |= 
+        IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY;
+  }
+  if (config->hotpatchCompat) {
+    ex_characteristics_flags |= IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE;
+  }
+
+  if (ex_characteristics_flags) {
     debugRecords.emplace_back(COFF::IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS,
                               make<ExtendedDllCharacteristicsChunk>(
-                                  IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT));
+                                  ex_characteristics_flags));
   }
 
   // Align and add each chunk referenced by the debug data directory.
diff --git a/lld/test/COFF/options.test b/lld/test/COFF/options.test
index 0dd889042869a..d131169eada61 100644
--- a/lld/test/COFF/options.test
+++ b/lld/test/COFF/options.test
@@ -60,6 +60,46 @@ CETCOMPAT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT
 # RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPAT %s
 NONCETCOMPAT-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT
 
+# RUN: lld-link /out:%t.exe /entry:main /cetcompatstrict %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CETCOMPATSTRICT %s
+CETCOMPATSTRICT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE
+
+# RUN: lld-link /out:%t.exe /entry:main %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPATSTRICT %s
+# RUN: lld-link /out:%t.exe /entry:main /cetcompatstrict:no %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPATSTRICT %s
+NONCETCOMPATSTRICT-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE
+
+# RUN: lld-link /out:%t.exe /entry:main /cetipvalidationrelaxed %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CETCOMPATSTIPVALIDATIONRELAXED %s
+CETCOMPATSTIPVALIDATIONRELAXED: IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE
+
+# RUN: lld-link /out:%t.exe /entry:main %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPATSTIPVALIDATIONRELAXED %s
+# RUN: lld-link /out:%t.exe /entry:main /cetipvalidationrelaxed:no %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPATSTIPVALIDATIONRELAXED %s
+NONCETCOMPATSTIPVALIDATIONRELAXED-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE
+
+# RUN: lld-link /out:%t.exe /entry:main /cetdynamicapisinproc %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CETDYNAMICAPISINPROC %s
+CETDYNAMICAPISINPROC: IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY
+
+# RUN: lld-link /out:%t.exe /entry:main %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETDYNAMICAPISINPROC %s
+# RUN: lld-link /out:%t.exe /entry:main /cetdynamicapisinproc:no %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETDYNAMICAPISINPROC %s
+NONCETDYNAMICAPISINPROC-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY
+
+# RUN: lld-link /out:%t.exe /entry:main /hotpatchcompatible %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=HOTPATCHCOMPATIBLE %s
+HOTPATCHCOMPATIBLE: IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE
+
+# RUN: lld-link /out:%t.exe /entry:main %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONHOTPATCHCOMPATIBLE %s
+# RUN: lld-link /out:%t.exe /entry:main /hotpatchcompatible:no %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONHOTPATCHCOMPATIBLE %s
+NONHOTPATCHCOMPATIBLE-NOT: IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE
+
 # RUN: lld-link /out:%t.exe /entry:main /swaprun:CD %t.obj
 # RUN: llvm-readobj --file-headers %t.exe | FileCheck -check-prefix=SWAPCD %s
 # RUN: lld-link /out:%t.exe /entry:main /swaprun:cd,net %t.obj
diff --git a/llvm/include/llvm/BinaryFormat/COFF.h b/llvm/include/llvm/BinaryFormat/COFF.h
index f3b5d5e3f23c6..e06fc39b5de23 100644
--- a/llvm/include/llvm/BinaryFormat/COFF.h
+++ b/llvm/include/llvm/BinaryFormat/COFF.h
@@ -694,7 +694,22 @@ enum DLLCharacteristics : unsigned {
 
 enum ExtendedDLLCharacteristics : unsigned {
   /// Image is CET compatible
-  IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT = 0x0001
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT = 0x0001,
+  /// Image is CET compatible in strict mode
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE = 0x0002,
+  /// Image is CET compatible in such a way that context IP validation is relaxed
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE = 0x0004,
+  /// Image is CET compatible in such a way that the use of
+  /// dynamic APIs is restricted to processes only
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY = 0x0008,
+  /// Reserved for future use. Not used by MSVC link.exe
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_1 = 0x0010,
+  /// Reserved for future use. Not used by MSVC link.exe
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_2 = 0x0020,
+  /// Image is CFI compatible.
+  IMAGE_DLL_CHARACTERISTICS_EX_FORWARD_CFI_COMPAT = 0x0040,
+  /// Image is hotpatch compatible.
+  IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE = 0x0080,
 };
 
 enum DebugType : unsigned {
diff --git a/llvm/test/tools/llvm-readobj/COFF/cetcompatstrict.test b/llvm/test/tools/llvm-readobj/COFF/cetcompatstrict.test
new file mode 100644
index 0000000000000..9807bfe5686ee
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/COFF/cetcompatstrict.test
@@ -0,0 +1,16 @@
+# To regenerate has-cetstrict.exe
+# $ echo int main() { return 0; } > has-cetstrict.c
+# $ cl has-cetstrict.c /link /cetcompatstrict
+RUN: llvm-readobj --coff-debug-directory %p/Inputs/has-cetstrict.exe | FileCheck %s
+
+CHECK:  DebugEntry {
+CHECK:    Characteristics: 0x0
+CHECK:    Type: ExtendedDLLCharacteristics (0x14)
+CHECK:    ExtendedCharacteristics [ (0x2)
+CHECK:      IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE (0x2)
+CHECK:    ]
+CHECK:    RawData (
+CHECK:      0000: 02000000                             |....|
+CHECK:    )
+CHECK:  }
+
diff --git a/llvm/test/tools/llvm-readobj/COFF/cetdynamicapisinproc.test b/llvm/test/tools/llvm-readobj/COFF/cetdynamicapisinproc.test
new file mode 100644
index 0000000000000..18b3ec70177cb
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/COFF/cetdynamicapisinproc.test
@@ -0,0 +1,16 @@
+# To regenerate has-cetdynamicapisinproc.exe
+# $ echo int main() { return 0; } > has-cetdynamicapisinproc.c
+# $ cl has-cetdynamicapisinproc.c /link /cetdynamicapisinproc
+RUN: llvm-readobj --coff-debug-directory %p/Inputs/has-cetdynamicapisinproc.exe | FileCheck %s
+
+CHECK:  DebugEntry {
+CHECK:    Characteristics: 0x0
+CHECK:    Type: ExtendedDLLCharacteristics (0x14)
+CHECK:    ExtendedCharacteristics [ (0x8)
+CHECK:      IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY (0x8)
+CHECK:    ]
+CHECK:    RawData (
+CHECK:      0000: 08000000                             |....|
+CHECK:    )
+CHECK:  }
+
diff --git a/llvm/test/tools/llvm-readobj/COFF/cetipvalidationrelaxed.test b/llvm/test/tools/llvm-readobj/COFF/cetipvalidationrelaxed.test
new file mode 100644
index 0000000000000..25cf1db3464f7
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/COFF/cetipvalidationrelaxed.test
@@ -0,0 +1,16 @@
+# To regenerate has-cetipvalidationrelaxed.exe
+# $ echo int main() { return 0; } > has-cetipvalidationrelaxed.c
+# $ cl has-cetipvalidationrelaxed.c /link /cetipvalidationrelaxed
+RUN: llvm-readobj --coff-debug-directory %p/Inputs/has-cetipvalidationrelaxed.exe | FileCheck %s
+
+CHECK:  DebugEntry {
+CHECK:    Characteristics: 0x0
+CHECK:    Type: ExtendedDLLCharacteristics (0x14)
+CHECK:    ExtendedCharacteristics [ (0x4)
+CHECK:      IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE (0x4)
+CHECK:    ]
+CHECK:    RawData (
+CHECK:      0000: 04000000                             |....|
+CHECK:    )
+CHECK:  }
+
diff --git a/llvm/test/tools/llvm-readobj/COFF/hotpatchcompatible.test b/llvm/test/tools/llvm-readobj/COFF/hotpatchcompatible.test
new file mode 100644
index 0000000000000..87208d24f9fee
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/COFF/hotpatchcompatible.test
@@ -0,0 +1,16 @@
+# To regenerate has-hotpatchcompatible.exe
+# $ echo int main() { return 0; } > has-hotpatchcompatible.c
+# $ cl has-hotpatchcompatible.c /link /hotpatchcompatible
+RUN: llvm-readobj --coff-debug-directory %p/Inputs/has-hotpatchcompatible.exe | FileCheck %s
+
+CHECK:  DebugEntry {
+CHECK:    Characteristics: 0x0
+CHECK:    Type: ExtendedDLLCharacteristics (0x14)
+CHECK:    ExtendedCharacteristics [ (0x80)
+CHECK:      IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE (0x80)
+CHECK:    ]
+CHECK:    RawData (
+CHECK:      0000: 80000000                             |....|
+CHECK:    )
+CHECK:  }
+
diff --git a/llvm/tools/llvm-readobj/COFFDumper.cpp b/llvm/tools/llvm-readobj/COFFDumper.cpp
index dce8e60bda1ef..2f158e1bbe781 100644
--- a/llvm/tools/llvm-readobj/COFFDumper.cpp
+++ b/llvm/tools/llvm-readobj/COFFDumper.cpp
@@ -414,7 +414,14 @@ const EnumEntry<COFF::DLLCharacteristics> PEDLLCharacteristics[] = {
 
 static const EnumEntry<COFF::ExtendedDLLCharacteristics>
     PEExtendedDLLCharacteristics[] = {
-        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT                                ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE                    ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY       ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_1                            ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_2                            ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_FORWARD_CFI_COMPAT                        ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE                       ),
 };
 
 static const EnumEntry<COFF::SectionCharacteristics>

@llvmbot
Copy link
Member

llvmbot commented Jul 26, 2025

@llvm/pr-subscribers-lld

Author: None (kkent030315)

Changes

Those are all MS link.exe compatible flags.

CET (Control-flow Enforcement Technology) family

  • Added /cetcompatstrict[:no] flag in LLD/COFF
  • Added /cetipvalidationrelaxed[:no] flag in LLD/COFF
  • Added /cetdynamicapisinproc[:no] flag in LLD/COFF

Misc

  • Added /hotpatchcompatible[:no] flag in LLD/COFF

Full diff: https://github.com/llvm/llvm-project/pull/150761.diff

11 Files Affected:

  • (modified) lld/COFF/Config.h (+4)
  • (modified) lld/COFF/Driver.cpp (+7)
  • (modified) lld/COFF/Options.td (+8)
  • (modified) lld/COFF/Writer.cpp (+24-2)
  • (modified) lld/test/COFF/options.test (+40)
  • (modified) llvm/include/llvm/BinaryFormat/COFF.h (+16-1)
  • (added) llvm/test/tools/llvm-readobj/COFF/cetcompatstrict.test (+16)
  • (added) llvm/test/tools/llvm-readobj/COFF/cetdynamicapisinproc.test (+16)
  • (added) llvm/test/tools/llvm-readobj/COFF/cetipvalidationrelaxed.test (+16)
  • (added) llvm/test/tools/llvm-readobj/COFF/hotpatchcompatible.test (+16)
  • (modified) llvm/tools/llvm-readobj/COFFDumper.cpp (+8-1)
diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h
index 91b6e632fa7ed..95491c51bb7cd 100644
--- a/lld/COFF/Config.h
+++ b/lld/COFF/Config.h
@@ -307,6 +307,10 @@ struct Configuration {
   bool dynamicBase = true;
   bool allowBind = true;
   bool cetCompat = false;
+  bool cetCompatStrict = false;
+  bool cetCompatIpValidationRelaxed = false;
+  bool cetCompatDynamicApisInProcOnly = false;
+  bool hotpatchCompat = false;
   bool nxCompat = true;
   bool allowIsolation = true;
   bool terminalServerAware = true;
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 83040b534be9c..8cdadb2068ccd 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -2145,6 +2145,13 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   config->integrityCheck =
       args.hasFlag(OPT_integritycheck, OPT_integritycheck_no, false);
   config->cetCompat = args.hasFlag(OPT_cetcompat, OPT_cetcompat_no, false);
+  config->cetCompatStrict = args.hasFlag(OPT_cetcompatstrict, OPT_cetcompatstrict_no, false);
+  config->cetCompatIpValidationRelaxed =
+      args.hasFlag(OPT_cetipvalidationrelaxed, OPT_cetipvalidationrelaxed_no, false);
+  config->cetCompatDynamicApisInProcOnly =
+      args.hasFlag(OPT_cetdynamicapisinproc, OPT_cetdynamicapisinproc_no, false);
+  config->hotpatchCompat =
+    args.hasFlag(OPT_hotpatchcompatible, OPT_hotpatchcompatible_no, false);
   config->nxCompat = args.hasFlag(OPT_nxcompat, OPT_nxcompat_no, true);
   for (auto *arg : args.filtered(OPT_swaprun))
     parseSwaprun(arg->getValue());
diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td
index 2a82fb5cd8845..da2e4706fa606 100644
--- a/lld/COFF/Options.td
+++ b/lld/COFF/Options.td
@@ -196,6 +196,14 @@ defm appcontainer : B<"appcontainer",
                       "Image can run outside an app container (default)">;
 defm cetcompat : B<"cetcompat", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack",
                    "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack (default)">;
+defm cetcompatstrict : B<"cetcompatstrict", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack in strict mode",
+                         "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack in strict mode (default)">;
+defm cetipvalidationrelaxed : B<"cetipvalidationrelaxed", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack with relaxed context IP validation",
+                                "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack with relaxed context IP validation (default)">;
+defm cetdynamicapisinproc : B<"cetdynamicapisinproc", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack in such a way that dynamic APIs allowed in process",
+                              "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack with dynamic APIs allowed in process (default)">;
+defm hotpatchcompatible : B<"hotpatchcompatible", "Mark executable image as compatible with hotpatch",
+                            "Don't mark executable image as compatible with hotpatch (default)">;
 defm dynamicbase : B<"dynamicbase", "Enable ASLR (default unless /fixed)",
                      "Disable ASLR (default when /fixed)">;
 defm fixed : B<"fixed", "Disable base relocations",
diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp
index 076561807af47..5c72cca522feb 100644
--- a/lld/COFF/Writer.cpp
+++ b/lld/COFF/Writer.cpp
@@ -1216,7 +1216,9 @@ void Writer::createMiscChunks() {
   // Create Debug Information Chunks
   debugInfoSec = config->mingw ? buildidSec : rdataSec;
   if (config->buildIDHash != BuildIDHash::None || config->debug ||
-      config->repro || config->cetCompat) {
+      config->repro || config->cetCompat || config->cetCompatStrict ||
+      config->cetCompatIpValidationRelaxed ||
+      config->cetCompatDynamicApisInProcOnly || config->hotpatchCompat) {
     debugDirectory =
         make<DebugDirectoryChunk>(ctx, debugRecords, config->repro);
     debugDirectory->setAlignment(4);
@@ -1237,10 +1239,30 @@ void Writer::createMiscChunks() {
     });
   }
 
+  uint16_t ex_characteristics_flags = 0;
   if (config->cetCompat) {
+    ex_characteristics_flags |= IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT;
+  }
+  if (config->cetCompatStrict) {
+    ex_characteristics_flags |=
+        IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE;
+  }
+  if (config->cetCompatIpValidationRelaxed) {
+    ex_characteristics_flags |= 
+        IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE;
+  }
+  if (config->cetCompatDynamicApisInProcOnly) {
+    ex_characteristics_flags |= 
+        IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY;
+  }
+  if (config->hotpatchCompat) {
+    ex_characteristics_flags |= IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE;
+  }
+
+  if (ex_characteristics_flags) {
     debugRecords.emplace_back(COFF::IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS,
                               make<ExtendedDllCharacteristicsChunk>(
-                                  IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT));
+                                  ex_characteristics_flags));
   }
 
   // Align and add each chunk referenced by the debug data directory.
diff --git a/lld/test/COFF/options.test b/lld/test/COFF/options.test
index 0dd889042869a..d131169eada61 100644
--- a/lld/test/COFF/options.test
+++ b/lld/test/COFF/options.test
@@ -60,6 +60,46 @@ CETCOMPAT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT
 # RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPAT %s
 NONCETCOMPAT-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT
 
+# RUN: lld-link /out:%t.exe /entry:main /cetcompatstrict %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CETCOMPATSTRICT %s
+CETCOMPATSTRICT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE
+
+# RUN: lld-link /out:%t.exe /entry:main %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPATSTRICT %s
+# RUN: lld-link /out:%t.exe /entry:main /cetcompatstrict:no %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPATSTRICT %s
+NONCETCOMPATSTRICT-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE
+
+# RUN: lld-link /out:%t.exe /entry:main /cetipvalidationrelaxed %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CETCOMPATSTIPVALIDATIONRELAXED %s
+CETCOMPATSTIPVALIDATIONRELAXED: IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE
+
+# RUN: lld-link /out:%t.exe /entry:main %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPATSTIPVALIDATIONRELAXED %s
+# RUN: lld-link /out:%t.exe /entry:main /cetipvalidationrelaxed:no %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPATSTIPVALIDATIONRELAXED %s
+NONCETCOMPATSTIPVALIDATIONRELAXED-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE
+
+# RUN: lld-link /out:%t.exe /entry:main /cetdynamicapisinproc %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CETDYNAMICAPISINPROC %s
+CETDYNAMICAPISINPROC: IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY
+
+# RUN: lld-link /out:%t.exe /entry:main %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETDYNAMICAPISINPROC %s
+# RUN: lld-link /out:%t.exe /entry:main /cetdynamicapisinproc:no %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETDYNAMICAPISINPROC %s
+NONCETDYNAMICAPISINPROC-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY
+
+# RUN: lld-link /out:%t.exe /entry:main /hotpatchcompatible %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=HOTPATCHCOMPATIBLE %s
+HOTPATCHCOMPATIBLE: IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE
+
+# RUN: lld-link /out:%t.exe /entry:main %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONHOTPATCHCOMPATIBLE %s
+# RUN: lld-link /out:%t.exe /entry:main /hotpatchcompatible:no %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONHOTPATCHCOMPATIBLE %s
+NONHOTPATCHCOMPATIBLE-NOT: IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE
+
 # RUN: lld-link /out:%t.exe /entry:main /swaprun:CD %t.obj
 # RUN: llvm-readobj --file-headers %t.exe | FileCheck -check-prefix=SWAPCD %s
 # RUN: lld-link /out:%t.exe /entry:main /swaprun:cd,net %t.obj
diff --git a/llvm/include/llvm/BinaryFormat/COFF.h b/llvm/include/llvm/BinaryFormat/COFF.h
index f3b5d5e3f23c6..e06fc39b5de23 100644
--- a/llvm/include/llvm/BinaryFormat/COFF.h
+++ b/llvm/include/llvm/BinaryFormat/COFF.h
@@ -694,7 +694,22 @@ enum DLLCharacteristics : unsigned {
 
 enum ExtendedDLLCharacteristics : unsigned {
   /// Image is CET compatible
-  IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT = 0x0001
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT = 0x0001,
+  /// Image is CET compatible in strict mode
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE = 0x0002,
+  /// Image is CET compatible in such a way that context IP validation is relaxed
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE = 0x0004,
+  /// Image is CET compatible in such a way that the use of
+  /// dynamic APIs is restricted to processes only
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY = 0x0008,
+  /// Reserved for future use. Not used by MSVC link.exe
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_1 = 0x0010,
+  /// Reserved for future use. Not used by MSVC link.exe
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_2 = 0x0020,
+  /// Image is CFI compatible.
+  IMAGE_DLL_CHARACTERISTICS_EX_FORWARD_CFI_COMPAT = 0x0040,
+  /// Image is hotpatch compatible.
+  IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE = 0x0080,
 };
 
 enum DebugType : unsigned {
diff --git a/llvm/test/tools/llvm-readobj/COFF/cetcompatstrict.test b/llvm/test/tools/llvm-readobj/COFF/cetcompatstrict.test
new file mode 100644
index 0000000000000..9807bfe5686ee
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/COFF/cetcompatstrict.test
@@ -0,0 +1,16 @@
+# To regenerate has-cetstrict.exe
+# $ echo int main() { return 0; } > has-cetstrict.c
+# $ cl has-cetstrict.c /link /cetcompatstrict
+RUN: llvm-readobj --coff-debug-directory %p/Inputs/has-cetstrict.exe | FileCheck %s
+
+CHECK:  DebugEntry {
+CHECK:    Characteristics: 0x0
+CHECK:    Type: ExtendedDLLCharacteristics (0x14)
+CHECK:    ExtendedCharacteristics [ (0x2)
+CHECK:      IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE (0x2)
+CHECK:    ]
+CHECK:    RawData (
+CHECK:      0000: 02000000                             |....|
+CHECK:    )
+CHECK:  }
+
diff --git a/llvm/test/tools/llvm-readobj/COFF/cetdynamicapisinproc.test b/llvm/test/tools/llvm-readobj/COFF/cetdynamicapisinproc.test
new file mode 100644
index 0000000000000..18b3ec70177cb
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/COFF/cetdynamicapisinproc.test
@@ -0,0 +1,16 @@
+# To regenerate has-cetdynamicapisinproc.exe
+# $ echo int main() { return 0; } > has-cetdynamicapisinproc.c
+# $ cl has-cetdynamicapisinproc.c /link /cetdynamicapisinproc
+RUN: llvm-readobj --coff-debug-directory %p/Inputs/has-cetdynamicapisinproc.exe | FileCheck %s
+
+CHECK:  DebugEntry {
+CHECK:    Characteristics: 0x0
+CHECK:    Type: ExtendedDLLCharacteristics (0x14)
+CHECK:    ExtendedCharacteristics [ (0x8)
+CHECK:      IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY (0x8)
+CHECK:    ]
+CHECK:    RawData (
+CHECK:      0000: 08000000                             |....|
+CHECK:    )
+CHECK:  }
+
diff --git a/llvm/test/tools/llvm-readobj/COFF/cetipvalidationrelaxed.test b/llvm/test/tools/llvm-readobj/COFF/cetipvalidationrelaxed.test
new file mode 100644
index 0000000000000..25cf1db3464f7
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/COFF/cetipvalidationrelaxed.test
@@ -0,0 +1,16 @@
+# To regenerate has-cetipvalidationrelaxed.exe
+# $ echo int main() { return 0; } > has-cetipvalidationrelaxed.c
+# $ cl has-cetipvalidationrelaxed.c /link /cetipvalidationrelaxed
+RUN: llvm-readobj --coff-debug-directory %p/Inputs/has-cetipvalidationrelaxed.exe | FileCheck %s
+
+CHECK:  DebugEntry {
+CHECK:    Characteristics: 0x0
+CHECK:    Type: ExtendedDLLCharacteristics (0x14)
+CHECK:    ExtendedCharacteristics [ (0x4)
+CHECK:      IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE (0x4)
+CHECK:    ]
+CHECK:    RawData (
+CHECK:      0000: 04000000                             |....|
+CHECK:    )
+CHECK:  }
+
diff --git a/llvm/test/tools/llvm-readobj/COFF/hotpatchcompatible.test b/llvm/test/tools/llvm-readobj/COFF/hotpatchcompatible.test
new file mode 100644
index 0000000000000..87208d24f9fee
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/COFF/hotpatchcompatible.test
@@ -0,0 +1,16 @@
+# To regenerate has-hotpatchcompatible.exe
+# $ echo int main() { return 0; } > has-hotpatchcompatible.c
+# $ cl has-hotpatchcompatible.c /link /hotpatchcompatible
+RUN: llvm-readobj --coff-debug-directory %p/Inputs/has-hotpatchcompatible.exe | FileCheck %s
+
+CHECK:  DebugEntry {
+CHECK:    Characteristics: 0x0
+CHECK:    Type: ExtendedDLLCharacteristics (0x14)
+CHECK:    ExtendedCharacteristics [ (0x80)
+CHECK:      IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE (0x80)
+CHECK:    ]
+CHECK:    RawData (
+CHECK:      0000: 80000000                             |....|
+CHECK:    )
+CHECK:  }
+
diff --git a/llvm/tools/llvm-readobj/COFFDumper.cpp b/llvm/tools/llvm-readobj/COFFDumper.cpp
index dce8e60bda1ef..2f158e1bbe781 100644
--- a/llvm/tools/llvm-readobj/COFFDumper.cpp
+++ b/llvm/tools/llvm-readobj/COFFDumper.cpp
@@ -414,7 +414,14 @@ const EnumEntry<COFF::DLLCharacteristics> PEDLLCharacteristics[] = {
 
 static const EnumEntry<COFF::ExtendedDLLCharacteristics>
     PEExtendedDLLCharacteristics[] = {
-        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT                                ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE                    ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY       ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_1                            ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_2                            ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_FORWARD_CFI_COMPAT                        ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE                       ),
 };
 
 static const EnumEntry<COFF::SectionCharacteristics>

@llvmbot
Copy link
Member

llvmbot commented Jul 26, 2025

@llvm/pr-subscribers-llvm-binary-utilities

Author: None (kkent030315)

Changes

Those are all MS link.exe compatible flags.

CET (Control-flow Enforcement Technology) family

  • Added /cetcompatstrict[:no] flag in LLD/COFF
  • Added /cetipvalidationrelaxed[:no] flag in LLD/COFF
  • Added /cetdynamicapisinproc[:no] flag in LLD/COFF

Misc

  • Added /hotpatchcompatible[:no] flag in LLD/COFF

Full diff: https://github.com/llvm/llvm-project/pull/150761.diff

11 Files Affected:

  • (modified) lld/COFF/Config.h (+4)
  • (modified) lld/COFF/Driver.cpp (+7)
  • (modified) lld/COFF/Options.td (+8)
  • (modified) lld/COFF/Writer.cpp (+24-2)
  • (modified) lld/test/COFF/options.test (+40)
  • (modified) llvm/include/llvm/BinaryFormat/COFF.h (+16-1)
  • (added) llvm/test/tools/llvm-readobj/COFF/cetcompatstrict.test (+16)
  • (added) llvm/test/tools/llvm-readobj/COFF/cetdynamicapisinproc.test (+16)
  • (added) llvm/test/tools/llvm-readobj/COFF/cetipvalidationrelaxed.test (+16)
  • (added) llvm/test/tools/llvm-readobj/COFF/hotpatchcompatible.test (+16)
  • (modified) llvm/tools/llvm-readobj/COFFDumper.cpp (+8-1)
diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h
index 91b6e632fa7ed..95491c51bb7cd 100644
--- a/lld/COFF/Config.h
+++ b/lld/COFF/Config.h
@@ -307,6 +307,10 @@ struct Configuration {
   bool dynamicBase = true;
   bool allowBind = true;
   bool cetCompat = false;
+  bool cetCompatStrict = false;
+  bool cetCompatIpValidationRelaxed = false;
+  bool cetCompatDynamicApisInProcOnly = false;
+  bool hotpatchCompat = false;
   bool nxCompat = true;
   bool allowIsolation = true;
   bool terminalServerAware = true;
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 83040b534be9c..8cdadb2068ccd 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -2145,6 +2145,13 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   config->integrityCheck =
       args.hasFlag(OPT_integritycheck, OPT_integritycheck_no, false);
   config->cetCompat = args.hasFlag(OPT_cetcompat, OPT_cetcompat_no, false);
+  config->cetCompatStrict = args.hasFlag(OPT_cetcompatstrict, OPT_cetcompatstrict_no, false);
+  config->cetCompatIpValidationRelaxed =
+      args.hasFlag(OPT_cetipvalidationrelaxed, OPT_cetipvalidationrelaxed_no, false);
+  config->cetCompatDynamicApisInProcOnly =
+      args.hasFlag(OPT_cetdynamicapisinproc, OPT_cetdynamicapisinproc_no, false);
+  config->hotpatchCompat =
+    args.hasFlag(OPT_hotpatchcompatible, OPT_hotpatchcompatible_no, false);
   config->nxCompat = args.hasFlag(OPT_nxcompat, OPT_nxcompat_no, true);
   for (auto *arg : args.filtered(OPT_swaprun))
     parseSwaprun(arg->getValue());
diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td
index 2a82fb5cd8845..da2e4706fa606 100644
--- a/lld/COFF/Options.td
+++ b/lld/COFF/Options.td
@@ -196,6 +196,14 @@ defm appcontainer : B<"appcontainer",
                       "Image can run outside an app container (default)">;
 defm cetcompat : B<"cetcompat", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack",
                    "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack (default)">;
+defm cetcompatstrict : B<"cetcompatstrict", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack in strict mode",
+                         "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack in strict mode (default)">;
+defm cetipvalidationrelaxed : B<"cetipvalidationrelaxed", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack with relaxed context IP validation",
+                                "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack with relaxed context IP validation (default)">;
+defm cetdynamicapisinproc : B<"cetdynamicapisinproc", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack in such a way that dynamic APIs allowed in process",
+                              "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack with dynamic APIs allowed in process (default)">;
+defm hotpatchcompatible : B<"hotpatchcompatible", "Mark executable image as compatible with hotpatch",
+                            "Don't mark executable image as compatible with hotpatch (default)">;
 defm dynamicbase : B<"dynamicbase", "Enable ASLR (default unless /fixed)",
                      "Disable ASLR (default when /fixed)">;
 defm fixed : B<"fixed", "Disable base relocations",
diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp
index 076561807af47..5c72cca522feb 100644
--- a/lld/COFF/Writer.cpp
+++ b/lld/COFF/Writer.cpp
@@ -1216,7 +1216,9 @@ void Writer::createMiscChunks() {
   // Create Debug Information Chunks
   debugInfoSec = config->mingw ? buildidSec : rdataSec;
   if (config->buildIDHash != BuildIDHash::None || config->debug ||
-      config->repro || config->cetCompat) {
+      config->repro || config->cetCompat || config->cetCompatStrict ||
+      config->cetCompatIpValidationRelaxed ||
+      config->cetCompatDynamicApisInProcOnly || config->hotpatchCompat) {
     debugDirectory =
         make<DebugDirectoryChunk>(ctx, debugRecords, config->repro);
     debugDirectory->setAlignment(4);
@@ -1237,10 +1239,30 @@ void Writer::createMiscChunks() {
     });
   }
 
+  uint16_t ex_characteristics_flags = 0;
   if (config->cetCompat) {
+    ex_characteristics_flags |= IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT;
+  }
+  if (config->cetCompatStrict) {
+    ex_characteristics_flags |=
+        IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE;
+  }
+  if (config->cetCompatIpValidationRelaxed) {
+    ex_characteristics_flags |= 
+        IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE;
+  }
+  if (config->cetCompatDynamicApisInProcOnly) {
+    ex_characteristics_flags |= 
+        IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY;
+  }
+  if (config->hotpatchCompat) {
+    ex_characteristics_flags |= IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE;
+  }
+
+  if (ex_characteristics_flags) {
     debugRecords.emplace_back(COFF::IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS,
                               make<ExtendedDllCharacteristicsChunk>(
-                                  IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT));
+                                  ex_characteristics_flags));
   }
 
   // Align and add each chunk referenced by the debug data directory.
diff --git a/lld/test/COFF/options.test b/lld/test/COFF/options.test
index 0dd889042869a..d131169eada61 100644
--- a/lld/test/COFF/options.test
+++ b/lld/test/COFF/options.test
@@ -60,6 +60,46 @@ CETCOMPAT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT
 # RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPAT %s
 NONCETCOMPAT-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT
 
+# RUN: lld-link /out:%t.exe /entry:main /cetcompatstrict %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CETCOMPATSTRICT %s
+CETCOMPATSTRICT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE
+
+# RUN: lld-link /out:%t.exe /entry:main %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPATSTRICT %s
+# RUN: lld-link /out:%t.exe /entry:main /cetcompatstrict:no %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPATSTRICT %s
+NONCETCOMPATSTRICT-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE
+
+# RUN: lld-link /out:%t.exe /entry:main /cetipvalidationrelaxed %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CETCOMPATSTIPVALIDATIONRELAXED %s
+CETCOMPATSTIPVALIDATIONRELAXED: IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE
+
+# RUN: lld-link /out:%t.exe /entry:main %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPATSTIPVALIDATIONRELAXED %s
+# RUN: lld-link /out:%t.exe /entry:main /cetipvalidationrelaxed:no %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETCOMPATSTIPVALIDATIONRELAXED %s
+NONCETCOMPATSTIPVALIDATIONRELAXED-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE
+
+# RUN: lld-link /out:%t.exe /entry:main /cetdynamicapisinproc %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=CETDYNAMICAPISINPROC %s
+CETDYNAMICAPISINPROC: IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY
+
+# RUN: lld-link /out:%t.exe /entry:main %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETDYNAMICAPISINPROC %s
+# RUN: lld-link /out:%t.exe /entry:main /cetdynamicapisinproc:no %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONCETDYNAMICAPISINPROC %s
+NONCETDYNAMICAPISINPROC-NOT: IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY
+
+# RUN: lld-link /out:%t.exe /entry:main /hotpatchcompatible %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=HOTPATCHCOMPATIBLE %s
+HOTPATCHCOMPATIBLE: IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE
+
+# RUN: lld-link /out:%t.exe /entry:main %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONHOTPATCHCOMPATIBLE %s
+# RUN: lld-link /out:%t.exe /entry:main /hotpatchcompatible:no %t.obj
+# RUN: llvm-readobj --coff-debug-directory %t.exe | FileCheck -check-prefix=NONHOTPATCHCOMPATIBLE %s
+NONHOTPATCHCOMPATIBLE-NOT: IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE
+
 # RUN: lld-link /out:%t.exe /entry:main /swaprun:CD %t.obj
 # RUN: llvm-readobj --file-headers %t.exe | FileCheck -check-prefix=SWAPCD %s
 # RUN: lld-link /out:%t.exe /entry:main /swaprun:cd,net %t.obj
diff --git a/llvm/include/llvm/BinaryFormat/COFF.h b/llvm/include/llvm/BinaryFormat/COFF.h
index f3b5d5e3f23c6..e06fc39b5de23 100644
--- a/llvm/include/llvm/BinaryFormat/COFF.h
+++ b/llvm/include/llvm/BinaryFormat/COFF.h
@@ -694,7 +694,22 @@ enum DLLCharacteristics : unsigned {
 
 enum ExtendedDLLCharacteristics : unsigned {
   /// Image is CET compatible
-  IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT = 0x0001
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT = 0x0001,
+  /// Image is CET compatible in strict mode
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE = 0x0002,
+  /// Image is CET compatible in such a way that context IP validation is relaxed
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE = 0x0004,
+  /// Image is CET compatible in such a way that the use of
+  /// dynamic APIs is restricted to processes only
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY = 0x0008,
+  /// Reserved for future use. Not used by MSVC link.exe
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_1 = 0x0010,
+  /// Reserved for future use. Not used by MSVC link.exe
+  IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_2 = 0x0020,
+  /// Image is CFI compatible.
+  IMAGE_DLL_CHARACTERISTICS_EX_FORWARD_CFI_COMPAT = 0x0040,
+  /// Image is hotpatch compatible.
+  IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE = 0x0080,
 };
 
 enum DebugType : unsigned {
diff --git a/llvm/test/tools/llvm-readobj/COFF/cetcompatstrict.test b/llvm/test/tools/llvm-readobj/COFF/cetcompatstrict.test
new file mode 100644
index 0000000000000..9807bfe5686ee
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/COFF/cetcompatstrict.test
@@ -0,0 +1,16 @@
+# To regenerate has-cetstrict.exe
+# $ echo int main() { return 0; } > has-cetstrict.c
+# $ cl has-cetstrict.c /link /cetcompatstrict
+RUN: llvm-readobj --coff-debug-directory %p/Inputs/has-cetstrict.exe | FileCheck %s
+
+CHECK:  DebugEntry {
+CHECK:    Characteristics: 0x0
+CHECK:    Type: ExtendedDLLCharacteristics (0x14)
+CHECK:    ExtendedCharacteristics [ (0x2)
+CHECK:      IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE (0x2)
+CHECK:    ]
+CHECK:    RawData (
+CHECK:      0000: 02000000                             |....|
+CHECK:    )
+CHECK:  }
+
diff --git a/llvm/test/tools/llvm-readobj/COFF/cetdynamicapisinproc.test b/llvm/test/tools/llvm-readobj/COFF/cetdynamicapisinproc.test
new file mode 100644
index 0000000000000..18b3ec70177cb
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/COFF/cetdynamicapisinproc.test
@@ -0,0 +1,16 @@
+# To regenerate has-cetdynamicapisinproc.exe
+# $ echo int main() { return 0; } > has-cetdynamicapisinproc.c
+# $ cl has-cetdynamicapisinproc.c /link /cetdynamicapisinproc
+RUN: llvm-readobj --coff-debug-directory %p/Inputs/has-cetdynamicapisinproc.exe | FileCheck %s
+
+CHECK:  DebugEntry {
+CHECK:    Characteristics: 0x0
+CHECK:    Type: ExtendedDLLCharacteristics (0x14)
+CHECK:    ExtendedCharacteristics [ (0x8)
+CHECK:      IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY (0x8)
+CHECK:    ]
+CHECK:    RawData (
+CHECK:      0000: 08000000                             |....|
+CHECK:    )
+CHECK:  }
+
diff --git a/llvm/test/tools/llvm-readobj/COFF/cetipvalidationrelaxed.test b/llvm/test/tools/llvm-readobj/COFF/cetipvalidationrelaxed.test
new file mode 100644
index 0000000000000..25cf1db3464f7
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/COFF/cetipvalidationrelaxed.test
@@ -0,0 +1,16 @@
+# To regenerate has-cetipvalidationrelaxed.exe
+# $ echo int main() { return 0; } > has-cetipvalidationrelaxed.c
+# $ cl has-cetipvalidationrelaxed.c /link /cetipvalidationrelaxed
+RUN: llvm-readobj --coff-debug-directory %p/Inputs/has-cetipvalidationrelaxed.exe | FileCheck %s
+
+CHECK:  DebugEntry {
+CHECK:    Characteristics: 0x0
+CHECK:    Type: ExtendedDLLCharacteristics (0x14)
+CHECK:    ExtendedCharacteristics [ (0x4)
+CHECK:      IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE (0x4)
+CHECK:    ]
+CHECK:    RawData (
+CHECK:      0000: 04000000                             |....|
+CHECK:    )
+CHECK:  }
+
diff --git a/llvm/test/tools/llvm-readobj/COFF/hotpatchcompatible.test b/llvm/test/tools/llvm-readobj/COFF/hotpatchcompatible.test
new file mode 100644
index 0000000000000..87208d24f9fee
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/COFF/hotpatchcompatible.test
@@ -0,0 +1,16 @@
+# To regenerate has-hotpatchcompatible.exe
+# $ echo int main() { return 0; } > has-hotpatchcompatible.c
+# $ cl has-hotpatchcompatible.c /link /hotpatchcompatible
+RUN: llvm-readobj --coff-debug-directory %p/Inputs/has-hotpatchcompatible.exe | FileCheck %s
+
+CHECK:  DebugEntry {
+CHECK:    Characteristics: 0x0
+CHECK:    Type: ExtendedDLLCharacteristics (0x14)
+CHECK:    ExtendedCharacteristics [ (0x80)
+CHECK:      IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE (0x80)
+CHECK:    ]
+CHECK:    RawData (
+CHECK:      0000: 80000000                             |....|
+CHECK:    )
+CHECK:  }
+
diff --git a/llvm/tools/llvm-readobj/COFFDumper.cpp b/llvm/tools/llvm-readobj/COFFDumper.cpp
index dce8e60bda1ef..2f158e1bbe781 100644
--- a/llvm/tools/llvm-readobj/COFFDumper.cpp
+++ b/llvm/tools/llvm-readobj/COFFDumper.cpp
@@ -414,7 +414,14 @@ const EnumEntry<COFF::DLLCharacteristics> PEDLLCharacteristics[] = {
 
 static const EnumEntry<COFF::ExtendedDLLCharacteristics>
     PEExtendedDLLCharacteristics[] = {
-        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT                                ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE                    ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY       ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_1                            ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_2                            ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_FORWARD_CFI_COMPAT                        ),
+        LLVM_READOBJ_ENUM_ENT(COFF, IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE                       ),
 };
 
 static const EnumEntry<COFF::SectionCharacteristics>

Copy link

github-actions bot commented Jul 26, 2025

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

@kkent030315
Copy link
Contributor Author

For the linting problem in COFFDumper.cpp I personally think it's traditional way and should be kept as is.

@tru
Copy link
Collaborator

tru commented Jul 26, 2025

For the linting problem in COFFDumper.cpp I personally think it's traditional way and should be kept as is.

I agree. Feel free to add the clang format on/off comments around that block.

@kkent030315 kkent030315 force-pushed the cetvariants branch 2 times, most recently from 72842c2 to 3c52ce8 Compare July 26, 2025 19:26
Copy link
Member

@mstorsjo mstorsjo left a comment

Choose a reason for hiding this comment

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

To emphasize the layering, I think this would be good to split into two separate PRs, one for adding support for the new flags in llvm-readobj, and one for making LLD produce them.

kkent030315 added a commit to kkent030315/llvm-project that referenced this pull request Jul 28, 2025
Isolated COFF dumper part from llvm#150761.

- Decreased the size of existing `has-cet.exe`.
- Added `IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE`
- Added `IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE`
- Added `IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY`
- Added `IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_1`
- Added `IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_2`
- Added `IMAGE_DLL_CHARACTERISTICS_EX_FORWARD_CFI_COMPAT`
- Added `IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE`
kkent030315 added a commit to kkent030315/llvm-project that referenced this pull request Jul 28, 2025
Split out the COFF dumper from issue llvm#150761.

- Decreased the size of existing `has-cet.exe`.
- Added `IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE`
- Added `IMAGE_DLL_CHARACTERISTICS_EX_CET_SET_CONTEXT_IP_VALIDATION_RELAXED_MODE`
- Added `IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY`
- Added `IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_1`
- Added `IMAGE_DLL_CHARACTERISTICS_EX_CET_RESERVED_2`
- Added `IMAGE_DLL_CHARACTERISTICS_EX_FORWARD_CFI_COMPAT`
- Added `IMAGE_DLL_CHARACTERISTICS_EX_HOTPATCH_COMPATIBLE`
@kkent030315
Copy link
Contributor Author

@mstorsjo Thanks for the review for this one as well! I parted the llvm-readobj changes to #150967. In order to merge this PR it requires #150967 to get merged because of enum flags being added. Afterwards I can revert llvm-readobj changes in this PR and get the final review.

@kkent030315
Copy link
Contributor Author

Ok, #150967 is now merged and parted changs reverted in this PR. Ready for final review :)

@kkent030315 kkent030315 changed the title [LLD][COFF] Add more variety of CET flags [LLD][COFF] Add more variety of CET/hotpatch flags Jul 30, 2025
@kkent030315 kkent030315 changed the title [LLD][COFF] Add more variety of CET/hotpatch flags [LLD][COFF] Add more variety of CET and hotpatch flags Jul 30, 2025
Copy link
Member

@mstorsjo mstorsjo left a comment

Choose a reason for hiding this comment

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

LGTM now, thanks!

@aganea
Copy link
Member

aganea commented Jul 30, 2025

While I am not familiar with these options, I have the impression a lot more is required to support these CET and hotpatching features in the linker, not just emitting the right flags in the binary. I have the feeling we can't just blindly emit these flags if the feature is not there, can we? I'd be fine to support the /flag:no variants for completness, but we cannot give our users the false impression these features are supported when they are not. @sivadeilra @dpaoliello Are you able please to give more insight on this, and/or to assert this PR is indeed in line (or not) with what link.exe does?

@kkent030315
Copy link
Contributor Author

kkent030315 commented Jul 30, 2025

While I am not familiar with these options, I have the impression a lot more is required to support these CET and hotpatching features in the linker, not just emitting the right flags in the binary. I have the feeling we can't just blindly emit these flags if the feature is not there, can we? I'd be fine to support the /flag:no variants for completness, but we cannot give our users the false impression these features are supported when they are not. @sivadeilra @dpaoliello Are you able please to give more insight on this, and/or to assert this PR is indeed in line (or not) with what link.exe does?

Let me clarify that all of these options (including the /cetcompat) are just flags that are being read by the Windows loader and used by the Windows kernel to handle the real CET behavior in the kernel. Nothing needs to be performed on the linker other than adding those flags. (You can bindiff with and without those flags using MSVC toolchain, and CET SS is hardware feature, not software.)

Here is the snippet of the link.exe code that handles those flags. *((_DWORD *)v5 + 986) is where the extended DLL characheristics flag will be stored.
image

Another look here:

Generally, code changes are not needed and the only modification to the binary is a bit in the PE header.
https://techcommunity.microsoft.com/blog/windowsosplatform/developer-guidance-for-hardware-enforced-stack-protection/2163340

The flags being added in this PR is to control /cetcompat behavior.

@kkent030315
Copy link
Contributor Author

kkent030315 commented Jul 30, 2025

Just to make sure that my statements stands correct, I have attached a zip consists of 4 PE64 file compiled/linked with different MSVC linker flags.

(/DEBUG:NONE is used to eliminate the unique codeview info to appear -- that is just a noise)

  • cetdiff-cetcompat.exe: /DEBUG:NONE /cetcompat
  • cetdiff-cetdynamicapisinproc.exe: /DEBUG:NONE /cetcompat /cetcompatstrict
  • cetdiff-cetipvalidationrelaxed.exe: /DEBUG:NONE /cetcompat /cetipvalidationrelaxed
  • cetdiff-cetcompatstrict.exe: /DEBUG:NONE /cetcompat /cetdynamicapisinproc

You can manually generate them:

$ echo int main() { return 0; } > cetdiff.c
$ cl cetdiff.c /link /entry:main <the-rest-of-linker-flags>

Then you can bindiff (if you're Windows user, there's fc command to do so). Take this for example:

$ fc /b cetdiff-cetcompat.exe cetdiff-cetipvalidationrelaxed.exe
Comparing files cetdiff-cetcompat.exe and CETDIFF-CETIPVALIDATIONRELAXED.EXE
00000108: 7F 6A ; TimeDateStamp in CoffHeader
000019F4: 7F 6A ; TimeDateStamp in ImageDebugDirectoryEntry
00001A10: 7F 6A ; TimeDateStamp in ImageDebugDirectoryEntry
00001A2C: 7F 6A ; TimeDateStamp in ImageDebugDirectoryEntry
00001D60: 01 05 ; The flag

cetdiff.zip

@kkent030315
Copy link
Contributor Author

kkent030315 commented Jul 30, 2025

Some good read is here:

Actually, those flags are to control some "mitigation policy", as some described in the article:

  • When /cetcompatstrict is on, the Windows kernel sets CetUserShadowStacksStrictMode mitigation policy flag in kernel if applicable.
  • /cetipvalidationrelaxed is involved to UserCetSetContextIpValidationRelaxedMode mitigation policy flag in kernel.
  • /cetdynamicapisinproc is involved to CetDynamicApisOutOfProcOnly mitigation policy flag in kernel if applicable. (The naming of this flag is a bit confusing, but remember this is IMAGE_DLL_CHARACTERISTICS_EX_CET_DYNAMIC_APIS_ALLOW_IN_PROC_ONLY)

DUMMYUNIONNAME.DUMMYSTRUCTNAME.EnableUserShadowStackStrictMode1
If TRUE, user-mode Hardware-enforced Stack Protection is enabled for the process in strict mode. All shadow stack violations are fatal. When this field is TRUE, EnableUserShadowStack must be TRUE. If HSP is enabled in compatibility mode, it can be upgraded to strict mode at runtime by setting this field to TRUE and calling SetProcessMitigationPolicy. HSP cannot be downgraded or disabled via SetProcessMitigationPolicy. If HSP is disabled, it cannot be enabled via SetProcessMitigationPolicy.

DUMMYUNIONNAME.DUMMYSTRUCTNAME.CetDynamicApisOutOfProcOnly1
If TRUE, certain HSP APIs used to specify security properties of dynamic code can only be called from outside of the process for security purposes. These APIs are SetProcessDynamicEHContinuationTargets and SetProcessDynamicEnforcedCetCompatibleRanges. This policy can be enabled after a process has started by calling SetProcessMitigationPolicy. It cannot be disabled once enabled.

DUMMYUNIONNAME.DUMMYSTRUCTNAME.SetContextIpValidationRelaxedMode1
If TRUE, the process's Instruction Pointer validation is downgraded to relaxed mode, which allows all Instruction Pointers that are in dynamic code or in binaries that do not contain exception handling continuation metadata. When this field is TRUE, SetContextIpValidation must be TRUE. The process can be upgraded from relaxed mode to normal mode at runtime by setting this field to FALSE and calling SetProcessMitigationPolicy.

So all that said -- this is all about a flag to tell the OS how CET binaries should be treatened, so there should not be anything that should linker do.

Nice catch though. Thanks @aganea for raising the concern :)

Footnotes

  1. https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-process_mitigation_user_shadow_stack_policy 2 3

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.

5 participants