From 31e393d48d59e51fd51a70d8191dd12f11d03aad Mon Sep 17 00:00:00 2001 From: Hyper-Fumetsu Date: Sun, 28 Jun 2026 13:11:04 +0000 Subject: [PATCH 01/13] Replace ashmem backend with memfd_create On newer Android devices (tested: Honor ALT-AN00, kernel 5.10.226, Android 14), the kernel has deprecated ashmem -- ioctl(/dev/ashmem, SET_SIZE) returns ENOTTY. This replaces the ashmem backend with memfd_create() + ftruncate(), preserving the identical System V API (shmget/shmat/shmdt/shmctl). Cross-process fd sharing via SCM_RIGHTS is unchanged. Changes: - ashmem_create_region() -> memfd_create_region() (memfd_create + ftruncate) - ashmem_get_size_region() -> memfd_get_size_region() (fstat) Tested with 10 stress tests (full shm lifecycle, 64MB large alloc, 100 concurrent segments, cross-process SCM_RIGHTS, 8-process concurrency, mmap, edge cases). All pass on real Honor device. --- shmem.c | 57 ++++++++++++++++++++++++++------------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/shmem.c b/shmem.c index f95fc07..9d675a7 100644 --- a/shmem.c +++ b/shmem.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include #include @@ -111,46 +113,39 @@ static int ancil_recv_fd(int sock) return ((int*) CMSG_DATA(cmsg))[0]; } -static int ashmem_get_size_region(int fd) +static int memfd_get_size_region(int fd) { -#if __ANDROID_API__ >= 26 - return ASharedMemory_getSize(fd); -#else - return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL)); -#endif + struct stat st; + if (fstat(fd, &st) < 0) return -1; + return (int)st.st_size; } /* - * ashmem_create_region - creates a new named ashmem region and returns the file + * memfd_create_region - creates a new named memfd region and returns the file * descriptor, or <0 on error. * - * `name' is the label to give the region (visible in /proc/pid/maps) + * memfd is anonymous memory (no /dev path, no filesystem footprint). + * Works on Android 10+ (API 29+) where memfd_create is available. + * + * `name' is the label to give the region (visible in /proc/self/fd/) * `size' is the size of the region, in page-aligned bytes */ -static int ashmem_create_region(char const* name, size_t size) +static int memfd_create_region(char const* name, size_t size) { -#if __ANDROID_API__ >= 26 - return ASharedMemory_create(name, size); -#else - // From https://android.googlesource.com/platform/system/core/+/master/libcutils/ashmem-dev.c - int fd = open("/dev/ashmem", O_RDWR); + // Use memfd_create instead of ashmem — ashmem SET_SIZE ioctl + // returns ENOTTY on Honor 5.10.226 (kernel has deprecated ashmem). + // + // memfd_create syscall is available on Android 10+ (API 29+), + // Honor ALT-AN00 runs Android 14 (API 34). + int fd = syscall(279, name, 1); // SYS_memfd_create = 279 on arm64, MFD_CLOEXEC=1 if (fd < 0) return fd; - char name_buffer[ASHMEM_NAME_LEN] = {0}; - strncpy(name_buffer, name, sizeof(name_buffer)); - name_buffer[sizeof(name_buffer)-1] = 0; - - int ret = ioctl(fd, ASHMEM_SET_NAME, name_buffer); - if (ret < 0) goto error; - - ret = ioctl(fd, ASHMEM_SET_SIZE, size); - if (ret < 0) goto error; + if (ftruncate(fd, (off_t)size) < 0) { + close(fd); + return -1; + } return fd; -error: - close(fd); - return ret; -#endif } static void ashv_check_pid() @@ -275,9 +270,9 @@ static int ashv_read_remote_segment(int shmid) } close(recvsock); - int size = ashmem_get_size_region(descriptor); + int size = memfd_get_size_region(descriptor); if (size == 0 || size == -1) { - DBG ("%s: ERROR: ashmem_get_size_region() returned %d on socket %s: %s", __PRETTY_FUNCTION__, size, addr.sun_path + 1, strerror(errno)); + DBG ("%s: ERROR: memfd_get_size_region() returned %d on socket %s: %s", __PRETTY_FUNCTION__, size, addr.sun_path + 1, strerror(errno)); return -1; } @@ -399,14 +394,14 @@ int shmget(key_t key, size_t size, int flags) shmem = realloc(shmem, shmem_amount * sizeof(shmem_t)); size = ROUND_UP(size, getpagesize()); shmem[idx].size = size; - shmem[idx].descriptor = ashmem_create_region(buf, size); + shmem[idx].descriptor = memfd_create_region(buf, size); shmem[idx].addr = NULL; shmem[idx].id = shmid; shmem[idx].markedForDeletion = false; shmem[idx].key = key; if (shmem[idx].descriptor < 0) { - DBG("%s: ashmem_create_region() failed for size %zu: %s", __PRETTY_FUNCTION__, size, strerror(errno)); + DBG("%s: memfd_create_region() failed for size %zu: %s", __PRETTY_FUNCTION__, size, strerror(errno)); shmem_amount --; shmem = realloc(shmem, shmem_amount * sizeof(shmem_t)); pthread_mutex_unlock (&mutex); From 48e2dc431e05edcb1972b232b437c78946ec9d4d Mon Sep 17 00:00:00 2001 From: Hyper-Fumetsu Date: Sun, 28 Jun 2026 13:24:51 +0000 Subject: [PATCH 02/13] Keep backward compatibility: memfd (API 30+) + ashmem (API <30) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three code paths, preserving all existing behavior: - API 30+ (Android 11+): memfd_create via syscall — fixes ENOTTY on Honor - API 26-29 (Android 8-10): ASharedMemory_create (NDK ashmem) — unchanged - API <26 (Android <8): open(/dev/ashmem) + ioctl (legacy) — unchanged No code removed — only one new code path added for API 30+. All 10 stress tests pass on real Honor hardware. --- shmem.c | 67 +++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/shmem.c b/shmem.c index 9d675a7..02c2c7e 100644 --- a/shmem.c +++ b/shmem.c @@ -113,39 +113,70 @@ static int ancil_recv_fd(int sock) return ((int*) CMSG_DATA(cmsg))[0]; } -static int memfd_get_size_region(int fd) +static int shmem_get_size_region(int fd) { +#if __ANDROID_API__ >= 30 + // memfd path: use fstat struct stat st; if (fstat(fd, &st) < 0) return -1; return (int)st.st_size; +#elif __ANDROID_API__ >= 26 + return ASharedMemory_getSize(fd); +#else + return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL)); +#endif } /* - * memfd_create_region - creates a new named memfd region and returns the file - * descriptor, or <0 on error. + * shmem_create_region - creates a new shared memory region and returns + * the file descriptor, or <0 on error. + * + * API 30+ (Android 11+): memfd_create via syscall. + * - No /dev/ashmem dependency (ashmem deprecated on newer kernels, + * e.g. Honor 5.10.226 returns ENOTTY from SET_SIZE). + * - Anonymous memory, no filesystem path. * - * memfd is anonymous memory (no /dev path, no filesystem footprint). - * Works on Android 10+ (API 29+) where memfd_create is available. + * API 26-29: ASharedMemory_create (Android NDK ashmem wrapper). + * API <26: open("/dev/ashmem") + ioctl (legacy). * - * `name' is the label to give the region (visible in /proc/self/fd/) - * `size' is the size of the region, in page-aligned bytes + * `name' is the label for the region. + * `size' is the region size, rounded up to page boundary by caller. */ -static int memfd_create_region(char const* name, size_t size) +static int shmem_create_region(char const* name, size_t size) { - // Use memfd_create instead of ashmem — ashmem SET_SIZE ioctl - // returns ENOTTY on Honor 5.10.226 (kernel has deprecated ashmem). - // - // memfd_create syscall is available on Android 10+ (API 29+), - // Honor ALT-AN00 runs Android 14 (API 34). - int fd = syscall(279, name, 1); // SYS_memfd_create = 279 on arm64, MFD_CLOEXEC=1 +#if __ANDROID_API__ >= 30 + // Use direct syscall for compatibility with older NDK versions + // that may not expose memfd_create wrapper. + int fd = syscall(279, name, 1); // SYS_memfd_create = 279 (arm64), MFD_CLOEXEC=1 if (fd < 0) return fd; if (ftruncate(fd, (off_t)size) < 0) { close(fd); return -1; } + return fd; +#elif __ANDROID_API__ >= 26 + return ASharedMemory_create(name, size); +#else + // Legacy ashmem path for Android < 8.0 (API < 26) + int fd = open("/dev/ashmem", O_RDWR); + if (fd < 0) return fd; + + char name_buffer[ASHMEM_NAME_LEN] = {0}; + strncpy(name_buffer, name, sizeof(name_buffer)); + name_buffer[sizeof(name_buffer)-1] = 0; + + int ret = ioctl(fd, ASHMEM_SET_NAME, name_buffer); + if (ret < 0) goto error; + + ret = ioctl(fd, ASHMEM_SET_SIZE, size); + if (ret < 0) goto error; return fd; +error: + close(fd); + return ret; +#endif } static void ashv_check_pid() @@ -270,9 +301,9 @@ static int ashv_read_remote_segment(int shmid) } close(recvsock); - int size = memfd_get_size_region(descriptor); + int size = shmem_get_size_region(descriptor); if (size == 0 || size == -1) { - DBG ("%s: ERROR: memfd_get_size_region() returned %d on socket %s: %s", __PRETTY_FUNCTION__, size, addr.sun_path + 1, strerror(errno)); + DBG ("%s: ERROR: shmem_get_size_region() returned %d on socket %s: %s", __PRETTY_FUNCTION__, size, addr.sun_path + 1, strerror(errno)); return -1; } @@ -394,14 +425,14 @@ int shmget(key_t key, size_t size, int flags) shmem = realloc(shmem, shmem_amount * sizeof(shmem_t)); size = ROUND_UP(size, getpagesize()); shmem[idx].size = size; - shmem[idx].descriptor = memfd_create_region(buf, size); + shmem[idx].descriptor = shmem_create_region(buf, size); shmem[idx].addr = NULL; shmem[idx].id = shmid; shmem[idx].markedForDeletion = false; shmem[idx].key = key; if (shmem[idx].descriptor < 0) { - DBG("%s: memfd_create_region() failed for size %zu: %s", __PRETTY_FUNCTION__, size, strerror(errno)); + DBG("%s: shmem_create_region() failed for size %zu: %s", __PRETTY_FUNCTION__, size, strerror(errno)); shmem_amount --; shmem = realloc(shmem, shmem_amount * sizeof(shmem_t)); pthread_mutex_unlock (&mutex); From 345ad9c0a0c648fd069f7acedc2387546e0099de Mon Sep 17 00:00:00 2001 From: Hyper-Fumetsu Date: Sun, 28 Jun 2026 13:26:57 +0000 Subject: [PATCH 03/13] =?UTF-8?q?Runtime=20fallback:=20memfd=20=E2=86=92?= =?UTF-8?q?=20ASharedMemory=20=E2=86=92=20/dev/ashmem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review feedback from twaik, replace compile-time #if with runtime probing. The backend order is: 1. memfd_create (syscall) — fastest, anonymous, no /dev dependency. Works on Honor 5.10.226 where ashmem returns ENOTTY. 2. ASharedMemory_create — Android NDK ashmem wrapper (API 26+). 3. /dev/ashmem — legacy ioctl path for older devices. get_size also tries fstat first (works for memfd and regular fds), falling back to ASharedMemory_getSize or ashmem ioctl. This handles all devices regardless of API level or kernel version. --- shmem.c | 89 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/shmem.c b/shmem.c index 02c2c7e..57ac2b1 100644 --- a/shmem.c +++ b/shmem.c @@ -113,69 +113,66 @@ static int ancil_recv_fd(int sock) return ((int*) CMSG_DATA(cmsg))[0]; } -static int shmem_get_size_region(int fd) -{ -#if __ANDROID_API__ >= 30 - // memfd path: use fstat - struct stat st; - if (fstat(fd, &st) < 0) return -1; - return (int)st.st_size; -#elif __ANDROID_API__ >= 26 - return ASharedMemory_getSize(fd); -#else - return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL)); -#endif -} - /* - * shmem_create_region - creates a new shared memory region and returns - * the file descriptor, or <0 on error. - * - * API 30+ (Android 11+): memfd_create via syscall. - * - No /dev/ashmem dependency (ashmem deprecated on newer kernels, - * e.g. Honor 5.10.226 returns ENOTTY from SET_SIZE). - * - Anonymous memory, no filesystem path. + * Probe backends at runtime from newest/preferred to oldest: + * 1. memfd_create (Linux 3.17+, anonymous, no FS dependency) + * 2. ASharedMemory (Android API 26+, NDK ashmem wrapper) + * 3. /dev/ashmem (legacy, deprecated on some kernels) * - * API 26-29: ASharedMemory_create (Android NDK ashmem wrapper). - * API <26: open("/dev/ashmem") + ioctl (legacy). - * - * `name' is the label for the region. - * `size' is the region size, rounded up to page boundary by caller. + * On newer devices (Honor kernel 5.10.226), ashmem SET_SIZE returns + * ENOTTY even though /dev/ashmem exists. Runtime fallback handles this. */ static int shmem_create_region(char const* name, size_t size) { -#if __ANDROID_API__ >= 30 - // Use direct syscall for compatibility with older NDK versions - // that may not expose memfd_create wrapper. - int fd = syscall(279, name, 1); // SYS_memfd_create = 279 (arm64), MFD_CLOEXEC=1 - if (fd < 0) return fd; - - if (ftruncate(fd, (off_t)size) < 0) { + int fd = -1; + + // 1) memfd_create — Linux 3.17+, available on most Android 10+ devices + // Use direct syscall for NDK compatibility. + fd = syscall(279, name, 1); // SYS_memfd_create=279 (arm64), MFD_CLOEXEC=1 + if (fd >= 0) { + if (ftruncate(fd, (off_t)size) == 0) + return fd; close(fd); - return -1; } - return fd; -#elif __ANDROID_API__ >= 26 - return ASharedMemory_create(name, size); -#else - // Legacy ashmem path for Android < 8.0 (API < 26) - int fd = open("/dev/ashmem", O_RDWR); + + // 2) ASharedMemory — Android 8+ (API 26+) +#if __ANDROID_API__ >= 26 + fd = ASharedMemory_create(name, size); + if (fd >= 0) + return fd; +#endif + + // 3) /dev/ashmem — legacy, may return ENOTTY on newer kernels + fd = open("/dev/ashmem", O_RDWR); if (fd < 0) return fd; char name_buffer[ASHMEM_NAME_LEN] = {0}; strncpy(name_buffer, name, sizeof(name_buffer)); name_buffer[sizeof(name_buffer)-1] = 0; - int ret = ioctl(fd, ASHMEM_SET_NAME, name_buffer); - if (ret < 0) goto error; - - ret = ioctl(fd, ASHMEM_SET_SIZE, size); - if (ret < 0) goto error; + if (ioctl(fd, ASHMEM_SET_NAME, name_buffer) < 0) + goto error; + if (ioctl(fd, ASHMEM_SET_SIZE, size) < 0) + goto error; return fd; error: close(fd); - return ret; + return -1; +} + +static int shmem_get_size_region(int fd) +{ + // Try fstat first — works for memfd and regular fds + struct stat st; + if (fstat(fd, &st) == 0) + return (int)st.st_size; + + // Fall back to ashmem-specific ioctl +#if __ANDROID_API__ >= 26 + return ASharedMemory_getSize(fd); +#else + return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL)); #endif } From a408bf9b59e534aec0397bdc17351406e6611c2b Mon Sep 17 00:00:00 2001 From: Hyper-Fumetsu Date: Sun, 28 Jun 2026 17:19:49 +0000 Subject: [PATCH 04/13] Use memfd_create() libc wrapper instead of hardcoded syscall 279 Per review: syscall numbers differ across CPU architectures. memfd_create() is available since Android API 30 and handles the correct syscall number per architecture automatically. --- shmem.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shmem.c b/shmem.c index 57ac2b1..5dab82b 100644 --- a/shmem.c +++ b/shmem.c @@ -9,7 +9,11 @@ #include #include #include +#define _GNU_SOURCE #include +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif #include #include #include @@ -128,7 +132,7 @@ static int shmem_create_region(char const* name, size_t size) // 1) memfd_create — Linux 3.17+, available on most Android 10+ devices // Use direct syscall for NDK compatibility. - fd = syscall(279, name, 1); // SYS_memfd_create=279 (arm64), MFD_CLOEXEC=1 + fd = memfd_create(name, MFD_CLOEXEC); if (fd >= 0) { if (ftruncate(fd, (off_t)size) == 0) return fd; From 38196876165ae2cc4870e93c10611815fd1eca1a Mon Sep 17 00:00:00 2001 From: Hyper-Fumetsu Date: Sun, 28 Jun 2026 18:15:06 +0000 Subject: [PATCH 05/13] Fix API 24 compat: use syscall(__NR_memfd_create) with arch fallback Per sylirre review: Termux targets API 24, and memfd_create() is only available from API 30. Use syscall() with __NR_memfd_create, defining per-architecture fallback numbers for older NDK versions. Arch numbers: aarch64: 279, arm: 385, x86_64: 319, i386: 356, riscv64: 286 --- shmem.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/shmem.c b/shmem.c index 5dab82b..0c3ca50 100644 --- a/shmem.c +++ b/shmem.c @@ -14,13 +14,37 @@ #ifndef MFD_CLOEXEC #define MFD_CLOEXEC 0x0001U #endif +#ifndef MFD_ALLOW_SEALING +#define MFD_ALLOW_SEALING 0x0002U +#endif #include #include #include +#include #include #include #include +/* + * __NR_memfd_create may not be defined in NDK headers for older API levels. + * Define per-architecture if missing. + */ +#ifndef __NR_memfd_create +#if defined(__aarch64__) +#define __NR_memfd_create 279 +#elif defined(__arm__) +#define __NR_memfd_create 385 +#elif defined(__x86_64__) +#define __NR_memfd_create 319 +#elif defined(__i386__) +#define __NR_memfd_create 356 +#elif defined(__riscv) && __riscv_xlen == 64 +#define __NR_memfd_create 286 +#else +#error "Unknown architecture: __NR_memfd_create not defined" +#endif +#endif + #if __ANDROID_API__ < 26 #define __u32 uint32_t #include @@ -132,7 +156,7 @@ static int shmem_create_region(char const* name, size_t size) // 1) memfd_create — Linux 3.17+, available on most Android 10+ devices // Use direct syscall for NDK compatibility. - fd = memfd_create(name, MFD_CLOEXEC); + fd = syscall(__NR_memfd_create, name, MFD_CLOEXEC); if (fd >= 0) { if (ftruncate(fd, (off_t)size) == 0) return fd; From 26196e6cc6ca8ce31244d605791658b1b7b8af20 Mon Sep 17 00:00:00 2001 From: Hyper-Fumetsu Date: Sun, 28 Jun 2026 19:03:02 +0000 Subject: [PATCH 06/13] Fix shmem_get_size_region: fstat returns st_size=0 for ashmem fds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On ashmem-backed fds, fstat() succeeds but st_size is always 0 because ashmem doesn't maintain i_size in the inode — the size is only accessible via ASHMEM_GET_SIZE ioctl. This caused ashv_read_remote_segment() to fail on devices that fall back to the ashmem path (kernel <3.17 or API <26). Add st.st_size > 0 check so ashmem fds correctly fall through to the backend-specific size query. --- shmem.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/shmem.c b/shmem.c index 0c3ca50..a00ba47 100644 --- a/shmem.c +++ b/shmem.c @@ -191,12 +191,14 @@ static int shmem_create_region(char const* name, size_t size) static int shmem_get_size_region(int fd) { - // Try fstat first — works for memfd and regular fds + // Try fstat first — works for memfd and regular fds. + // ashmem fds also succeed fstat but st_size is always 0 + // (ashmem doesn't maintain i_size, only accessible via ioctl). struct stat st; - if (fstat(fd, &st) == 0) + if (fstat(fd, &st) == 0 && st.st_size > 0) return (int)st.st_size; - // Fall back to ashmem-specific ioctl + // Fall back to backend-specific size query #if __ANDROID_API__ >= 26 return ASharedMemory_getSize(fd); #else From 408967969d69e53bd403dbe4068bab6c812ab1ab Mon Sep 17 00:00:00 2001 From: Hyper-Fumetsu Date: Sun, 28 Jun 2026 19:31:16 +0000 Subject: [PATCH 07/13] Use dlopen/dlsym for ASharedMemory instead of compile-time #if Replace #if __ANDROID_API__ >= 26 guards with runtime dlopen(libandroid.so) probing. This makes the backend selection truly runtime: even when the library is compiled at API 24 (as termux-packages does), it can use ASharedMemory on API 26+ devices. Changes: - Remove #include and its API level guard - Add #include - Probe ASharedMemory_create / ASharedMemory_getSize at runtime via dlsym - Remove #if __ANDROID_API__ < 26 guard on linux/ashmem.h (ioctl defs needed at all API levels for the legacy fallback path) Suggested by @twaik --- shmem.c | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/shmem.c b/shmem.c index a00ba47..5249756 100644 --- a/shmem.c +++ b/shmem.c @@ -1,7 +1,5 @@ #include -#if __ANDROID_API__ >= 26 -#include -#endif +#include #include #include #include @@ -45,10 +43,8 @@ #endif #endif -#if __ANDROID_API__ < 26 #define __u32 uint32_t #include -#endif #include "shm.h" @@ -163,12 +159,21 @@ static int shmem_create_region(char const* name, size_t size) close(fd); } - // 2) ASharedMemory — Android 8+ (API 26+) -#if __ANDROID_API__ >= 26 - fd = ASharedMemory_create(name, size); - if (fd >= 0) - return fd; -#endif + // 2) ASharedMemory — Android 8+ (API 26+), probed at runtime + // because the library may be compiled at API 24 but run on API 26+. + { + static int (*p_ASharedMemory_create)(const char*, size_t) = NULL; + static int probed = 0; + if (!probed) { + void *lib = dlopen("libandroid.so", RTLD_NOW); + if (lib) p_ASharedMemory_create = dlsym(lib, "ASharedMemory_create"); + probed = 1; + } + if (p_ASharedMemory_create) { + fd = p_ASharedMemory_create(name, size); + if (fd >= 0) return fd; + } + } // 3) /dev/ashmem — legacy, may return ENOTTY on newer kernels fd = open("/dev/ashmem", O_RDWR); @@ -199,11 +204,20 @@ static int shmem_get_size_region(int fd) return (int)st.st_size; // Fall back to backend-specific size query -#if __ANDROID_API__ >= 26 - return ASharedMemory_getSize(fd); -#else + { + static int (*p_ASharedMemory_getSize)(int) = NULL; + static int probed_size = 0; + if (!probed_size) { + void *lib = dlopen("libandroid.so", RTLD_NOW); + if (lib) p_ASharedMemory_getSize = dlsym(lib, "ASharedMemory_getSize"); + probed_size = 1; + } + if (p_ASharedMemory_getSize) { + int ret = p_ASharedMemory_getSize(fd); + if (ret > 0) return ret; + } + } return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL)); -#endif } static void ashv_check_pid() From fdaef26b27001f366fccfdb401229eb595106386 Mon Sep 17 00:00:00 2001 From: Hyper-Fumetsu Date: Sun, 28 Jun 2026 19:43:43 +0000 Subject: [PATCH 08/13] Replace dlopen with __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ Use weak-symbol mechanism (NDK r23+) for ASharedMemory instead of manual dlopen/dlsym. Link directly against libandroid; symbols unavailable at the target API level become weak references that resolve to NULL at runtime on older devices. - Define __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ 1 - Include android/sharedmem.h unconditionally - Add -landroid to test Makefile --- shmem.c | 36 +++++++++--------------------------- test/Makefile | 2 +- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/shmem.c b/shmem.c index 5249756..05c41b6 100644 --- a/shmem.c +++ b/shmem.c @@ -1,5 +1,6 @@ +#define __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ 1 #include -#include +#include #include #include #include @@ -159,20 +160,10 @@ static int shmem_create_region(char const* name, size_t size) close(fd); } - // 2) ASharedMemory — Android 8+ (API 26+), probed at runtime - // because the library may be compiled at API 24 but run on API 26+. - { - static int (*p_ASharedMemory_create)(const char*, size_t) = NULL; - static int probed = 0; - if (!probed) { - void *lib = dlopen("libandroid.so", RTLD_NOW); - if (lib) p_ASharedMemory_create = dlsym(lib, "ASharedMemory_create"); - probed = 1; - } - if (p_ASharedMemory_create) { - fd = p_ASharedMemory_create(name, size); - if (fd >= 0) return fd; - } + // 2) ASharedMemory — Android 8+ (API 26+), weak symbol + if (ASharedMemory_create) { + fd = ASharedMemory_create(name, size); + if (fd >= 0) return fd; } // 3) /dev/ashmem — legacy, may return ENOTTY on newer kernels @@ -204,18 +195,9 @@ static int shmem_get_size_region(int fd) return (int)st.st_size; // Fall back to backend-specific size query - { - static int (*p_ASharedMemory_getSize)(int) = NULL; - static int probed_size = 0; - if (!probed_size) { - void *lib = dlopen("libandroid.so", RTLD_NOW); - if (lib) p_ASharedMemory_getSize = dlsym(lib, "ASharedMemory_getSize"); - probed_size = 1; - } - if (p_ASharedMemory_getSize) { - int ret = p_ASharedMemory_getSize(fd); - if (ret > 0) return ret; - } + if (ASharedMemory_getSize) { + int ret = ASharedMemory_getSize(fd); + if (ret > 0) return ret; } return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL)); } diff --git a/test/Makefile b/test/Makefile index fbb54a2..1856a1b 100644 --- a/test/Makefile +++ b/test/Makefile @@ -3,7 +3,7 @@ CFLAGS += -Wall -Wextra -Werror -std=c99 OS := $(shell uname -o 2> /dev/null) ifeq ($(OS), Android) -CFLAGS += -I.. -llog +CFLAGS += -I.. -llog -landroid EXTRA_CFILE = ../shmem.c cleanup-memory: test ; else From 2872df7aafaa15ec509e481a361a79149c474e2e Mon Sep 17 00:00:00 2001 From: Hyper-Fumetsu Date: Sun, 28 Jun 2026 20:17:33 +0000 Subject: [PATCH 09/13] Drop custom __NR_memfd_create arch table, save errno on error - __NR_memfd_create is a kernel UAPI constant, always available in NDK regardless of __ANDROID_API__. Use the NDK builtin, fail at build time with #error if missing. - Preserve errno before close() in shmem_create_region error path so the caller sees the actual ioctl failure reason. --- shmem.c | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/shmem.c b/shmem.c index 05c41b6..603df56 100644 --- a/shmem.c +++ b/shmem.c @@ -24,24 +24,8 @@ #include #include -/* - * __NR_memfd_create may not be defined in NDK headers for older API levels. - * Define per-architecture if missing. - */ #ifndef __NR_memfd_create -#if defined(__aarch64__) -#define __NR_memfd_create 279 -#elif defined(__arm__) -#define __NR_memfd_create 385 -#elif defined(__x86_64__) -#define __NR_memfd_create 319 -#elif defined(__i386__) -#define __NR_memfd_create 356 -#elif defined(__riscv) && __riscv_xlen == 64 -#define __NR_memfd_create 286 -#else -#error "Unknown architecture: __NR_memfd_create not defined" -#endif +#error "__NR_memfd_create not defined" #endif #define __u32 uint32_t @@ -181,8 +165,12 @@ static int shmem_create_region(char const* name, size_t size) return fd; error: - close(fd); - return -1; + { + int save_errno = errno; + close(fd); + errno = save_errno; + return -1; + } } static int shmem_get_size_region(int fd) From a7e23ad43c4fbf4011573c1d56c65452bb88a2a3 Mon Sep 17 00:00:00 2001 From: Hyper-Fumetsu Date: Sun, 28 Jun 2026 20:22:53 +0000 Subject: [PATCH 10/13] Remove redundant MFD_* defines and device-specific comment - MFD_CLOEXEC/MFD_ALLOW_SEALING already defined in linux/memfd.h - Drop Honor ALT-AN00 mention from backend doc --- shmem.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/shmem.c b/shmem.c index 603df56..8e52bd7 100644 --- a/shmem.c +++ b/shmem.c @@ -10,12 +10,6 @@ #include #define _GNU_SOURCE #include -#ifndef MFD_CLOEXEC -#define MFD_CLOEXEC 0x0001U -#endif -#ifndef MFD_ALLOW_SEALING -#define MFD_ALLOW_SEALING 0x0002U -#endif #include #include #include @@ -124,12 +118,9 @@ static int ancil_recv_fd(int sock) /* * Probe backends at runtime from newest/preferred to oldest: - * 1. memfd_create (Linux 3.17+, anonymous, no FS dependency) - * 2. ASharedMemory (Android API 26+, NDK ashmem wrapper) - * 3. /dev/ashmem (legacy, deprecated on some kernels) - * - * On newer devices (Honor kernel 5.10.226), ashmem SET_SIZE returns - * ENOTTY even though /dev/ashmem exists. Runtime fallback handles this. + * 1. memfd_create (Linux 3.17+) + * 2. ASharedMemory (Android API 26+) + * 3. /dev/ashmem (legacy, deprecated on newer kernels) */ static int shmem_create_region(char const* name, size_t size) { From aae4e89b459ef3b92aa04e64850fb348f8ef0fe1 Mon Sep 17 00:00:00 2001 From: Hyper-Fumetsu Date: Sun, 28 Jun 2026 20:26:17 +0000 Subject: [PATCH 11/13] Add dma-buf heap and ion fallback backends - dma-buf heap (/dev/dma_heap/system): Android 10+, single ioctl - ion (/dev/ion): Android 4.0-9, ALLOC + SHARE ioctls - Restructure ashmem path to fall through on failure instead of returning immediately via goto error --- shmem.c | 66 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/shmem.c b/shmem.c index 8e52bd7..1d5082a 100644 --- a/shmem.c +++ b/shmem.c @@ -120,7 +120,9 @@ static int ancil_recv_fd(int sock) * Probe backends at runtime from newest/preferred to oldest: * 1. memfd_create (Linux 3.17+) * 2. ASharedMemory (Android API 26+) - * 3. /dev/ashmem (legacy, deprecated on newer kernels) + * 3. /dev/ashmem (legacy) + * 4. dma-buf heap (Android 10+, /dev/dma_heap/system) + * 5. ion (Android 4.0–9, /dev/ion) */ static int shmem_create_region(char const* name, size_t size) { @@ -143,25 +145,59 @@ static int shmem_create_region(char const* name, size_t size) // 3) /dev/ashmem — legacy, may return ENOTTY on newer kernels fd = open("/dev/ashmem", O_RDWR); - if (fd < 0) return fd; + if (fd >= 0) { + char name_buffer[ASHMEM_NAME_LEN] = {0}; + strncpy(name_buffer, name, sizeof(name_buffer)); + name_buffer[sizeof(name_buffer)-1] = 0; - char name_buffer[ASHMEM_NAME_LEN] = {0}; - strncpy(name_buffer, name, sizeof(name_buffer)); - name_buffer[sizeof(name_buffer)-1] = 0; + if (ioctl(fd, ASHMEM_SET_NAME, name_buffer) == 0 && + ioctl(fd, ASHMEM_SET_SIZE, size) == 0) + return fd; + close(fd); + } - if (ioctl(fd, ASHMEM_SET_NAME, name_buffer) < 0) - goto error; - if (ioctl(fd, ASHMEM_SET_SIZE, size) < 0) - goto error; + // 4) dma-buf heap — Android 10+ (/dev/dma_heap/system) + { + int heap_fd = open("/dev/dma_heap/system", O_RDWR | O_CLOEXEC); + if (heap_fd >= 0) { + struct dma_heap_allocation_data { + __u64 len; + __u32 fd; + __u32 fd_flags; + __u64 heap_flags; + } data = { .len = size, .fd_flags = O_RDWR | O_CLOEXEC }; + int ret = ioctl(heap_fd, _IOWR(0x48, 0x00, struct dma_heap_allocation_data), &data); + close(heap_fd); + if (ret == 0) return (int)data.fd; + } + } - return fd; -error: + // 5) ion — Android 4.0–9 (/dev/ion) { - int save_errno = errno; - close(fd); - errno = save_errno; - return -1; + int ion_fd = open("/dev/ion", O_RDWR); + if (ion_fd >= 0) { + struct ion_allocation_data { + __u64 len; + __u32 heap_id_mask; + __u32 flags; + __u32 handle; + __u32 unused; + } alloc = { .len = size, .heap_id_mask = 1 /*ION_HEAP_SYSTEM*/ }; + if (ioctl(ion_fd, _IOWR(0x49, 0x00, struct ion_allocation_data), &alloc) == 0) { + struct ion_fd_data { + __u32 handle; + __u32 fd; + } fd_data = { .handle = alloc.handle }; + if (ioctl(ion_fd, _IOWR(0x49, 0x08, struct ion_fd_data), &fd_data) == 0) { + close(ion_fd); + return (int)fd_data.fd; + } + } + close(ion_fd); + } } + + return -1; } static int shmem_get_size_region(int fd) From 6f02e698e56de5bf74bab6af975d1c61d2bd401c Mon Sep 17 00:00:00 2001 From: Hyper-Fumetsu Date: Sun, 28 Jun 2026 21:15:46 +0000 Subject: [PATCH 12/13] Remove non-functional dma_heap and ion backends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /dev/dma_heap/system: SELinux EACCES in untrusted_app domain - /dev/ion: ENOTTY — ioctl struct layout varies across kernel versions (size_t vs __u64, missing align field on some), same fragility as ashmem ioctls Tested by @sylirre with strace on real device. --- shmem.c | 43 ------------------------------------------- 1 file changed, 43 deletions(-) diff --git a/shmem.c b/shmem.c index 1d5082a..b6080af 100644 --- a/shmem.c +++ b/shmem.c @@ -121,8 +121,6 @@ static int ancil_recv_fd(int sock) * 1. memfd_create (Linux 3.17+) * 2. ASharedMemory (Android API 26+) * 3. /dev/ashmem (legacy) - * 4. dma-buf heap (Android 10+, /dev/dma_heap/system) - * 5. ion (Android 4.0–9, /dev/ion) */ static int shmem_create_region(char const* name, size_t size) { @@ -156,47 +154,6 @@ static int shmem_create_region(char const* name, size_t size) close(fd); } - // 4) dma-buf heap — Android 10+ (/dev/dma_heap/system) - { - int heap_fd = open("/dev/dma_heap/system", O_RDWR | O_CLOEXEC); - if (heap_fd >= 0) { - struct dma_heap_allocation_data { - __u64 len; - __u32 fd; - __u32 fd_flags; - __u64 heap_flags; - } data = { .len = size, .fd_flags = O_RDWR | O_CLOEXEC }; - int ret = ioctl(heap_fd, _IOWR(0x48, 0x00, struct dma_heap_allocation_data), &data); - close(heap_fd); - if (ret == 0) return (int)data.fd; - } - } - - // 5) ion — Android 4.0–9 (/dev/ion) - { - int ion_fd = open("/dev/ion", O_RDWR); - if (ion_fd >= 0) { - struct ion_allocation_data { - __u64 len; - __u32 heap_id_mask; - __u32 flags; - __u32 handle; - __u32 unused; - } alloc = { .len = size, .heap_id_mask = 1 /*ION_HEAP_SYSTEM*/ }; - if (ioctl(ion_fd, _IOWR(0x49, 0x00, struct ion_allocation_data), &alloc) == 0) { - struct ion_fd_data { - __u32 handle; - __u32 fd; - } fd_data = { .handle = alloc.handle }; - if (ioctl(ion_fd, _IOWR(0x49, 0x08, struct ion_fd_data), &fd_data) == 0) { - close(ion_fd); - return (int)fd_data.fd; - } - } - close(ion_fd); - } - } - return -1; } From 4cb1de1ed1b9e06209282b724875d4ff124191b4 Mon Sep 17 00:00:00 2001 From: Hyper-Fumetsu Date: Sun, 28 Jun 2026 22:48:13 +0000 Subject: [PATCH 13/13] =?UTF-8?q?Reorder=20backends:=20ASharedMemory=20?= =?UTF-8?q?=E2=86=92=20/dev/ashmem=20=E2=86=92=20memfd=5Fcreate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit memfd_create last because seccomp on Android 8-9 kills the process with SIGSYS instead of returning ENOSYS. ASharedMemory + /dev/ashmem already cover all real Android devices; memfd only reached on non-Android environments (termux-docker, PRoot). --- shmem.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/shmem.c b/shmem.c index b6080af..0f074f7 100644 --- a/shmem.c +++ b/shmem.c @@ -126,22 +126,14 @@ static int shmem_create_region(char const* name, size_t size) { int fd = -1; - // 1) memfd_create — Linux 3.17+, available on most Android 10+ devices - // Use direct syscall for NDK compatibility. - fd = syscall(__NR_memfd_create, name, MFD_CLOEXEC); - if (fd >= 0) { - if (ftruncate(fd, (off_t)size) == 0) - return fd; - close(fd); - } - - // 2) ASharedMemory — Android 8+ (API 26+), weak symbol + // 1) ASharedMemory — Android 8+ (API 26+), official NDK API. + // Weak symbol: resolves to NULL at runtime on older devices. if (ASharedMemory_create) { fd = ASharedMemory_create(name, size); if (fd >= 0) return fd; } - // 3) /dev/ashmem — legacy, may return ENOTTY on newer kernels + // 2) /dev/ashmem — legacy, exists since early Android. fd = open("/dev/ashmem", O_RDWR); if (fd >= 0) { char name_buffer[ASHMEM_NAME_LEN] = {0}; @@ -154,6 +146,17 @@ static int shmem_create_region(char const* name, size_t size) close(fd); } + // 3) memfd_create — Linux 3.17+. Last resort: seccomp on Android 8–9 + // may kill the process with SIGSYS instead of returning ENOSYS. + // Only reached when neither ASharedMemory nor /dev/ashmem exist + // (e.g. termux-docker / non-Android PRoot). + fd = syscall(__NR_memfd_create, name, MFD_CLOEXEC); + if (fd >= 0) { + if (ftruncate(fd, (off_t)size) == 0) + return fd; + close(fd); + } + return -1; }