RFC: Verify AT_ENTRY before using it

Daniel Jacobowitz
2010-02-24T22:49:30Z

I have a lot of occasions to run programs with a non-default dynamic
loader. Typically, I do this by taking advantage of gdbserver.
In one window, "gdbserver :PORT /path/to/this/loader /path/to/binary".
In the other, "gdb /path/to/binary" and "target remote :PORT".

Starting with the merge of PIE support, this blows up. GDB tries to
relocate BINARY by the auxv AT_ENTRY value, but AT_ENTRY describes the
loader, not the binary.

I chose to fix this by a program header comparison. We can fetch
program headers from AT_PHDRS / AT_PHENT / AT_PHNUM, or from the BFD.
If the program headers in both places are the same, the odds are very
good that AT_ENTRY (which will match AT_PHDRS - the kernel handles
that) belongs to the binary we think it does. If not, give up on
auxv-based relocation.

There's a short-circuit check so that the extra memory read can
be skipped for non-PIE.

I also have a local executable that acts as a proxy dynamic loader.
That triggers the same scenario.

I've tested this on x86_64, where it unbreaks the ld.so scenario
described above. I'll test it with our proxy loader on ARM tomorrow;
that machine is currently down for maintenance.

Any comments, or shall I commit this?

--
Daniel Jacobowitz
CodeSourcery

2010-02-24 Daniel Jacobowitz <dan@codesourcery.com>

* solib-svr4.c (read_program_header): Support type == -1 to read
all program headers.
(bfd_read_program_headers): New function.
(svr4_exec_displacement): Verify that AT_ENTRY is associated with
exec_bfd.

Index: solib-svr4.c
===================================================================
RCS file: /cvs/src/src/gdb/solib-svr4.c,v
retrieving revision 1.125
diff -u -p -r1.125 solib-svr4.c
--- solib-svr4.c 24 Feb 2010 00:29:02 -0000 1.125
+++ solib-svr4.c 24 Feb 2010 22:41:35 -0000
@@ -451,6 +451,9 @@ bfd_lookup_symbol (bfd *abfd, char *symn
/* Read program header TYPE from inferior memory. The header is found
by scanning the OS auxillary vector.

+ If TYPE == -1, return the program headers instead of the contents of
+ one program header.
+
Return a pointer to allocated memory holding the program header contents,
or NULL on failure. If sucessful, and unless P_SECT_SIZE is NULL, the
size of those contents is returned to P_SECT_SIZE. Likewise, the target
@@ -483,8 +486,13 @@ read_program_header (int type, int *p_se
else
return 0;

- /* Find .dynamic section via the PT_DYNAMIC PHDR. */
- if (arch_size == 32)
+ /* Find the requested segment. */
+ if (type == -1)
+ {
+ sect_addr = at_phdr;
+ sect_size = at_phent * at_phnum;
+ }
+ else if (arch_size == 32)
{
Elf32_External_Phdr phdr;
int i;
@@ -1669,6 +1677,29 @@ svr4_static_exec_displacement (void)
return 0;
}

+static gdb_byte *
+bfd_read_program_headers (bfd *abfd, int *phdrs_size)
+{
+ Elf_Internal_Ehdr *ehdr;
+ gdb_byte *buf;
+
+ ehdr = elf_elfheader (abfd);
+
+ *phdrs_size = ehdr->e_phnum * ehdr->e_phentsize;
+ if (*phdrs_size == 0)
+ return NULL;
+
+ buf = xmalloc (*phdrs_size);
+ if (bfd_seek (abfd, ehdr->e_phoff, SEEK_SET) != 0
+ || bfd_bread (buf, *phdrs_size, abfd) != *phdrs_size)
+ {
+ xfree (buf);
+ return NULL;
+ }
+
+ return buf;
+}
+
/* We relocate all of the sections by the same amount. This
behavior is mandated by recent editions of the System V ABI.
According to the System V Application Binary Interface,
@@ -1702,7 +1733,36 @@ svr4_exec_displacement (void)
return 0;

if (target_auxv_search (&current_target, AT_ENTRY, &entry_point) == 1)
- return entry_point - bfd_get_start_address (exec_bfd);
+ {
+ /* Verify that the auxilliary vector describes the same file as
+ exec_bfd, by comparing their program headers. If the program
+ headers in the auxilliary vector do not match the program
+ headers in the executable, then we are looking at a different
+ file than the one used by the kernel - for instance, "gdb
+ program" connected to "gdbserver :PORT ld.so program". */
+ int phdrs_size, phdrs2_size, ok = 0;
+ gdb_byte *buf, *buf2;
+
+ /* Take a shortcut for the common case. If the entry addresses
+ match, then it is incredibly unlikely that anything
+ complicated has happened. It's not impossible, if the loader
+ and executable are both PIE, but it would still require a
+ rare conjunction of load addresses. */
+ if (entry_point == bfd_get_start_address (exec_bfd))
+ return 0;
+
+ buf = read_program_header (-1, &phdrs_size, NULL);
+ buf2 = bfd_read_program_headers (exec_bfd, &phdrs2_size);
+ if (buf != NULL && buf2 != NULL
+ && phdrs_size == phdrs2_size
+ && memcmp (buf, buf2, phdrs_size) == 0)
+ ok = 1;
+ xfree (buf);
+ xfree (buf2);
+
+ if (ok)
+ return entry_point - bfd_get_start_address (exec_bfd);
+ }

return svr4_static_exec_displacement ();
}