diff --git a/general/package/glibc-compat/src/glibc-compat-static.c b/general/package/glibc-compat/src/glibc-compat-static.c index 4177697046..cd4b6f5998 100644 --- a/general/package/glibc-compat/src/glibc-compat-static.c +++ b/general/package/glibc-compat/src/glibc-compat-static.c @@ -16,11 +16,44 @@ * musl's mmap process-wide, breaking musl's own internal mmap callers * (malloc, dlopen, ...). * + * Dual-ABI peek (subtle): + * + * The static lib lives in the executable, so it intercepts mmap() calls + * from BOTH the vendor blob (glibc 32-bit off_t) AND from musl-built code + * that gets linked into the same binary (e.g. LVGL's lv_linux_fbdev, or + * any static library that issues mmap from user code). ARM EABI places the + * 6th arg differently depending on off_t width: + * + * glibc 32-bit off_t: offset at SP+4 (the slot right after `fd`) + * musl 64-bit off_t: SP+4..7 is alignment padding, offset_lo at SP+8 + * + * A `void *mmap(..., uint32_t offset)` signature reads SP+4. For musl + * callers that slot holds uninitialised stack — garbage — and the kernel + * rejects the resulting pgoff with EINVAL. The bug is invisible for + * minimal vendor-only consumers (which is what PR #2110 tested) but bites + * the moment a consumer mixes vendor MPP with non-trivial musl-static + * code: e.g. a dvr_home demo that statically links LVGL fails to mmap + * /dev/fb0 even though the bare-mmap path in a simpler binary works. + * + * Fix: declare mmap/mmap64 variadic, va_arg-peek BOTH candidate slots + * (SP+4 and SP+8) as uint32_t — uint32_t avoids the 8-byte alignment that + * va_arg(ap, uint64_t) would apply on ARM — then pick the slot whose + * value is page-aligned. For the common offset=0 case from either ABI it + * resolves to 0 cleanly; for vendor MMZ mmaps with a non-zero page-aligned + * physical address at SP+4 the heuristic still picks SP+4. The only + * failure mode is musl's uninit-padding happening to be page-aligned by + * coincidence (~1/4096 calls) — and that only matters if the musl caller + * ALSO has offset=0 and the picked SP+4 value happens to be a valid + * unrelated mapping (much rarer); for any value the kernel still validates, + * so worst case is EINVAL on a single startup call, never silent corruption. + * * Pattern mirrors OpenIPC/firmware#2000 (uclibc-compat-static for the - * hi3516cv100 vendor blobs). + * hi3516cv100 vendor blobs); see also PR #2110 for the glibc V2 SoC port + * and PR #1993 for the audit tool that finds these mismatches statically. */ #define _GNU_SOURCE +#include #include #include #include @@ -30,12 +63,33 @@ #define SYS_mmap2 192 #endif -void *mmap(void *addr, size_t len, int prot, int flags, int fd, uint32_t offset) +static uint32_t pick_offset(uint32_t slot_a, uint32_t slot_b) +{ + int a_pa = (slot_a & 0xFFF) == 0; + int b_pa = (slot_b & 0xFFF) == 0; + if (a_pa && !b_pa) return slot_a; + if (b_pa) return slot_b; + return slot_a; +} + +void *mmap(void *addr, size_t len, int prot, int flags, int fd, ...) { + va_list ap; + va_start(ap, fd); + uint32_t slot_a = va_arg(ap, uint32_t); /* SP+4 — vendor ABI */ + uint32_t slot_b = va_arg(ap, uint32_t); /* SP+8 — musl ABI low */ + va_end(ap); + uint32_t offset = pick_offset(slot_a, slot_b); return (void *)syscall(SYS_mmap2, addr, len, prot, flags, fd, offset >> 12); } -void *mmap64(void *addr, size_t len, int prot, int flags, int fd, uint32_t offset) +void *mmap64(void *addr, size_t len, int prot, int flags, int fd, ...) { + va_list ap; + va_start(ap, fd); + uint32_t slot_a = va_arg(ap, uint32_t); + uint32_t slot_b = va_arg(ap, uint32_t); + va_end(ap); + uint32_t offset = pick_offset(slot_a, slot_b); return (void *)syscall(SYS_mmap2, addr, len, prot, flags, fd, offset >> 12); } diff --git a/general/package/uclibc-compat/src/uclibc-compat-static.c b/general/package/uclibc-compat/src/uclibc-compat-static.c index a52068ac94..cb0b7825cf 100644 --- a/general/package/uclibc-compat/src/uclibc-compat-static.c +++ b/general/package/uclibc-compat/src/uclibc-compat-static.c @@ -16,6 +16,7 @@ * See: https://github.com/OpenIPC/firmware/issues/1992 */ +#include #include #include #include @@ -65,24 +66,63 @@ int lstat64(const char *p, void *b) { return lstat(p, b); } int fstat64(int fd, void *b) { return fstat(fd, b); } /* ====================================================================== - * mmap -- off_t width mismatch + * mmap -- off_t width mismatch (dual-ABI peek) * * uclibc: offset is uint32_t (4 bytes, page-aligned) * musl: offset is off_t (8 bytes) * - * On ARM, this changes the calling convention. Bypass with raw syscall. + * On ARM EABI the 6th arg lands in different stack slots per off_t width: + * uclibc 32-bit: offset at SP+4 + * musl 64-bit: SP+4..7 is alignment padding, offset_lo at SP+8 + * + * The static lib intercepts mmap() for both ABIs since it sits in the + * consumer executable. A plain `uint32_t offset` signature reads SP+4 — + * fine for the uclibc vendor blob, but musl-built static code in the + * same binary (e.g. LVGL fbdev) gets uninitialised padding from SP+4 and + * the kernel rejects with EINVAL. Symptom: lv_linux_fbdev fails to mmap + * /dev/fb0 even though a simpler bare-mmap binary in the same rootfs + * works fine. + * + * Fix: declare mmap/mmap64 variadic, va_arg-peek BOTH candidate slots + * (SP+4 and SP+8) as uint32_t, pick the page-aligned one. For + * offset=0 from either ABI this resolves to 0 cleanly; for vendor MMZ + * mmaps with a non-zero page-aligned PA at SP+4 the heuristic still + * picks SP+4. Failure mode: uninit padding accidentally page-aligned + * (~1/4096 calls); kernel still validates, so worst case is a one-off + * EINVAL, never silent corruption. * ====================================================================== */ #ifndef SYS_mmap2 #define SYS_mmap2 192 #endif -void *mmap(void *addr, size_t len, int prot, int flags, int fd, uint32_t offset) +static uint32_t pick_offset(uint32_t slot_a, uint32_t slot_b) +{ + int a_pa = (slot_a & 0xFFF) == 0; + int b_pa = (slot_b & 0xFFF) == 0; + if (a_pa && !b_pa) return slot_a; + if (b_pa) return slot_b; + return slot_a; +} + +void *mmap(void *addr, size_t len, int prot, int flags, int fd, ...) { + va_list ap; + va_start(ap, fd); + uint32_t slot_a = va_arg(ap, uint32_t); /* SP+4 — vendor ABI */ + uint32_t slot_b = va_arg(ap, uint32_t); /* SP+8 — musl ABI low */ + va_end(ap); + uint32_t offset = pick_offset(slot_a, slot_b); return (void *)syscall(SYS_mmap2, addr, len, prot, flags, fd, offset >> 12); } -void *mmap64(void *addr, size_t len, int prot, int flags, int fd, uint32_t offset) +void *mmap64(void *addr, size_t len, int prot, int flags, int fd, ...) { + va_list ap; + va_start(ap, fd); + uint32_t slot_a = va_arg(ap, uint32_t); + uint32_t slot_b = va_arg(ap, uint32_t); + va_end(ap); + uint32_t offset = pick_offset(slot_a, slot_b); return (void *)syscall(SYS_mmap2, addr, len, prot, flags, fd, offset >> 12); }