Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ define BUNDLE_SDK
$$SDK_CC -shared -Wall -O2 -fPIC \
-o /tmp/sdk-overlay/$$SDK_TOP/sdk/lib/libuclibc-compat.so \
$$COMPAT_SRC; \
COMPAT_STATIC=$(PWD)/general/package/uclibc-compat/src/uclibc-compat-static.c; \
if [ -f "$$COMPAT_STATIC" ]; then \
SDK_AR=$$(echo $$SDK_CC | sed 's/-gcc$$/-ar/'); \
$$SDK_CC -Wall -O2 -fPIC -c \
-o /tmp/sdk-overlay/$$SDK_TOP/sdk/lib/uclibc-compat-static.o \
$$COMPAT_STATIC; \
$$SDK_AR rcs /tmp/sdk-overlay/$$SDK_TOP/sdk/lib/libuclibc-compat-static.a \
/tmp/sdk-overlay/$$SDK_TOP/sdk/lib/uclibc-compat-static.o; \
rm -f /tmp/sdk-overlay/$$SDK_TOP/sdk/lib/uclibc-compat-static.o; \
fi; \
fi; \
gunzip $$SDK_TGZ && \
tar rf $${SDK_TGZ%.tar.gz}.tar -C /tmp/sdk-overlay $$SDK_TOP && \
Expand Down
14 changes: 9 additions & 5 deletions general/package/uclibc-compat/src/Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
LIB := libuclibc-compat

CFLAGS ?= -O2
CFLAGS += -Wall -fPIC

all: $(LIB).so
all: libuclibc-compat.so libuclibc-compat-static.a

$(LIB).so: uclibc-compat.c
libuclibc-compat.so: uclibc-compat.c
$(CC) $(CFLAGS) -shared -o $@ $^

libuclibc-compat-static.a: uclibc-compat-static.o
$(AR) rcs $@ $^

uclibc-compat-static.o: uclibc-compat-static.c
$(CC) $(CFLAGS) -c -o $@ $<

clean:
rm -f $(LIB).so
rm -f *.o *.so *.a
88 changes: 88 additions & 0 deletions general/package/uclibc-compat/src/uclibc-compat-static.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* uclibc-compat-static.c -- ABI wrappers that MUST be statically linked
*
* These functions override musl's implementations for symbols that musl
* DOES export but with incompatible ABI (different struct sizes, different
* argument widths).
*
* They MUST be linked statically into the executable (-luclibc-compat-static)
* so they appear in the executable's symbol table. The dynamic linker then
* resolves vendor .so imports from the executable BEFORE musl.
*
* They MUST NOT be in a shared library (.so) because that would override
* musl's implementations process-wide, breaking musl's own internal calls
* and any code expecting musl's ABI.
*
* See: https://github.com/OpenIPC/firmware/issues/1992
*/

#include <stdint.h>
#include <stddef.h>
#include <unistd.h>
#include <sys/syscall.h>

/* ======================================================================
* stat/fstat/lstat -- struct stat ABI translation
*
* uclibc ARM stat64: 96 bytes (from kernel's struct stat64)
* musl ARM stat: 152 bytes
*
* The kernel fills struct stat64 directly via the fstatat64 syscall.
* Vendor binaries expect the 96-byte kernel layout, so we bypass musl
* entirely and syscall directly.
* ====================================================================== */

#ifndef __NR_fstatat64
#define __NR_fstatat64 327
#endif
#ifndef AT_FDCWD
#define AT_FDCWD (-100)
#endif
#ifndef AT_EMPTY_PATH
#define AT_EMPTY_PATH 0x1000
#endif
#ifndef AT_SYMLINK_NOFOLLOW
#define AT_SYMLINK_NOFOLLOW 0x100
#endif

int stat(const char *path, void *buf)
{
return syscall(__NR_fstatat64, AT_FDCWD, path, buf, 0);
}

int lstat(const char *path, void *buf)
{
return syscall(__NR_fstatat64, AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW);
}

int fstat(int fd, void *buf)
{
return syscall(__NR_fstatat64, fd, "", buf, AT_EMPTY_PATH);
}

int stat64(const char *p, void *b) { return stat(p, b); }
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
*
* 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.
* ====================================================================== */

#ifndef SYS_mmap2
#define SYS_mmap2 192
#endif

void *mmap(void *addr, size_t len, int prot, int flags, int fd, uint32_t offset)
{
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)
{
return (void *)syscall(SYS_mmap2, addr, len, prot, flags, fd, offset >> 12);
}
133 changes: 12 additions & 121 deletions general/package/uclibc-compat/src/uclibc-compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,18 @@
* uclibc-compat.c -- ABI compatibility shim for vendor binaries on musl
*
* OpenIPC ships vendor .so libraries compiled against uclibc (or old glibc)
* that must run on musl. This shim bridges the ABI gaps:
* that must run on musl. This shim provides symbols that are MISSING from
* musl -- symbols that vendor binaries import but musl does not export.
*
* - struct stat translation (uclibc stat64=96B vs musl=152B)
* - mmap off_t mismatch (uclibc=4B vs musl=8B)
* - Missing symbols (__ctype_b, __assert, __fgetc_unlocked, etc.)
* - C11 Annex K "safe" functions (memcpy_s, snprintf_s, etc.)
* IMPORTANT: This .so must NOT export symbols that musl already provides
* (stat, fstat, lstat, mmap, etc). Doing so would override musl's
* implementations process-wide, breaking all code that expects musl's
* ABI (64-bit off_t, 152-byte struct stat).
*
* HOW TO USE:
*
* Executables that load vendor .so files must link this library
* statically (-luclibc-compat) so its symbols appear in the
* executable's scope BEFORE musl's. The dynamic linker then
* resolves vendor imports from the executable first.
*
* Struct layouts are from:
* - uclibc: arch/arm/include/uapi/asm/stat.h (struct stat64)
* - musl: arch/arm/bits/stat.h
* - Verified by audit-vendor-abi.py struct probe
* For struct-passing functions where the ABI differs (stat, mmap), the
* fix must be in the executable via static linking (-luclibc-compat-static),
* so the override only affects vendor .so lookups through the executable's
* symbol scope, not musl's own internal calls.
*
* See: https://github.com/OpenIPC/firmware/issues/1992
*/
Expand All @@ -32,111 +26,10 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>

/* ======================================================================
* stat/fstat/lstat -- struct stat ABI translation
*
* uclibc ARM stat64: 96 bytes (from kernel's struct stat64)
* musl ARM stat: 152 bytes (from musl's bits/stat.h)
*
* We bypass both libc's stat() and go straight to the fstatat64
* syscall, then translate the kernel's result into uclibc layout.
* ====================================================================== */

struct uclibc_stat64 {
unsigned long long st_dev; /* 0 */
unsigned char __pad0[4]; /* 8 */
unsigned long __st_ino; /* 12 */
unsigned int st_mode; /* 16 */
unsigned int st_nlink; /* 20 */
unsigned long st_uid; /* 24 */
unsigned long st_gid; /* 28 */
unsigned long long st_rdev; /* 32 */
unsigned char __pad3[4]; /* 40 */
long long st_size; /* 44 */
unsigned long st_blksize; /* 52 */
unsigned long long st_blocks; /* 56 */
unsigned long st_atime_; /* 64 */
unsigned long st_atime_nsec; /* 68 */
unsigned long st_mtime_; /* 72 */
unsigned long st_mtime_nsec; /* 76 */
unsigned long st_ctime_; /* 80 */
unsigned long st_ctime_nsec; /* 84 */
unsigned long long st_ino; /* 88 */
}; /* 96 bytes */

/*
* The kernel fills struct stat64 (same as uclibc_stat64 above) directly
* via the fstatat64 syscall. No translation needed -- the kernel struct
* IS the uclibc struct.
*/

#ifndef __NR_fstatat64
#define __NR_fstatat64 327
#endif
#ifndef AT_FDCWD
#define AT_FDCWD (-100)
#endif
#ifndef AT_EMPTY_PATH
#define AT_EMPTY_PATH 0x1000
#endif
#ifndef AT_SYMLINK_NOFOLLOW
#define AT_SYMLINK_NOFOLLOW 0x100
#endif

__attribute__((visibility("default")))
int stat(const char *path, void *buf)
{
return syscall(__NR_fstatat64, AT_FDCWD, path, buf, 0);
}

__attribute__((visibility("default")))
int lstat(const char *path, void *buf)
{
return syscall(__NR_fstatat64, AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW);
}

__attribute__((visibility("default")))
int fstat(int fd, void *buf)
{
return syscall(__NR_fstatat64, fd, "", buf, AT_EMPTY_PATH);
}

/* stat64/fstat64/lstat64 -- same as above, uclibc uses these names */
__attribute__((visibility("default"))) int stat64(const char *p, void *b) { return stat(p, b); }
__attribute__((visibility("default"))) int lstat64(const char *p, void *b) { return lstat(p, b); }
__attribute__((visibility("default"))) int fstat64(int fd, void *b) { return fstat(fd, b); }

/* ======================================================================
* mmap -- off_t width mismatch
*
* uclibc: offset is uint32_t (4 bytes, page-aligned)
* musl: offset is off_t (8 bytes)
*
* On ARM, this changes the calling convention -- the offset argument
* occupies different registers. Bypass entirely with raw mmap2 syscall.
* Same approach as majestic's BROKEN_MMAP.
* ====================================================================== */

#ifndef SYS_mmap2
#define SYS_mmap2 192
#endif

__attribute__((visibility("default")))
void *mmap(void *addr, size_t len, int prot, int flags, int fd, uint32_t offset)
{
return (void *)syscall(SYS_mmap2, addr, len, prot, flags, fd, offset >> 12);
}

__attribute__((visibility("default")))
void *mmap64(void *addr, size_t len, int prot, int flags, int fd, uint32_t offset)
{
return (void *)syscall(SYS_mmap2, addr, len, prot, flags, fd, offset >> 12);
}

/* ======================================================================
* Missing uclibc/glibc symbols
* Missing uclibc/glibc symbols -- safe to export from .so because
* musl does NOT provide these.
* ====================================================================== */

/*
Expand All @@ -145,8 +38,6 @@ void *mmap64(void *addr, size_t len, int prot, int flags, int fd, uint32_t offse
* Vendor binaries that import __ctype_b will dereference wrong indirection.
*
* We provide a global pointer initialized to a static ASCII table.
* This covers the common case (vendor code doing isdigit/isalpha on
* file paths and configuration strings).
*/
static const unsigned short int __uclibc_ctype_b_data[384] = {
[0 ... 255] = 0,
Expand Down
1 change: 1 addition & 0 deletions general/package/uclibc-compat/uclibc-compat.mk
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ endef

define UCLIBC_COMPAT_INSTALL_STAGING_CMDS
$(INSTALL) -D -m 0755 $(UCLIBC_COMPAT_PKGDIR)/src/libuclibc-compat.so $(STAGING_DIR)/usr/lib/libuclibc-compat.so
$(INSTALL) -D -m 0644 $(UCLIBC_COMPAT_PKGDIR)/src/libuclibc-compat-static.a $(STAGING_DIR)/usr/lib/libuclibc-compat-static.a
endef

define UCLIBC_COMPAT_INSTALL_TARGET_CMDS
Expand Down
Loading