[libc-commits] [libc] linux: add support to parse PT_GNU_PROPERTY (PR #174772)

via libc-commits libc-commits at lists.llvm.org
Wed Jan 7 06:06:57 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libc

Author: Jakob Koschel (jakos-sec)

<details>
<summary>Changes</summary>

In order to add Control-flow Enforcement Technology (CET) Shadow Stack (SHSTK) support, we need to parse the `PT_GNU_PROPERTY` program header and the corresponding section to evaluate if the binary being started was compiled with the necessary support.

PS: This is my first PR to llvm-libc, I might have made obvious styling mistakes so I'd appreciate any feedback or suggestions to improve it.

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


4 Files Affected:

- (modified) libc/startup/linux/CMakeLists.txt (+12) 
- (modified) libc/startup/linux/do_start.cpp (+5) 
- (added) libc/startup/linux/gnu_property_section.cpp (+111) 
- (added) libc/startup/linux/gnu_property_section.h (+92) 


``````````diff
diff --git a/libc/startup/linux/CMakeLists.txt b/libc/startup/linux/CMakeLists.txt
index df2c4b9ec508c..f629fa1adde68 100644
--- a/libc/startup/linux/CMakeLists.txt
+++ b/libc/startup/linux/CMakeLists.txt
@@ -88,6 +88,17 @@ endif()
 
 add_subdirectory(${LIBC_TARGET_ARCHITECTURE})
 
+add_object_library(
+  gnu_property_section
+  SRCS
+    gnu_property_section.cpp
+  HDRS
+    gnu_property_section.h
+  DEPENDS
+    libc.src.__support.CPP.string_view
+    libc.src.__support.macros.config
+)
+
 add_object_library(
   do_start
   SRCS
@@ -118,6 +129,7 @@ merge_relocatable_object(
   .${LIBC_TARGET_ARCHITECTURE}.start
   .${LIBC_TARGET_ARCHITECTURE}.tls
   .do_start
+  .gnu_property_section
 )
 
 add_startup_object(
diff --git a/libc/startup/linux/do_start.cpp b/libc/startup/linux/do_start.cpp
index 12493d8e66ef0..0586e5d0f0449 100644
--- a/libc/startup/linux/do_start.cpp
+++ b/libc/startup/linux/do_start.cpp
@@ -16,6 +16,7 @@
 #include "src/stdlib/atexit.h"
 #include "src/stdlib/exit.h"
 #include "src/unistd/environ.h"
+#include "startup/linux/gnu_property_section.h"
 
 #include <linux/auxvec.h>
 #include <linux/elf.h>
@@ -108,6 +109,7 @@ static TLSDescriptor tls;
   ptrdiff_t base = 0;
   app.tls.size = 0;
   ElfW(Phdr) *tls_phdr = nullptr;
+  [[gnu::unused]] ElfW(Phdr) *gnu_property_phdr = nullptr;
 
   for (uintptr_t i = 0; i < program_hdr_count; ++i) {
     ElfW(Phdr) &phdr = program_hdr_table[i];
@@ -117,6 +119,9 @@ static TLSDescriptor tls;
       base = reinterpret_cast<ptrdiff_t>(_DYNAMIC) - phdr.p_vaddr;
     if (phdr.p_type == PT_TLS)
       tls_phdr = &phdr;
+    if (phdr.p_type == PT_GNU_PROPERTY) {
+      gnu_property_phdr = &phdr;
+    }
     // TODO: adjust PT_GNU_STACK
   }
 
diff --git a/libc/startup/linux/gnu_property_section.cpp b/libc/startup/linux/gnu_property_section.cpp
new file mode 100644
index 0000000000000..ab2418a493d6a
--- /dev/null
+++ b/libc/startup/linux/gnu_property_section.cpp
@@ -0,0 +1,111 @@
+//===-- Implementation file of gnu_property_section -----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#include "startup/linux/gnu_property_section.h"
+
+#include "src/__support/macros/config.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/string/memory_utils/utils.h"
+#include "include/llvm-libc-macros/link-macros.h"
+
+#include <linux/elf.h>
+
+#ifdef LIBC_TARGET_ARCH_IS_X86_64
+
+// Property types used in GNU_PROPERTY_TYPE_0 notes
+// (currently only X86_FEATURE_1_AND required).
+#define GNU_PROPERTY_X86_FEATURE_1_AND 0xc0000002
+
+// x86 processor feature bits (currently only SHSTK required).
+#define GNU_PROPERTY_X86_FEATURE_1_SHSTK (1U << 1)
+
+#endif
+
+namespace LIBC_NAMESPACE_DECL {
+
+  bool GnuPropertySection::Parse(const ElfW(Phdr)* gnu_property_phdr, const ElfW(Addr) base) {
+    if (!gnu_property_phdr) {
+      return false;
+    }
+
+    const auto note_nhdr_size = gnu_property_phdr->p_memsz;
+    // Sanity check we are using the correct phdr and the memory size is large
+    // enough to fit the program property note.
+    if (gnu_property_phdr->p_type != PT_GNU_PROPERTY ||
+        note_nhdr_size < sizeof(ElfW(ProgramPropertyNote))) {
+      return false;
+    }
+
+    const ElfW(ProgramPropertyNote) *note_nhdr =
+      reinterpret_cast<ElfW(ProgramPropertyNote)*>(base + gnu_property_phdr->p_vaddr);
+    if (!note_nhdr) {
+      return false;
+    }
+
+    const ElfW(Word) nhdr_desc_size = note_nhdr->nhdr.n_descsz;
+
+    // sizeof(*note_nhdr) does not include the size of n_desc,
+    // since it is not known at compile time.
+    // The size of it combined with n_descsz cannot exceed the total size of the
+    // program property note.
+    if ((sizeof(*note_nhdr) + nhdr_desc_size) > note_nhdr_size) {
+      return false;
+    }
+
+    if (note_nhdr->nhdr.n_namesz != 4 ||
+        note_nhdr->nhdr.n_type != NT_GNU_PROPERTY_TYPE_0 ||
+        cpp::string_view(reinterpret_cast<const char *>(note_nhdr->n_name), 4) !=
+            cpp::string_view("GNU", 4)) {
+      return false;
+    }
+
+    // program property note is valid, we can parse the program property array.
+    ElfW(Word) offset = 0;
+    while (offset < nhdr_desc_size) {
+      if ((nhdr_desc_size - offset) < sizeof(ElfW(ProgramProperty))) {
+        // We can no longer even fit the statically known size of
+        // ProgramProperty. Doing the cast and reading pr_type/pr_datasz is no
+        // longer in bounds.
+        break;
+      }
+      const ElfW(ProgramProperty) *property =
+        reinterpret_cast<const ElfW(ProgramProperty)*>(&note_nhdr->n_desc[offset]);
+
+      // Sanity check that property is correctly aligned.
+      if (distance_to_align_up<sizeof(ElfW(Word))>(property) > 0) {
+        return false;
+      }
+
+      const ElfW(Xword) property_size = sizeof(*property) + property->pr_datasz;
+      // Also check that pr_data does not reach out of bounds.
+      if ((offset + property_size) > nhdr_desc_size) {
+        return false;
+      }
+
+      switch (property->pr_type) {
+#ifdef LIBC_TARGET_ARCH_IS_X86_64
+        case GNU_PROPERTY_X86_FEATURE_1_AND: {
+          // PR_DATASZ should always be 4 bytes, for both 32bit and 64bit.
+          if (property->pr_datasz != 4) {
+            return false;
+          }
+          const uint32_t feature_bitmap = *reinterpret_cast<const uint32_t*>(&property->pr_data[0]);
+          features_.shstk_supported = (feature_bitmap & GNU_PROPERTY_X86_FEATURE_1_SHSTK) != 0;
+          break;
+      }
+#endif
+        default:
+          break;
+      }
+
+      offset += property_size;
+    }
+
+    return true;
+  }
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/startup/linux/gnu_property_section.h b/libc/startup/linux/gnu_property_section.h
new file mode 100644
index 0000000000000..483000838fd37
--- /dev/null
+++ b/libc/startup/linux/gnu_property_section.h
@@ -0,0 +1,92 @@
+//===-- Header file of gnu_property_section -------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_LIBC_STARTUP_LINUX_GNU_PROPERTY_SECTION_H
+#define LLVM_LIBC_STARTUP_LINUX_GNU_PROPERTY_SECTION_H
+
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/attributes.h"
+#include "include/llvm-libc-macros/link-macros.h"
+
+#include <stddef.h>
+#include <linux/elf.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+// The layout of the .note.gnu.property section and the program property is
+// described in "System V Application Binary Interface - Linux Extensions"
+// (https://github.com/hjl-tools/linux-abi/wiki).
+
+// The program property note is basically a note (Elf64_Nhdr) prepended with:
+// * n_name[4]: should always be "GNU\0"
+// * n_desc: an array of n_descsz bytes with program property entries
+// Since we are casting a memory address into this struct, the layout needs to
+// *exactly* match.
+struct Elf64_ProgramPropertyNote {
+  Elf64_Nhdr nhdr;
+  unsigned char n_name[4];
+  unsigned char n_desc[0]; // the size of 'n_desc' depends on n_descsz and is
+                           // not known statically.
+};
+
+// 32-bit variant for ProgramPropertyNote.
+struct Elf32_ProgramPropertyNote {
+  Elf32_Nhdr nhdr;
+  unsigned char n_name[4];
+  unsigned char n_desc[0]; // the size of 'n_desc' depends on n_descsz and is
+                           // not known statically.
+};
+
+// A program property consists of a type, the data size, followed by the actual
+// data and potential padding (aligning it to 64bit).
+// Since we are casting a memory address into this struct, the layout needs to
+// *exactly* match (The padding is ommited since it doesn't have actual
+// content).
+// pr_data needs to be ElfW_Word aligned, therefore the whole struct needs to be
+// aligned.
+struct Elf64_ProgramProperty {
+  Elf64_Word pr_type;
+  Elf64_Word pr_datasz;
+  unsigned char pr_data[0];
+};
+
+// 32-bit variant for ProgramProperty.
+struct Elf32_ProgramProperty {
+  Elf32_Word pr_type;
+  Elf32_Word pr_datasz;
+  unsigned char pr_data[0];
+};
+
+struct GnuPropertyFeatures {
+#ifdef LIBC_TARGET_ARCH_IS_X86_64
+  // Set if the binary was compiled with SHSTK enabled and declares support.
+  bool shstk_supported = false;
+#endif
+};
+
+// This class parses the .note.gnu.property section within the ELF binary.
+// Currently it only extracts the bit representing SHSTK support but can easily
+// be expanded to other features included in it.
+class GnuPropertySection {
+private:
+  GnuPropertyFeatures features_;
+
+public:
+  LIBC_INLINE GnuPropertySection() = default;
+
+  bool Parse(const ElfW(Phdr)* gnu_property_phdr, const ElfW(Addr) base);
+
+#ifdef LIBC_TARGET_ARCH_IS_X86_64
+  LIBC_INLINE bool IsSHSTKSupported() const {
+    return features_.shstk_supported;
+  }
+#endif
+};
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_STARTUP_LINUX_GNU_PROPERTY_SECTION_H

``````````

</details>


https://github.com/llvm/llvm-project/pull/174772


More information about the libc-commits mailing list