Skip to content

glibc-compat,uclibc-compat: dual-ABI mmap shim (fix musl-caller EINVAL)#2119

Merged
widgetii merged 1 commit into
OpenIPC:masterfrom
widgetii:fix/glibc-compat-static-dual-abi-mmap
May 21, 2026
Merged

glibc-compat,uclibc-compat: dual-ABI mmap shim (fix musl-caller EINVAL)#2119
widgetii merged 1 commit into
OpenIPC:masterfrom
widgetii:fix/glibc-compat-static-dual-abi-mmap

Conversation

@widgetii
Copy link
Copy Markdown
Member

Summary

The static mmap shims from #2000 (uclibc) and #2110 (glibc) use the signature void *mmap(..., uint32_t offset). That's correct for vendor blobs built with a 32-bit off_t but silently breaks for musl-built callers that get linked into the same executable. ARM EABI places the 6th mmap arg in different stack slots depending on off_t width:

offset slot
vendor 32-bit off_t SP+4 (right after fd)
musl 64-bit off_t SP+8 (after 4 bytes of alignment padding at SP+4..7)

A uint32_t offset formal reads SP+4 — for musl callers that's uninitialised stack padding, so the kernel rejects the resulting pgoff with EINVAL.

Symptom

Invisible for minimal consumers like majestic (which only delegates into libmpi.so); bites the moment a consumer mixes vendor MPP with a non-trivial musl-static library that issues mmap from user code. The canonical reproducer is a small dvr_home demo that statically links LVGL — HI_MPI_VENC_CreateChn succeeds (vendor path, shim works as intended), but lv_linux_fbdev fails to mmap /dev/fb0:

Error: failed to map framebuffer device to memory: Invalid argument

even though hifb_demo's bare-mmap path in the exact same rootfs works fine — because hifb_demo doesn't link the static shim and goes through musl's real mmap.

Bisected by dropping an inline probe inside lv_hifb_disp_init that opens /dev/fb0 and calls mmap(0, smem_len, ..., fd, 0):

shim version probe result
before this PR mmap FAILED errno=22 (Invalid argument)
after this PR mmap OK at 0x407d9000

VENC bring-up (HI_MPI_VENC_CreateGroup / CreateChn / RegisterChn) all stay at 0x0 on all four channels — vendor path is unaffected.

Fix

Declare mmap/mmap64 variadic and va_arg-peek both candidate slots (SP+4 and SP+8) as uint32_tuint32_t avoids the 8-byte alignment that va_arg(ap, uint64_t) would apply on ARM EABI — 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 PA at SP+4 the heuristic still picks SP+4.

Failure mode: uninit padding happens to be page-aligned by coincidence (~1/4096 calls). The kernel still validates the resulting mapping, so worst case is a one-off EINVAL at startup — never silent corruption.

Applied identically to both shims; the stat() translators in uclibc-compat-static.c are untouched.

Test plan

  • make BOARD=hi3520dv200_lite builds clean (cross-compile shim, link into demo binary).
  • On lab DVR with patched binary: HI_MPI_VENC_CreateGroup/CreateChn/RegisterChn all return 0x0 for chns 0..3 (vendor path regression check).
  • On lab DVR with patched binary: LVGL lv_linux_fbdev_create successfully mmaps /dev/fb0 (the original bug).
  • CI: full toolchain matrix rebuild after merge (will need a follow-up workflow-dispatch since the toolchain release-asset names are stable; see glibc-compat: add static-link mmap shim (32-bit off_t for vendor V2 blobs) #2110 discussion).
  • Cross-platform smoke: hi3516cv100 still boots (uclibc-compat-static.a is hi3516cv100's primary consumer).

Refs

🤖 Generated with Claude Code

The static mmap shims from OpenIPC#2000 (uclibc) and OpenIPC#2110 (glibc) use the
signature `void *mmap(..., uint32_t offset)`. That's correct for vendor
blobs built with a 32-bit off_t but silently breaks for musl-built
callers that get linked into the same executable. ARM EABI places the
6th mmap arg in different stack slots depending on off_t width:

  vendor 32-bit off_t: offset at SP+4 (slot right after `fd`)
  musl   64-bit off_t: SP+4..7 is 8-byte alignment padding,
                       offset_lo at SP+8, offset_hi at SP+12

A `uint32_t offset` formal reads SP+4 — for musl callers that's
uninitialised stack padding, so the kernel rejects the resulting pgoff
with EINVAL.

Invisible for minimal consumers like majestic (which only delegates to
libmpi); bites the moment a consumer mixes vendor MPP with a non-
trivial musl-static library that issues mmap from user code. The
canonical reproducer is a dvr_home demo that statically links LVGL —
HI_MPI_VENC_CreateChn succeeds (vendor path) but lv_linux_fbdev fails
to mmap /dev/fb0 ("failed to map framebuffer device to memory:
Invalid argument") even though hifb_demo's bare-mmap path in the
exact same rootfs works fine.

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 — 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 PA at SP+4 the heuristic still picks SP+4.

Failure mode: uninit padding happens to be page-aligned by coincidence
(~1/4096 calls). Kernel still validates the resulting mapping, so
worst case is a one-off EINVAL at startup — never silent corruption.

Applied identically to both shims; the stat() translators in
uclibc-compat-static.c are untouched.

Refs: OpenIPC#1992 (audit), OpenIPC#1993 (cv100 stat shim), OpenIPC#2000 (cv100 mmap split),
OpenIPC#2110 (V2 SoC port). Bisected against dvr_home on hi3520dv200; PROBE
inside lv_hifb_disp_init showed mmap(/dev/fb0, ..., 0) returning
EINVAL with the old shim and the same call returning a valid mapping
once the dual-ABI peek was in place.
@widgetii widgetii merged commit c5c28c2 into OpenIPC:master May 21, 2026
93 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant