NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

kern/59359: static pies are broken



>Number:         59359
>Category:       kern
>Synopsis:       static pies are broken
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sat Apr 26 15:25:00 +0000 2025
>Originator:     Taylor R Campbell
>Release:        current, 10, 9, ...
>Organization:
The StaticBSD Pie-in-the-sky
>Environment:
>Description:
$ cat hello.c
#include <stdio.h>

int
main(void)
{
	printf("Hello, world!\n");
	fflush(stdout);
	return ferror(stdout);
}
$ make hello.o hello DBG=-g\ -O2\ -Wall\ -Werror\ -fpie LDFLAGS=-pie\ -static
cc -g -O2 -Wall -Werror -fpie   -c hello.c -o hello.o
cc -g -O2 -Wall -Werror -fpie  -pie -static  -o hello hello.c 
$ ./hello
[1]   Segmentation fault (core dumped) ./hello

Same deal on mips and riscv.  Need to test and review on other platforms.  Presumably this happens because nothing relocates the GOT in static executables.

On mips, it crashes inside ___start (crt0-common.c) as soon as it tries to read something out of the GOT:

(gdb) x/i $pc
=> 0x2345c <___start+52>:       ld      s2,-32720(gp)
(gdb) print &_GLOBAL_OFFSET_TABLE_
$6 = (<variable (not text or data), no debug info> *) 0xf53f0
(gdb) print $gp - 0x7ff0
$8 = 0xe53f0

Note that these are off by 0x10000, the page size.  And so is $t9 (called procedure) versus the ___start symbol itself:

(gdb) print ___start
$9 = {void (void (*)(void), struct ps_strings *)} 0x23428 <___start>
(gdb) print $t9
$10 = 0x13428

But somehow control has reached the ___start symbol!

The executable itself has a LOAD command at 0, as shown by `readelf -l', which is rounded up by the kernel to the requested segment alignment, 0x10000:

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000001c0 0x00000000000001c0  R      0x8
  ABIFLAGS       0x0000000000000230 0x0000000000000230 0x0000000000000230
                 0x0000000000000018 0x0000000000000018  R      0x8
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000cd45c 0x00000000000cd45c  R E    0x10000
  LOAD           0x00000000000cd460 0x00000000000dd460 0x00000000000dd460
                 0x0000000000009bc8 0x0000000000118700  RW     0x10000
  DYNAMIC        0x00000000000024a8 0x00000000000024a8 0x00000000000024a8
                 0x00000000000001e0 0x00000000000001e0  R      0x8
  NOTE           0x0000000000000200 0x0000000000000200 0x0000000000000200
                 0x000000000000002c 0x000000000000002c  R      0x4
  TLS            0x00000000000cd460 0x00000000000dd460 0x00000000000dd460
                 0x0000000000000a68 0x0000000000000a71  R      0x8
  NULL           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000         0x8

    128 static int
    129 elf_placedynexec(struct exec_package *epp, Elf_Ehdr *eh, Elf_Phdr *ph)
    130 {
    131 	Elf_Addr align, offset;
    132 	int i;
    133 
    134 	for (align = 1, i = 0; i < eh->e_phnum; i++)
    135 		if (ph[i].p_type == PT_LOAD && ph[i].p_align > align)
    136 			align = ph[i].p_align;
    137 
    138 	offset = (Elf_Addr)pax_aslr_exec_offset(epp, align);
    139 	if (offset < epp->ep_vm_minaddr)
    140 		offset = roundup(epp->ep_vm_minaddr, align);
    141 	if ((offset & (align - 1)) != 0) {
    142 		DPRINTF("bad offset=%#jx align=%#jx",
    143 		    (uintmax_t)offset, (uintmax_t)align);
    144 		return SET_ERROR(EINVAL);
    145 	}
    146 
    147 	for (i = 0; i < eh->e_phnum; i++)
    148 		ph[i].p_vaddr += offset;
    149 	epp->ep_entryoffset = offset;
    150 	eh->e_entry += offset;
    151 	return 0;
    152 }

https://nxr.netbsd.org/xref/src/sys/kern/exec_elf.c?r=1.107#128

How did control successfully transfer from __start (crt0.S) to ___start (crt0-common.c) with the wrong address in t9, when it works via jump to register t9?

     55 	PTR_L	t9,%call16(_C_LABEL(___start))(gp)
     56 	 move	a1, a3		/* ps_strings */
     57 	.reloc  1f,R_MIPS_JALR,___start
     58 1:	jr	t9

https://nxr.netbsd.org/xref/src/lib/csu/arch/mips/crt0.S?r=1.4#55

Disassembling crt0.o reveals why -- the assembler/linker has cleverly replaced  jr t9  by  b ___start:

0000000000013390 <__start>:
__start():
   13390:       03807825        move    t3,gp
   13394:       3c1c000e        lui     gp,0xe
   13398:       279ca050        addiu   gp,gp,-24496
   1339c:       0399e02d        daddu   gp,gp,t9
   133a0:       00a02025        move    a0,a1
   133a4:       df998020        ld      t9,-32736(gp)
   133a8:       00e02825        move    a1,a3
   133ac:       1000001e        b       13428 <___start>
   133b0:       00000000        nop

So the global offset table content is off by 0x10000, the amount by which the kernel adjusted the LOAD commands to avoid the zero page.  If I set a breakpoint on __start, gdb confirms this:

(gdb) disas
Dump of assembler code for function _start:
   0x0000000000023390 <+0>:     move    t3,gp
   0x0000000000023394 <+4>:     lui     gp,0xe
   0x0000000000023398 <+8>:     addiu   gp,gp,-24496
   0x000000000002339c <+12>:    daddu   gp,gp,t9
=> 0x00000000000233a0 <+16>:    move    a0,a1
   0x00000000000233a4 <+20>:    ld      t9,-32736(gp)
   0x00000000000233a8 <+24>:    move    a1,a3
   0x00000000000233ac <+28>:    b       0x23428 <___start>
   0x00000000000233b0 <+32>:    nop
End of assembler dump.
(gdb) print $gp
$5 = 0xfd3e0
(gdb) print &_GLOBAL_OFFSET_TABLE_
$6 = (<variable (not text or data), no debug info> *) 0xf53f0
(gdb) print $gp - 0x7ff0
$7 = 0xf53f0
(gdb) x/xg $gp - 32736
0xf5400:        0x0000000000013428
(gdb) print ___start
$8 = {void (void (*)(void), struct ps_strings *)} 0x23428 <___start>

Presumably there is nothing relocating the GOT like _rtld_start/_rtld_relocate_nonplt_self does on mips.  Likely the same issue on Alpha, but with explicit relocations instead of hard-coded logic to relocate each GOT entry; however, gdb is having more trouble on alpha, so I haven't confirmed this.

Guessing that in order to make this work, we need some CSU routine to do essentially the logic of _rtld_relocate_nonplt_self and relocate the GOT/.rel.dyn/.rela.dyn.
>How-To-Repeat:
as above
>Fix:
Yes, please!



Home | Main Index | Thread Index | Old Index