|
|
Message-ID:
<DS4PR01MB9944536EE2DBC5877446EA3D5EEA692@DS4PR01MB994453.prod.exchangelabs.com>
Date: Tue, 3 Nov 2026 00:39:16 +0000
From: "Puranam, Ananta Srikar" <srikar.puranam@....edu>
To: "musl@...ts.openwall.com" <musl@...ts.openwall.com>
CC: "West, Emily" <emily.west1@....edu>, "Priya, Swarn" <swarn.priya@....edu>,
"Gao, Peng" <penggao@...edu>,
"Ammanaghatta Shivakumar, Basavesh"
<basavesh.shivakumar@....edu>
Subject: ldso: relocation table sizes can wrap around in do_relocs
Hi,
I'm Ananta Srikar Puranam, a PhD student at Washington State University. I'm reporting this bug on behalf of my team: Emily West, Dr. Swarn Priya, Dr. Peng Gao and Dr. Basavesh Ammanaghatta Shivakumar.
do_relocs and do_relr_relocs in ldso/dynlink.c (lines 407 and 568 at 7813e911) only stop when the remaining size is exactly zero:
for (; rel_size; rel+=stride, rel_size-=stride*sizeof(size_t))
for (; relr_size; relr++, relr_size-=sizeof(size_t))
If DT_RELASZ, DT_RELSZ, DT_PLTRELSZ or DT_RELRSZ is not a multiple of the entry size, the size wraps around to nearly SIZE_MAX and the loader keeps applying whatever follows the table as relocations until it faults. With DT_RELASZ = 1, gdb on master (b1efda5b) shows:
do_relocs (rel_size=18446744073709551569, stride=3)
at ldso/dynlink.c:431
reloc_all at ldso/dynlink.c:1423
dlopen at ldso/dynlink.c:2200
With DT_RELRSZ = 1 the fault is in do_relr_relocs at line 571.
With the same change glibc's loader stops at the end of the table; dlopen returns, and the library only fails later because its relocations were not applied. I'd expect musl to stop once less than one entry is left, or to reject the object.
Reproducer (musl 1.2.5, GNU ld 2.46, x86_64). setdyn.py overwrites one dynamic tag's value:
cat > lib.c <<'EOF'
int x = 42;
int *p = &x;
int get(void) { return *p; }
EOF
cat > main.c <<'EOF'
#include <dlfcn.h>
#include <stdio.h>
int main(int argc, char **argv) {
void *h = dlopen(argv[1], RTLD_NOW);
if (!h) { printf("dlopen: %s\n", dlerror()); return 1; }
int (*get)(void) = (int (*)(void))dlsym(h, "get");
printf("get() = %d\n", get());
return 0;
}
EOF
cat > setdyn.py <<'EOF'
import struct, sys
path, tag, val = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
b = bytearray(open(path, 'rb').read())
phoff, = struct.unpack_from('<Q', b, 32)
phnum, = struct.unpack_from('<H', b, 56)
for i in range(phnum):
o = phoff + 56 * i
if struct.unpack_from('<I', b, o)[0] == 2: # PT_DYNAMIC
d = struct.unpack_from('<Q', b, o + 8)[0]
while struct.unpack_from('<q', b, d)[0] not in (0, tag):
d += 16
struct.pack_into('<Q', b, d + 8, val)
open(path, 'wb').write(b)
EOF
musl-gcc -o main main.c
musl-gcc -shared -fPIC -o rela.so lib.c
musl-gcc -shared -fPIC -Wl,-z,pack-relative-relocs -o relr.so lib.c
# DT_RELASZ (8) = 1 and DT_RELRSZ (35) = 1
cp rela.so rela-bad.so; python3 setdyn.py rela-bad.so 8 1
cp relr.so relr-bad.so; python3 setdyn.py relr-bad.so 35 1
for f in rela rela-bad relr relr-bad; do ./main ./$f.so; done
Output:
get() = 42
Segmentation fault
get() = 42
Segmentation fault
Both crashes are inside ld-musl, before dlopen returns.
This is malformed input only; I'm not claiming a security issue.
The bug was found by an ELF loader fuzzer we are developing. I reproduced it by hand. This report was drafted with help from an AI assistant (Claude), and I reviewed and checked it before sending.
Thanks,
Srikar
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.