All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] linux-user: Fix AT_PHDR when program headers are relocated into their own segment
@ 2026-06-13 15:21 valium007
  2026-06-15 16:06 ` Helge Deller
  0 siblings, 1 reply; 4+ messages in thread
From: valium007 @ 2026-06-13 15:21 UTC (permalink / raw)
  To: qemu-devel; +Cc: laurent, deller, pierrick.bouvier, valium007

When a binary is patched or relocated such that the program header table is
moved into a separate PT_LOAD segment (rather than sitting at the start of the
first loadable segment), QEMU's AT_PHDR auxv entry becomes incorrect. The
loader was computing AT_PHDR as load_addr + e_phoff, which assumes the headers
are mapped 1:1 from file offset 0. This breaks when the headers are elsewhere.

The Linux kernel instead locates the PT_LOAD segment that contains e_phoff,
then computes the in-memory address as p_vaddr + (e_phoff - p_offset). This
correctly handles relocated headers.

Fix by:
1. Add phdr_addr field to image_info to cache the resolved address.
2. Initialize to load_addr + e_phoff (fallback for headers outside any PT_LOAD).
3. In the PT_LOAD mapping loop, detect if the segment contains e_phoff and
   override with the segment-relative address.
4. Use info->phdr_addr for AT_PHDR instead of the incorrect formula.

Signed-off-by: valium007 <valium7171@gmail.com>
---
 linux-user/elfload.c | 21 ++++++++++++++++++++-
 linux-user/qemu.h    |  1 +
 2 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/linux-user/elfload.c b/linux-user/elfload.c
index b05b8b0..8049c8a 100644
--- a/linux-user/elfload.c
+++ b/linux-user/elfload.c
@@ -699,7 +699,7 @@ static abi_ulong create_elf_tables(abi_ulong p, int argc, int envc,
     /* There must be exactly DLINFO_ITEMS entries here, or the assert
      * on info->auxv_len will trigger.
      */
-    NEW_AUX_ENT(AT_PHDR, (abi_ulong)(info->load_addr + exec->e_phoff));
+    NEW_AUX_ENT(AT_PHDR, (abi_ulong)(info->phdr_addr));
     NEW_AUX_ENT(AT_PHENT, (abi_ulong)(sizeof (struct elf_phdr)));
     NEW_AUX_ENT(AT_PHNUM, (abi_ulong)(exec->e_phnum));
     NEW_AUX_ENT(AT_PAGESZ, (abi_ulong)(TARGET_PAGE_SIZE));
@@ -1469,6 +1469,12 @@ static void load_elf_image(const char *image_name, const ImageSource *src,
     info->data_offset = load_bias;
     info->load_addr = load_addr;
     info->entry = ehdr->e_entry + load_bias;
+    /*
+     * Fallback for AT_PHDR if the program headers do not fall within
+     * any PT_LOAD segment (see the loop below, which overrides this with
+     * the correct in-memory address when a containing segment is found).
+     */
+    info->phdr_addr = load_addr + ehdr->e_phoff;
     info->start_code = -1;
     info->end_code = 0;
     info->start_data = -1;
@@ -1523,6 +1529,19 @@ static void load_elf_image(const char *image_name, const ImageSource *src,
             vaddr_ef = vaddr + eppnt->p_filesz;
             vaddr_em = vaddr + eppnt->p_memsz;
 
+            /*
+             * If this segment contains the program headers, record their
+             * in-memory address for AT_PHDR. This matches the kernel, which
+             * locates the headers via the containing PT_LOAD rather than
+             * assuming load_addr + e_phoff (false when the phdrs are not
+             * mapped 1:1 from file offset 0, e.g. relocated into their own
+             * segment by a binary patcher).
+             */
+            if (eppnt->p_offset <= ehdr->e_phoff &&
+                ehdr->e_phoff < eppnt->p_offset + eppnt->p_filesz) {
+                info->phdr_addr = vaddr + (ehdr->e_phoff - eppnt->p_offset);
+            }
+
             /*
              * Some segments may be completely empty, with a non-zero p_memsz
              * but no backing file segment.
diff --git a/linux-user/qemu.h b/linux-user/qemu.h
index 07fe801..2268493 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -26,6 +26,7 @@
 struct image_info {
         abi_ulong       load_bias;
         abi_ulong       load_addr;
+        abi_ulong       phdr_addr;
         abi_ulong       start_code;
         abi_ulong       end_code;
         abi_ulong       start_data;
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH] linux-user: Fix AT_PHDR when program headers are relocated into their own segment
  2026-06-13 15:21 [PATCH] linux-user: Fix AT_PHDR when program headers are relocated into their own segment valium007
@ 2026-06-15 16:06 ` Helge Deller
  2026-06-15 17:33   ` Pierrick Bouvier
  2026-06-17 12:41   ` valium
  0 siblings, 2 replies; 4+ messages in thread
From: Helge Deller @ 2026-06-15 16:06 UTC (permalink / raw)
  To: valium007, qemu-devel; +Cc: laurent, pierrick.bouvier

On 6/13/26 17:21, valium007 wrote:
> When a binary is patched or relocated such that the program header table is
> moved into a separate PT_LOAD segment 

How relevant is this? How/why does it happen?

> (rather than sitting at the start of the
> first loadable segment), QEMU's AT_PHDR auxv entry becomes incorrect. The
> loader was computing AT_PHDR as load_addr + e_phoff, which assumes the headers
> are mapped 1:1 from file offset 0. This breaks when the headers are elsewhere.
> 
> The Linux kernel instead locates the PT_LOAD segment that contains e_phoff,
> then computes the in-memory address as p_vaddr + (e_phoff - p_offset). This
> correctly handles relocated headers.

Ok, I need to check on this.
  
> Fix by:
> 1. Add phdr_addr field to image_info to cache the resolved address.
> 2. Initialize to load_addr + e_phoff (fallback for headers outside any PT_LOAD).
> 3. In the PT_LOAD mapping loop, detect if the segment contains e_phoff and
>     override with the segment-relative address.
> 4. Use info->phdr_addr for AT_PHDR instead of the incorrect formula.
> 
> Signed-off-by: valium007 <valium7171@gmail.com>

Can you sign with a real name?

Helge

> ---
>   linux-user/elfload.c | 21 ++++++++++++++++++++-
>   linux-user/qemu.h    |  1 +
>   2 files changed, 21 insertions(+), 1 deletion(-)
> 
> diff --git a/linux-user/elfload.c b/linux-user/elfload.c
> index b05b8b0..8049c8a 100644
> --- a/linux-user/elfload.c
> +++ b/linux-user/elfload.c
> @@ -699,7 +699,7 @@ static abi_ulong create_elf_tables(abi_ulong p, int argc, int envc,
>       /* There must be exactly DLINFO_ITEMS entries here, or the assert
>        * on info->auxv_len will trigger.
>        */
> -    NEW_AUX_ENT(AT_PHDR, (abi_ulong)(info->load_addr + exec->e_phoff));
> +    NEW_AUX_ENT(AT_PHDR, (abi_ulong)(info->phdr_addr));
>       NEW_AUX_ENT(AT_PHENT, (abi_ulong)(sizeof (struct elf_phdr)));
>       NEW_AUX_ENT(AT_PHNUM, (abi_ulong)(exec->e_phnum));
>       NEW_AUX_ENT(AT_PAGESZ, (abi_ulong)(TARGET_PAGE_SIZE));
> @@ -1469,6 +1469,12 @@ static void load_elf_image(const char *image_name, const ImageSource *src,
>       info->data_offset = load_bias;
>       info->load_addr = load_addr;
>       info->entry = ehdr->e_entry + load_bias;
> +    /*
> +     * Fallback for AT_PHDR if the program headers do not fall within
> +     * any PT_LOAD segment (see the loop below, which overrides this with
> +     * the correct in-memory address when a containing segment is found).
> +     */
> +    info->phdr_addr = load_addr + ehdr->e_phoff;
>       info->start_code = -1;
>       info->end_code = 0;
>       info->start_data = -1;
> @@ -1523,6 +1529,19 @@ static void load_elf_image(const char *image_name, const ImageSource *src,
>               vaddr_ef = vaddr + eppnt->p_filesz;
>               vaddr_em = vaddr + eppnt->p_memsz;
>   
> +            /*
> +             * If this segment contains the program headers, record their
> +             * in-memory address for AT_PHDR. This matches the kernel, which
> +             * locates the headers via the containing PT_LOAD rather than
> +             * assuming load_addr + e_phoff (false when the phdrs are not
> +             * mapped 1:1 from file offset 0, e.g. relocated into their own
> +             * segment by a binary patcher).
> +             */
> +            if (eppnt->p_offset <= ehdr->e_phoff &&
> +                ehdr->e_phoff < eppnt->p_offset + eppnt->p_filesz) {
> +                info->phdr_addr = vaddr + (ehdr->e_phoff - eppnt->p_offset);
> +            }
> +
>               /*
>                * Some segments may be completely empty, with a non-zero p_memsz
>                * but no backing file segment.
> diff --git a/linux-user/qemu.h b/linux-user/qemu.h
> index 07fe801..2268493 100644
> --- a/linux-user/qemu.h
> +++ b/linux-user/qemu.h
> @@ -26,6 +26,7 @@
>   struct image_info {
>           abi_ulong       load_bias;
>           abi_ulong       load_addr;
> +        abi_ulong       phdr_addr;
>           abi_ulong       start_code;
>           abi_ulong       end_code;
>           abi_ulong       start_data;



^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] linux-user: Fix AT_PHDR when program headers are relocated into their own segment
  2026-06-15 16:06 ` Helge Deller
@ 2026-06-15 17:33   ` Pierrick Bouvier
  2026-06-17 12:41   ` valium
  1 sibling, 0 replies; 4+ messages in thread
From: Pierrick Bouvier @ 2026-06-15 17:33 UTC (permalink / raw)
  To: Helge Deller, valium007, qemu-devel; +Cc: laurent

On 6/15/2026 9:06 AM, Helge Deller wrote:
> On 6/13/26 17:21, valium007 wrote:
>> When a binary is patched or relocated such that the program header
>> table is
>> moved into a separate PT_LOAD segment 
> 
> How relevant is this? How/why does it happen?
>

Maybe a tcg test could be provided to show how it can be reproduced, and
check this patch fixes the problem.

>> (rather than sitting at the start of the
>> first loadable segment), QEMU's AT_PHDR auxv entry becomes incorrect. The
>> loader was computing AT_PHDR as load_addr + e_phoff, which assumes the
>> headers
>> are mapped 1:1 from file offset 0. This breaks when the headers are
>> elsewhere.
>>
>> The Linux kernel instead locates the PT_LOAD segment that contains
>> e_phoff,
>> then computes the in-memory address as p_vaddr + (e_phoff - p_offset).
>> This
>> correctly handles relocated headers.
> 
> Ok, I need to check on this.
>  
>> Fix by:
>> 1. Add phdr_addr field to image_info to cache the resolved address.
>> 2. Initialize to load_addr + e_phoff (fallback for headers outside any
>> PT_LOAD).
>> 3. In the PT_LOAD mapping loop, detect if the segment contains e_phoff
>> and
>>     override with the segment-relative address.
>> 4. Use info->phdr_addr for AT_PHDR instead of the incorrect formula.
>>
>> Signed-off-by: valium007 <valium7171@gmail.com>
> 
> Can you sign with a real name?
> 
> Helge
> 
>> ---
>>   linux-user/elfload.c | 21 ++++++++++++++++++++-
>>   linux-user/qemu.h    |  1 +
>>   2 files changed, 21 insertions(+), 1 deletion(-)
>>
>> diff --git a/linux-user/elfload.c b/linux-user/elfload.c
>> index b05b8b0..8049c8a 100644
>> --- a/linux-user/elfload.c
>> +++ b/linux-user/elfload.c
>> @@ -699,7 +699,7 @@ static abi_ulong create_elf_tables(abi_ulong p,
>> int argc, int envc,
>>       /* There must be exactly DLINFO_ITEMS entries here, or the assert
>>        * on info->auxv_len will trigger.
>>        */
>> -    NEW_AUX_ENT(AT_PHDR, (abi_ulong)(info->load_addr + exec->e_phoff));
>> +    NEW_AUX_ENT(AT_PHDR, (abi_ulong)(info->phdr_addr));
>>       NEW_AUX_ENT(AT_PHENT, (abi_ulong)(sizeof (struct elf_phdr)));
>>       NEW_AUX_ENT(AT_PHNUM, (abi_ulong)(exec->e_phnum));
>>       NEW_AUX_ENT(AT_PAGESZ, (abi_ulong)(TARGET_PAGE_SIZE));
>> @@ -1469,6 +1469,12 @@ static void load_elf_image(const char
>> *image_name, const ImageSource *src,
>>       info->data_offset = load_bias;
>>       info->load_addr = load_addr;
>>       info->entry = ehdr->e_entry + load_bias;
>> +    /*
>> +     * Fallback for AT_PHDR if the program headers do not fall within
>> +     * any PT_LOAD segment (see the loop below, which overrides this
>> with
>> +     * the correct in-memory address when a containing segment is
>> found).
>> +     */
>> +    info->phdr_addr = load_addr + ehdr->e_phoff;
>>       info->start_code = -1;
>>       info->end_code = 0;
>>       info->start_data = -1;
>> @@ -1523,6 +1529,19 @@ static void load_elf_image(const char
>> *image_name, const ImageSource *src,
>>               vaddr_ef = vaddr + eppnt->p_filesz;
>>               vaddr_em = vaddr + eppnt->p_memsz;
>>   +            /*
>> +             * If this segment contains the program headers, record
>> their
>> +             * in-memory address for AT_PHDR. This matches the
>> kernel, which
>> +             * locates the headers via the containing PT_LOAD rather
>> than
>> +             * assuming load_addr + e_phoff (false when the phdrs are
>> not
>> +             * mapped 1:1 from file offset 0, e.g. relocated into
>> their own
>> +             * segment by a binary patcher).
>> +             */
>> +            if (eppnt->p_offset <= ehdr->e_phoff &&
>> +                ehdr->e_phoff < eppnt->p_offset + eppnt->p_filesz) {
>> +                info->phdr_addr = vaddr + (ehdr->e_phoff - eppnt-
>> >p_offset);
>> +            }
>> +
>>               /*
>>                * Some segments may be completely empty, with a non-
>> zero p_memsz
>>                * but no backing file segment.
>> diff --git a/linux-user/qemu.h b/linux-user/qemu.h
>> index 07fe801..2268493 100644
>> --- a/linux-user/qemu.h
>> +++ b/linux-user/qemu.h
>> @@ -26,6 +26,7 @@
>>   struct image_info {
>>           abi_ulong       load_bias;
>>           abi_ulong       load_addr;
>> +        abi_ulong       phdr_addr;
>>           abi_ulong       start_code;
>>           abi_ulong       end_code;
>>           abi_ulong       start_data;
> 



^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] linux-user: Fix AT_PHDR when program headers are relocated into their own segment
  2026-06-15 16:06 ` Helge Deller
  2026-06-15 17:33   ` Pierrick Bouvier
@ 2026-06-17 12:41   ` valium
  1 sibling, 0 replies; 4+ messages in thread
From: valium @ 2026-06-17 12:41 UTC (permalink / raw)
  To: Helge Deller, valium007, qemu-devel; +Cc: laurent, pierrick.bouvier

On Mon Jun 15, 2026 at 9:36 PM IST, Helge Deller wrote:
> On 6/13/26 17:21, valium007 wrote:
>> When a binary is patched or relocated such that the program header table is
>> moved into a separate PT_LOAD segment 
>
> How relevant is this? How/why does it happen?

This is very much relevant with the issue. This can happen when certain
packers/protectors move the phdrs into a separate PT_LOAD for the
purpose of anti-tamper/protection mechanisms. The patched binary will
run on real environment but not on QEMU (in most cases the binary will
segfault).

> Can you sign with a real name?

Sure, this is my very first patch on qemu, to continue with this I think
I need to send the patch again with my real name signed off?


>> ---
>>   linux-user/elfload.c | 21 ++++++++++++++++++++-
>>   linux-user/qemu.h    |  1 +
>>   2 files changed, 21 insertions(+), 1 deletion(-)
>> 
>> diff --git a/linux-user/elfload.c b/linux-user/elfload.c
>> index b05b8b0..8049c8a 100644
>> --- a/linux-user/elfload.c
>> +++ b/linux-user/elfload.c
>> @@ -699,7 +699,7 @@ static abi_ulong create_elf_tables(abi_ulong p, int argc, int envc,
>>       /* There must be exactly DLINFO_ITEMS entries here, or the assert
>>        * on info->auxv_len will trigger.
>>        */
>> -    NEW_AUX_ENT(AT_PHDR, (abi_ulong)(info->load_addr + exec->e_phoff));
>> +    NEW_AUX_ENT(AT_PHDR, (abi_ulong)(info->phdr_addr));
>>       NEW_AUX_ENT(AT_PHENT, (abi_ulong)(sizeof (struct elf_phdr)));
>>       NEW_AUX_ENT(AT_PHNUM, (abi_ulong)(exec->e_phnum));
>>       NEW_AUX_ENT(AT_PAGESZ, (abi_ulong)(TARGET_PAGE_SIZE));
>> @@ -1469,6 +1469,12 @@ static void load_elf_image(const char *image_name, const ImageSource *src,
>>       info->data_offset = load_bias;
>>       info->load_addr = load_addr;
>>       info->entry = ehdr->e_entry + load_bias;
>> +    /*
>> +     * Fallback for AT_PHDR if the program headers do not fall within
>> +     * any PT_LOAD segment (see the loop below, which overrides this with
>> +     * the correct in-memory address when a containing segment is found).
>> +     */
>> +    info->phdr_addr = load_addr + ehdr->e_phoff;
>>       info->start_code = -1;
>>       info->end_code = 0;
>>       info->start_data = -1;
>> @@ -1523,6 +1529,19 @@ static void load_elf_image(const char *image_name, const ImageSource *src,
>>               vaddr_ef = vaddr + eppnt->p_filesz;
>>               vaddr_em = vaddr + eppnt->p_memsz;
>>   
>> +            /*
>> +             * If this segment contains the program headers, record their
>> +             * in-memory address for AT_PHDR. This matches the kernel, which
>> +             * locates the headers via the containing PT_LOAD rather than
>> +             * assuming load_addr + e_phoff (false when the phdrs are not
>> +             * mapped 1:1 from file offset 0, e.g. relocated into their own
>> +             * segment by a binary patcher).
>> +             */
>> +            if (eppnt->p_offset <= ehdr->e_phoff &&
>> +                ehdr->e_phoff < eppnt->p_offset + eppnt->p_filesz) {
>> +                info->phdr_addr = vaddr + (ehdr->e_phoff - eppnt->p_offset);
>> +            }
>> +
>>               /*
>>                * Some segments may be completely empty, with a non-zero p_memsz
>>                * but no backing file segment.
>> diff --git a/linux-user/qemu.h b/linux-user/qemu.h
>> index 07fe801..2268493 100644
>> --- a/linux-user/qemu.h
>> +++ b/linux-user/qemu.h
>> @@ -26,6 +26,7 @@
>>   struct image_info {
>>           abi_ulong       load_bias;
>>           abi_ulong       load_addr;
>> +        abi_ulong       phdr_addr;
>>           abi_ulong       start_code;
>>           abi_ulong       end_code;
>>           abi_ulong       start_data;



^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-06-17 12:42 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-13 15:21 [PATCH] linux-user: Fix AT_PHDR when program headers are relocated into their own segment valium007
2026-06-15 16:06 ` Helge Deller
2026-06-15 17:33   ` Pierrick Bouvier
2026-06-17 12:41   ` valium

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.