Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <20190813145437.lgmli3uutqvvkx3y@gmail.com>
Date: Tue, 13 Aug 2019 14:54:37 +0000
From: Fangrui Song <i@...kray.me>
To: Samuel Holland <samuel@...lland.org>
Cc: musl@...ts.openwall.com
Subject: Re: [PATCH] add support for powerpc/powerpc64 unaligned
 relocations

On 2019-06-30, Samuel Holland wrote:
>R_PPC_UADDR32 (R_PPC64_UADDR64) has the same meaning as R_PPC_ADDR32
>(R_PPC64_ADDR64), except that its address need not be aligned. For
>powerpc64, BFD ld(1) will automatically convert between ADDR<->UADDR
>relocations when the address is/isn't at its native alignment. This
>will happen if, for example, there is a pointer in a packed struct.
>
>gold and lld do not currently generate R_PPC64_UADDR64, but pass
>through misaligned R_PPC64_ADDR64 relocations from object files,
>possibly relaxing them to misaligned R_PPC64_RELATIVE. In both cases
>(relaxed or not) this violates the PSABI, which defines the relevant
>field type as "a 64-bit field occupying 8 bytes, the alignment of
>which is 8 bytes unless otherwise specified."
>
>All three linkers violate the PSABI on 32-bit powerpc, where the only
>difference is that the field is 32 bits wide, aligned to 4 bytes.
>
>Currently musl fails to load executables linked by BFD ld containing
>R_PPC64_UADDR64, with the error "unsupported relocation type 43".
>This change provides compatibility with BFD ld on powerpc64, and any
>static linker on either architecture that starts following the PSABI
>more closely.
>---
> arch/powerpc/reloc.h   | 1 +
> arch/powerpc64/reloc.h | 1 +
> ldso/dynlink.c         | 3 +++
> src/internal/dynlink.h | 1 +
> 4 files changed, 6 insertions(+)
>
>diff --git a/arch/powerpc/reloc.h b/arch/powerpc/reloc.h
>index 1b4cab36..527b6b7c 100644
>--- a/arch/powerpc/reloc.h
>+++ b/arch/powerpc/reloc.h
>@@ -9,6 +9,7 @@
> #define TPOFF_K (-0x7000)
>
> #define REL_SYMBOLIC    R_PPC_ADDR32
>+#define REL_USYMBOLIC   R_PPC_UADDR32
> #define REL_GOT         R_PPC_GLOB_DAT
> #define REL_PLT         R_PPC_JMP_SLOT
> #define REL_RELATIVE    R_PPC_RELATIVE
>diff --git a/arch/powerpc64/reloc.h b/arch/powerpc64/reloc.h
>index faf70acd..5bdaeede 100644
>--- a/arch/powerpc64/reloc.h
>+++ b/arch/powerpc64/reloc.h
>@@ -11,6 +11,7 @@
> #define TPOFF_K (-0x7000)
>
> #define REL_SYMBOLIC    R_PPC64_ADDR64
>+#define REL_USYMBOLIC   R_PPC64_UADDR64
> #define REL_GOT         R_PPC64_GLOB_DAT
> #define REL_PLT         R_PPC64_JMP_SLOT
> #define REL_RELATIVE    R_PPC64_RELATIVE
>diff --git a/ldso/dynlink.c b/ldso/dynlink.c
>index db543c19..b5ef4bfc 100644
>--- a/ldso/dynlink.c
>+++ b/ldso/dynlink.c
>@@ -407,6 +407,9 @@ static void do_relocs(struct dso *dso, size_t *rel, size_t rel_size, size_t stri
> 		case REL_PLT:
> 			*reloc_addr = sym_val + addend;
> 			break;
>+		case REL_USYMBOLIC:
>+			memcpy(reloc_addr, &(size_t){sym_val + addend}, sizeof(size_t));
>+			break;
> 		case REL_RELATIVE:
> 			*reloc_addr = (size_t)base + addend;
> 			break;
>diff --git a/src/internal/dynlink.h b/src/internal/dynlink.h
>index 165bbedb..ffd06b04 100644
>--- a/src/internal/dynlink.h
>+++ b/src/internal/dynlink.h
>@@ -28,6 +28,7 @@ typedef Elf64_Sym Sym;
> enum {
> 	REL_NONE = 0,
> 	REL_SYMBOLIC = -100,
>+	REL_USYMBOLIC,
> 	REL_GOT,
> 	REL_PLT,
> 	REL_RELATIVE,

This has been applied (commit 08869deb7efbda6e979886cb67e3d5843f92c2e8)
Writing something for archival purposes...


% cat d.c
char y; struct{char _; const char *x; char *y;} __attribute__((packed)) _ = {'_', "a", &y};
void _start() {}
# smaeul mentioned the real world case is https://github.com/FreeRDP/FreeRDP/blob/master/winpr/libwinpr/timezone/TimeZones.c

powerpc gas does not produce UADDR32. It just produces misaligned ADDR32.
For an initial relocation ADDR32 in a writable location, ld produces misaligned RELATIVE (nonpreemptable) or ADDR32 (preemptable).

% powerpc-linux-gnu-gcc -c -fpic d.c; readelf -Wr d.o | grep R_PPC
00000001  00000501 R_PPC_ADDR32           00000000   .rodata + 0
00000005  00000901 R_PPC_ADDR32           00000001   y + 0
% powerpc-linux-gnu-ld -pie d.o -o d; readelf -Wr d | grep R_PPC
00020001  00000016 R_PPC_RELATIVE                    1b8
00020005  00000016 R_PPC_RELATIVE                    2001c
% powerpc-linux-gnu-ld -shared d.o -o d.so; readelf -Wr d.so | grep R_PPC
00020001  00000016 R_PPC_RELATIVE                    1c4
00020005  00000301 R_PPC_ADDR32           0002001c   y + 0

I hacked llvm-mc to produce UADDR32. powerpc ld will create dynamic UADDR32. It doesn't optimize aligned UADDR32 to ADDR32. Dynamic UADDR32 is a case that a STT_SECTION STB_LOCAL symbol may be exported into .dynsym

Relocation section '.rela.dyn' at offset 0x178 contains 2 entries:
 Offset     Info    Type                Sym. Value  Symbol's Name + Addend
 00020001  00000118 R_PPC_UADDR32          00000190   .rodata + 190
 00020005  00000118 R_PPC_UADDR32          00000190   .rodata + 2001c

nsz mentioned glibc/sysdeps/powerpc/powerpc32/dl-machine.h:elf_machine_rela handles relocations to STB_LOCAL differently.
Fortunately, if gas never emits initial UADDR32, ld will never emit outstanding UADDR32 and this piece of code will not run. (Ignoring r_addend seems a really broken behavior) The code was added in 2003 so probably nobody hit the issue.



powerpc64 gas does not produce UADDR64. It just produces misaligned ADDR64.
But ld is smart enough to convert misaligned ADDR64 to UADDR64, and aligned UADDR64 to ADDR64.

% powerpc64le-linux-gnu-gcc -c -fpic d.c; readelf -Wr d.o | grep R_PPC64
0000000000000001  0000000500000026 R_PPC64_ADDR64         0000000000000000 .rodata + 0
0000000000000009  0000000900000026 R_PPC64_ADDR64         0000000000000001 y + 0
% powerpc64le-linux-gnu-ld -pie d.o -o d; readelf -Wr d | grep R_PPC64
0000000000020001  000000010000002b R_PPC64_UADDR64        0000000000000298 .text + 28
0000000000020009  000000020000002b R_PPC64_UADDR64        0000000000020000 .data + 11
% powerpc64le-linux-gnu-ld -shared d.o -o d.so; readelf -Wr d.so | grep R_PPC64
0000000000020001  000000010000002b R_PPC64_UADDR64        0000000000000288 .text + 28
0000000000020009  000000040000002b R_PPC64_UADDR64        0000000000020011 y + 0

I don't know if on some older powerpc hardware misaligned store may trap but kernels will fix the trap:

FreeBSD: sys/powerpc/powerpc/trap.c
  case EXC_ALI:
    if (fix_unaligned(td, frame) != 0)

Linux: arch/powerpc/kernel/traps.c (controlled by prctl PR_SET_UNALIGN)
	if (!(current->thread.align_ctl & PR_UNALIGN_SIGBUS))
		fixed = fix_alignment(regs);


Misaligned ADDR violates ABIs.
32-bit psABI says: word32 Specifies a 32-bit bit-field taking up 4 bytes maintaining 4-byte alignment unless otherwise indicated.
ELFv2 ABI says: doubleword64 specifies a 64-bit field occupying 8 bytes, the alignment of which is 8 bytes unless otherwise specified.

In powerpc64 ld,
UADDR64 -> ADDR64 conversion is probably not relevant nowadays.
ADDR64 -> UADDR64 conversion makes outstanding relocation conform to the ABI but I am not sure if this is useful now.
(on powerpc64le (defaults to -mcpu=power8), `std` is generated as 64-bit store. The ABI ADDR64/UADDR64 distinction makes little sense.)

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.