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

Jakob Koschel via libc-commits libc-commits at lists.llvm.org
Wed Jan 14 05:56:44 PST 2026


https://github.com/jakos-sec updated https://github.com/llvm/llvm-project/pull/174772

>From dfa5c8ba904544c878729df026391ea0c78cdca2 Mon Sep 17 00:00:00 2001
From: Jakob Koschel <jakobkoschel at google.com>
Date: Tue, 9 Dec 2025 16:51:55 +0000
Subject: [PATCH] linux: add support to parse PT_GNU_PROPERTY

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.
---
 libc/include/elf.yaml                       |   2 +
 libc/startup/linux/CMakeLists.txt           |  14 ++
 libc/startup/linux/do_start.cpp             |   4 +
 libc/startup/linux/gnu_property_section.cpp | 148 ++++++++++++++++++++
 libc/startup/linux/gnu_property_section.h   |  49 +++++++
 5 files changed, 217 insertions(+)
 create mode 100644 libc/startup/linux/gnu_property_section.cpp
 create mode 100644 libc/startup/linux/gnu_property_section.h

diff --git a/libc/include/elf.yaml b/libc/include/elf.yaml
index 819bcda98cb11..b3a3867344c30 100644
--- a/libc/include/elf.yaml
+++ b/libc/include/elf.yaml
@@ -267,6 +267,8 @@ macros:
     macro_value: '0x70000000'
   - macro_name: PT_HIPROC
     macro_value: '0x7fffffff'
+  - macro_name: PT_GNU_PROPERTY
+    macro_value: '0x6474e553'
   - macro_name: PF_X
     macro_value: '0x1'
   - macro_name: PF_W
diff --git a/libc/startup/linux/CMakeLists.txt b/libc/startup/linux/CMakeLists.txt
index 27dafa716083d..1caabcd9179d0 100644
--- a/libc/startup/linux/CMakeLists.txt
+++ b/libc/startup/linux/CMakeLists.txt
@@ -88,6 +88,19 @@ 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
+    libc.hdr.elf_proxy
+    libc.hdr.link_macros
+)
+
 add_object_library(
   do_start
   SRCS
@@ -119,6 +132,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 aac9a0c10bf9c..65adfb50bccba 100644
--- a/libc/startup/linux/do_start.cpp
+++ b/libc/startup/linux/do_start.cpp
@@ -17,6 +17,7 @@
 #include "src/stdlib/atexit.h"
 #include "src/stdlib/exit.h"
 #include "src/unistd/environ.h"
+#include "startup/linux/gnu_property_section.h"
 
 #include <sys/mman.h>
 #include <sys/syscall.h>
@@ -107,6 +108,7 @@ static TLSDescriptor tls;
   ptrdiff_t base = 0;
   app.tls.size = 0;
   ElfW(Phdr) *tls_phdr = nullptr;
+  [[maybe_unused]] ElfW(Phdr) *gnu_property_phdr = nullptr;
 
   for (uintptr_t i = 0; i < program_hdr_count; ++i) {
     ElfW(Phdr) &phdr = program_hdr_table[i];
@@ -116,6 +118,8 @@ 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..0eab9a5ef1a38
--- /dev/null
+++ b/libc/startup/linux/gnu_property_section.cpp
@@ -0,0 +1,148 @@
+//===-- 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 "hdr/elf_proxy.h"
+#include "hdr/link_macros.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/macros/config.h"
+#include "src/string/memory_utils/utils.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 {
+
+// 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];
+};
+
+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..029810e589c53
--- /dev/null
+++ b/libc/startup/linux/gnu_property_section.h
@@ -0,0 +1,49 @@
+//===-- 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 "hdr/elf_proxy.h"
+#include "hdr/link_macros.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+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.
+// 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).
+class GnuPropertySection {
+private:
+  [[maybe_unused]] 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 is_shstk_supported() const {
+    return features_.shstk_supported;
+  }
+#endif
+};
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_STARTUP_LINUX_GNU_PROPERTY_SECTION_H



More information about the libc-commits mailing list