[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)*>(¬e_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