Hi Fabian,
On Fri, Mar 20, 2026 at 9:50 AM Fabian Rast <[email protected]> wrote:
>
> * 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 <[email protected]>
> ---
> 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;
Thanks for this patch and for including relevant links to the ELF
spec. I removed one extra space from the line 'prev_pt_load_vaddr =
phdr->p_vaddr;' and pushed this patch.
Aaron
> + 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
>