[PATCH] elflint: add additional lints to LOAD, INTERP and PHDR segments

Fabian Rast fabian.rast@tum.de
Fri Mar 20 13:50:35 GMT 2026


* src/elflint.c (check_program_header):
- PT_PHDR may not occur more than once
- PT_INTERP must precede any loadable segment entry
- PT_LOAD entries must be sorted on the p_vaddr member

Signed-off-by: Fabian Rast <fabian.rast@tum.de>
---
 src/elflint.c | 26 ++++++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/src/elflint.c b/src/elflint.c
index f47593df..3b4da4fd 100644
--- a/src/elflint.c
+++ b/src/elflint.c
@@ -4568,6 +4568,9 @@ only executables, shared objects, and core files can have program headers\n"));
   int num_pt_interp = 0;
   int num_pt_tls = 0;
   int num_pt_relro = 0;
+  int num_pt_phdr = 0;
+  size_t prev_pt_load_vaddr = 0;
+  bool pt_load_sorted = true;
 
   for (unsigned int cnt = 0; cnt < phnum; ++cnt)
     {
@@ -4592,7 +4595,17 @@ program header entry %d: unknown program header entry type %#" PRIx64 "\n"),
 	       cnt, (uint64_t) phdr->p_type);
 
       if (phdr->p_type == PT_LOAD)
-	has_loadable_segment = true;
+	{
+	  if (has_loadable_segment && pt_load_sorted
+	      && prev_pt_load_vaddr >= phdr->p_vaddr)
+	    {
+	      ERROR (_("LOAD segments not sorted by vaddr\n"));
+	      pt_load_sorted = false;
+	    }
+	  else
+	     prev_pt_load_vaddr = phdr->p_vaddr;
+	  has_loadable_segment = true;
+        }
       else if (phdr->p_type == PT_INTERP)
 	{
 	  if (++num_pt_interp != 1)
@@ -4601,6 +4614,9 @@ program header entry %d: unknown program header entry type %#" PRIx64 "\n"),
 		ERROR (_("\
 more than one INTERP entry in program header\n"));
 	    }
+	  else if (has_loadable_segment)
+	    ERROR (_("\
+INTERP entry is preceded by a loadable segment in program header\n"));
 	  has_interp_segment = true;
 	}
       else if (phdr->p_type == PT_TLS)
@@ -4694,7 +4710,13 @@ GNU_RELRO [%u] flags are not a subset of the loadable segment [%u] flags\n"),
 	}
       else if (phdr->p_type == PT_PHDR)
 	{
-	  /* Check that the region is in a writable segment.  */
+	  if (++num_pt_phdr != 1)
+	    {
+	      if (num_pt_phdr == 2)
+		ERROR (_("\
+more than one PHDR entry in program header\n"));
+	    }
+	  /* Check that the region is in a loaded segment.  */
 	  unsigned int inner;
 	  for (inner = 0; inner < phnum; ++inner)
 	    {
-- 
2.53.0


Relevant links to the specification:
- PT_PHDR may not occur more than once: https://gabi.xinuos.com/elf/07-pheader.html#program-header-entry:~:text=This%20segment%20type%20may%20not%20occur%20more%20than%20once%20in%20a%20file%2E
- PT_INTERP must precede any loadable segment entry: https://gabi.xinuos.com/elf/07-pheader.html#program-header-entry:~:text=If%20it%20is%20present%2C%20it%20must%20precede%20any%20loadable%20segment%20entry%2E
- PT_LOAD entries must be sorted on the p_vaddr member: https://gabi.xinuos.com/elf/07-pheader.html#program-header-entry:~:text=Loadable%20segment%20entries%20in%20the%20program%20header%20table%20appear%20in%20ascending%20order%2C%20sorted%20on%20the%20p%5Fvaddr%20member

The links refer to the 4.3 draft spec, but all of this is not new and also
included in version 4.2 (https://gabi.xinuos.com/v42/elf/07-pheader.html#segment-types)
and previous versions.

Cheers,
Fabian

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 293 bytes
Desc: not available
URL: <https://sourceware.org/pipermail/elfutils-devel/attachments/20260320/9fb027aa/attachment.sig>


More information about the Elfutils-devel mailing list