mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
* fix(preload): answer NETLINK_ROUTE queries inside a session ip(8) could not run in a session: the app's SELinux domain has no netlink_route_socket bind permission, so iproute2 failed at bind and never saw the link that getifaddrs() and /proc/net already expose. Serve NETLINK_ROUTE from the same etc/droiddeck-net snapshot: an AF_UNIX socketpair backs the netlink fd, link/addr/route dumps and route get are synthesised, writes return EPERM, and multicast subscriptions keep failing with EACCES so consumers that wait for link events keep their fallback. Replies carry the port id reported by getsockname() and recvmsg() rewrites the source address to AF_NETLINK/pid 0. iproute2's dump loop requires both before it accepts a message or NLMSG_DONE; when either is missing every message is skipped silently and the process blocks in recvmsg(). * fix(preload): keep socket() and send() semantics for emulated netlink * fix(preload): deliver netlink replies with the caller's blocking mode * fix(preload): preserve NETLINK_ROUTE protocol and fd semantics - Send ACKs for successful queries and honor NETLINK_CAP_ACK - Reject unsupported subscriptions and apply socket timeouts - Track socket identity and shared references across dup and close - Support kernel connect and rewrite recvfrom source addresses - Resolve local and connected routes before default gateways - Add netlink and iproute2 regression tests * fix(preload): handle netlink request limits and socket lifecycle - Collect large sendmsg requests without falling back to UNIX sockets - Reject oversized replies explicitly instead of returning partial results - Allocate unique port IDs and reject duplicate bindings - Honor socket flags and restore inherited sockets across exec - Validate socket types and report netlink socket identity - Replace the fixed socket table with a dynamic registry - Add regression tests for batching, port conflicts and exec inheritance * fix(preload): optimize netlink tracking and support link name queries - Add lock-free fd tracking to bypass netlink locks on ordinary sockets - Move descriptor restoration to initialization and explicit discovery paths - Support RTM_GETLINK lookups via IFLA_IFNAME with correct error codes - Add regression coverage for hot-path overhead and name-based queries * fix(preload): accept unterminated netlink interface names - Copy IFLA_IFNAME into a bounded, NUL-terminated buffer before lookup - Match kernel string length validation and ERANGE errors - Add regression coverage for unterminated names and boundary cases * fix(preload): pin peers for netlink replies * fix(preload): make autobind idempotent * fix(preload): isolate forked netlink cleanup * fix(preload): avoid interposed fd scans * perf(preload): skip PID checks for untracked fds * fix(preload): verify peer before replay --------- Co-authored-by: uubooster-team <uu.booster@service.netease.com>
411 lines
14 KiB
C
411 lines
14 KiB
C
#define _GNU_SOURCE
|
|
#include <arpa/inet.h>
|
|
#include <spawn.h>
|
|
#include <dlfcn.h>
|
|
#include <fcntl.h>
|
|
#include <linux/netlink.h>
|
|
#include <netinet/in.h>
|
|
#include <stdio.h>
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
static const char *net_dir(void) {
|
|
static char dir[256];
|
|
if (!dir[0]) {
|
|
const char *base = getenv("BL_SYSV_DIR");
|
|
snprintf(dir, sizeof(dir), "%s/net", base && *base ? base : "/dev/shm/wnsysv");
|
|
char parent[256];
|
|
snprintf(parent, sizeof(parent), "%s", dir);
|
|
char *slash = strrchr(parent, '/');
|
|
if (slash) {
|
|
*slash = '\0';
|
|
mkdir(parent, 0777);
|
|
}
|
|
mkdir(dir, 0777);
|
|
}
|
|
return dir;
|
|
}
|
|
|
|
static int loopback_family(const struct sockaddr *address, socklen_t length) {
|
|
if (address && address->sa_family == AF_INET && length >= sizeof(struct sockaddr_in)) {
|
|
return ((const struct sockaddr_in *)address)->sin_addr.s_addr == htonl(INADDR_LOOPBACK) ? AF_INET : 0;
|
|
}
|
|
if (address && address->sa_family == AF_INET6 && length >= sizeof(struct sockaddr_in6)) {
|
|
const struct in6_addr *ip = &((const struct sockaddr_in6 *)address)->sin6_addr;
|
|
if (IN6_IS_ADDR_LOOPBACK(ip)) return AF_INET6;
|
|
if (IN6_IS_ADDR_V4MAPPED(ip) && ip->s6_addr[12] == 127 && ip->s6_addr[13] == 0 &&
|
|
ip->s6_addr[14] == 0 && ip->s6_addr[15] == 1) return AF_INET;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void record_port(int fd, unsigned peer) {
|
|
struct sockaddr_storage ss;
|
|
socklen_t len = sizeof(ss);
|
|
if (getsockname(fd, (struct sockaddr *)&ss, &len) != 0) {
|
|
return;
|
|
}
|
|
int family = loopback_family((struct sockaddr *)&ss, len);
|
|
if (!family) return;
|
|
unsigned port;
|
|
if (ss.ss_family == AF_INET) {
|
|
struct sockaddr_in *in = (struct sockaddr_in *)&ss;
|
|
port = ntohs(in->sin_port);
|
|
} else if (ss.ss_family == AF_INET6) {
|
|
port = ntohs(((struct sockaddr_in6 *)&ss)->sin6_port);
|
|
} else {
|
|
return;
|
|
}
|
|
if (port == 0) {
|
|
return;
|
|
}
|
|
struct stat socket_stat;
|
|
if (fstat(fd, &socket_stat) != 0) return;
|
|
char path[300];
|
|
snprintf(path, sizeof(path), "%s/p%u", net_dir(), port);
|
|
char staged[360];
|
|
snprintf(staged, sizeof(staged), "%s.tmp.%d.%d", path, (int)getpid(), fd);
|
|
char line[128];
|
|
int n = snprintf(line, sizeof(line), "%d %d %d %u %d %llu %d\n", (int)getpid(), (int)getppid(),
|
|
(int)getuid(), peer, fd, (unsigned long long)socket_stat.st_ino, family);
|
|
int out = open(staged, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0600);
|
|
if (out >= 0) {
|
|
int complete = write(out, line, n) == n;
|
|
close(out);
|
|
if (complete) rename(staged, path);
|
|
unlink(staged);
|
|
}
|
|
}
|
|
|
|
static unsigned addr_port(const struct sockaddr *addr, socklen_t len) {
|
|
if (addr && addr->sa_family == AF_INET && len >= sizeof(struct sockaddr_in)) {
|
|
return ntohs(((const struct sockaddr_in *)addr)->sin_port);
|
|
}
|
|
if (addr && addr->sa_family == AF_INET6 && len >= sizeof(struct sockaddr_in6)) {
|
|
return ntohs(((const struct sockaddr_in6 *)addr)->sin6_port);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static unsigned peer_port(int fd) {
|
|
struct sockaddr_storage ps;
|
|
socklen_t plen = sizeof(ps);
|
|
if (getpeername(fd, (struct sockaddr *)&ps, &plen) != 0) {
|
|
return 0;
|
|
}
|
|
return addr_port((struct sockaddr *)&ps, plen);
|
|
}
|
|
|
|
typedef int (*bind_fn)(int, const struct sockaddr *, socklen_t);
|
|
typedef int (*listen_fn)(int, int);
|
|
typedef int (*connect_fn)(int, const struct sockaddr *, socklen_t);
|
|
typedef int (*accept4_fn)(int, struct sockaddr *, socklen_t *, int);
|
|
static unsigned guarded_port(void);
|
|
|
|
__attribute__((visibility("hidden"))) int bl_udevmon_stand_in(int fd);
|
|
__attribute__((visibility("hidden"))) int bl_nl_bind(int fd, const struct sockaddr *addr,
|
|
socklen_t len, int *rc);
|
|
__attribute__((visibility("hidden"))) int bl_nl_connect(int fd, const struct sockaddr *addr,
|
|
socklen_t len, int *rc);
|
|
|
|
int bind(int fd, const struct sockaddr *addr, socklen_t len) {
|
|
static bind_fn real;
|
|
int nl_rc;
|
|
if (!real) real = (bind_fn)dlsym(RTLD_NEXT, "bind");
|
|
if (bl_nl_bind(fd, addr, len, &nl_rc)) return nl_rc;
|
|
if (bl_udevmon_stand_in(fd)) {
|
|
if (addr == NULL || len < sizeof(struct sockaddr_nl) || addr->sa_family != AF_NETLINK) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
struct sockaddr_storage local;
|
|
unsigned guarded = guarded_port();
|
|
if (guarded && addr_port(addr, len) == guarded && len <= sizeof(local)) {
|
|
memcpy(&local, addr, len);
|
|
if (addr->sa_family == AF_INET) {
|
|
((struct sockaddr_in *)&local)->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
addr = (struct sockaddr *)&local;
|
|
} else if (addr->sa_family == AF_INET6) {
|
|
((struct sockaddr_in6 *)&local)->sin6_addr = in6addr_loopback;
|
|
addr = (struct sockaddr *)&local;
|
|
}
|
|
}
|
|
int ret = real(fd, addr, len);
|
|
if (ret == 0) record_port(fd, 0);
|
|
return ret;
|
|
}
|
|
|
|
int listen(int fd, int backlog) {
|
|
static listen_fn real;
|
|
if (!real) real = (listen_fn)dlsym(RTLD_NEXT, "listen");
|
|
int ret = real(fd, backlog);
|
|
if (ret == 0) record_port(fd, 0);
|
|
return ret;
|
|
}
|
|
|
|
static unsigned local_port(int fd) {
|
|
struct sockaddr_storage ss;
|
|
socklen_t len = sizeof(ss);
|
|
if (getsockname(fd, (struct sockaddr *)&ss, &len) != 0) {
|
|
return 0;
|
|
}
|
|
return addr_port((struct sockaddr *)&ss, len);
|
|
}
|
|
|
|
static void bind_before_loopback_connect(int fd, const struct sockaddr *addr, socklen_t len) {
|
|
if (!loopback_family(addr, len)) return;
|
|
struct sockaddr_storage ss;
|
|
socklen_t slen = sizeof(ss);
|
|
if (getsockname(fd, (struct sockaddr *)&ss, &slen) != 0 || ss.ss_family != addr->sa_family ||
|
|
addr_port((struct sockaddr *)&ss, slen) != 0) {
|
|
return;
|
|
}
|
|
struct sockaddr_storage local;
|
|
if (len > sizeof(local)) return;
|
|
memcpy(&local, addr, len);
|
|
if (addr->sa_family == AF_INET) ((struct sockaddr_in *)&local)->sin_port = 0;
|
|
else ((struct sockaddr_in6 *)&local)->sin6_port = 0;
|
|
int saved = errno;
|
|
bind(fd, (struct sockaddr *)&local, len);
|
|
errno = saved;
|
|
}
|
|
|
|
static unsigned guarded_port(void) {
|
|
static int port = -1;
|
|
if (port < 0) {
|
|
const char *value = getenv("BL_CDP_GUARD");
|
|
port = value && *value ? atoi(value) : 0;
|
|
if (port < 0 || port > 65535) port = 0;
|
|
}
|
|
return (unsigned)port;
|
|
}
|
|
|
|
static int guest_peer(int fd) {
|
|
struct sockaddr_storage address;
|
|
socklen_t length = sizeof(address);
|
|
if (getpeername(fd, (struct sockaddr *)&address, &length) != 0) return 0;
|
|
int family = loopback_family((struct sockaddr *)&address, length);
|
|
if (!family) return 0;
|
|
unsigned peer = peer_port(fd);
|
|
if (peer == 0) {
|
|
return 0;
|
|
}
|
|
char path[300];
|
|
snprintf(path, sizeof(path), "%s/p%u", net_dir(), peer);
|
|
struct stat st;
|
|
if (stat(path, &st) != 0 || time(NULL) - st.st_mtime > 30) {
|
|
return 0;
|
|
}
|
|
int in = open(path, O_RDONLY);
|
|
if (in < 0) {
|
|
return 0;
|
|
}
|
|
char buf[128];
|
|
int n = read(in, buf, sizeof(buf) - 1);
|
|
close(in);
|
|
if (n <= 0) {
|
|
return 0;
|
|
}
|
|
buf[n] = '\0';
|
|
int pid, parent, uid, descriptor, recorded_family;
|
|
unsigned destination;
|
|
unsigned long long inode;
|
|
if (sscanf(buf, "%d %d %d %u %d %llu %d", &pid, &parent, &uid, &destination, &descriptor,
|
|
&inode, &recorded_family) != 7 || pid <= 0 || descriptor < 0 || destination != guarded_port() ||
|
|
recorded_family != family) return 0;
|
|
snprintf(path, sizeof(path), "/proc/%d/fd/%d", pid, descriptor);
|
|
return stat(path, &st) == 0 && S_ISSOCK(st.st_mode) && (unsigned long long)st.st_ino == inode;
|
|
}
|
|
|
|
int connect(int fd, const struct sockaddr *addr, socklen_t len) {
|
|
static connect_fn real;
|
|
int nl_rc;
|
|
if (bl_nl_connect(fd, addr, len, &nl_rc)) return nl_rc;
|
|
if (!real) real = (connect_fn)dlsym(RTLD_NEXT, "connect");
|
|
bind_before_loopback_connect(fd, addr, len);
|
|
unsigned peer = loopback_family(addr, len) ? addr_port(addr, len) : 0;
|
|
if (peer) record_port(fd, peer);
|
|
int ret = real(fd, addr, len);
|
|
if (ret == 0 || errno == EINPROGRESS) {
|
|
int saved = errno;
|
|
record_port(fd, peer);
|
|
errno = saved;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int accept4(int fd, struct sockaddr *addr, socklen_t *len, int flags) {
|
|
static accept4_fn real;
|
|
if (!real) real = (accept4_fn)dlsym(RTLD_NEXT, "accept4");
|
|
for (;;) {
|
|
int ret = real(fd, addr, len, flags);
|
|
if (ret < 0) return ret;
|
|
unsigned guarded = guarded_port();
|
|
if (guarded && local_port(ret) == guarded && !guest_peer(ret)) {
|
|
close(ret);
|
|
continue;
|
|
}
|
|
record_port(ret, peer_port(ret));
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
int accept(int fd, struct sockaddr *addr, socklen_t *len) {
|
|
return accept4(fd, addr, len, 0);
|
|
}
|
|
|
|
extern char **environ;
|
|
typedef int (*spawn_fn)(pid_t *, const char *, const posix_spawn_file_actions_t *, const posix_spawnattr_t *,
|
|
char *const[], char *const[]);
|
|
__attribute__((visibility("hidden"))) void bl_binfmt_before(const char *path, char *const argv[], char *const envp[]);
|
|
__attribute__((visibility("hidden"))) int bl_binfmt_retry(const char *path, char *const argv[], char *const envp[], int error);
|
|
__attribute__((visibility("hidden"))) void bl_binfmt_before_search(const char *file, char *const argv[], char *const envp[]);
|
|
__attribute__((visibility("hidden"))) int bl_binfmt_after_search(const char *file, char *const argv[], char *const envp[], int error);
|
|
__attribute__((visibility("hidden"))) char *const *bl_webhelper_argv(const char *path, char *const argv[]);
|
|
__attribute__((visibility("hidden"))) int bl_binfmt_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *fa,
|
|
const posix_spawnattr_t *attr, char *const argv[], char *const envp[],
|
|
spawn_fn real);
|
|
|
|
static int maybe_answer_lsof(const char *path, char *const argv[]) {
|
|
const char *base = strrchr(path, '/');
|
|
base = base ? base + 1 : path;
|
|
if (strcmp(base, "lsof") != 0) {
|
|
return 0;
|
|
}
|
|
const char *port_str = NULL;
|
|
for (int i = 0; argv[i]; i++) {
|
|
const char *at = strstr(argv[i], "TCP@");
|
|
if (at) {
|
|
const char *colon = strrchr(at, ':');
|
|
if (colon) port_str = colon + 1;
|
|
}
|
|
}
|
|
if (!port_str) {
|
|
return 0;
|
|
}
|
|
char path_buf[300];
|
|
snprintf(path_buf, sizeof(path_buf), "%s/p%u", net_dir(), (unsigned)atoi(port_str));
|
|
int in = open(path_buf, O_RDONLY);
|
|
if (in < 0) {
|
|
_exit(1);
|
|
}
|
|
char buf[64];
|
|
int n = read(in, buf, sizeof(buf) - 1);
|
|
close(in);
|
|
if (n <= 0) {
|
|
_exit(1);
|
|
}
|
|
buf[n] = '\0';
|
|
int pid = 0, ppid = 0, uid = 0;
|
|
unsigned peer = 0;
|
|
sscanf(buf, "%d %d %d %u", &pid, &ppid, &uid, &peer);
|
|
char out[160];
|
|
int m = peer ? snprintf(out, sizeof(out), "p%d\nR%d\nu%d\nn127.0.0.1:%u->127.0.0.1:%u\n",
|
|
pid, ppid, uid, (unsigned)atoi(port_str), peer)
|
|
: snprintf(out, sizeof(out), "p%d\nR%d\nu%d\nn127.0.0.1:%u\n",
|
|
pid, ppid, uid, (unsigned)atoi(port_str));
|
|
if (write(1, out, m) != m) { }
|
|
_exit(0);
|
|
}
|
|
|
|
static char **web_helper_redirect(const char *path, char *const argv[], const char **launcher) {
|
|
const char *want = getenv("BL_X64_ARM_WEBHELPER");
|
|
if (!want || !*want || !path) return NULL;
|
|
const char *base = strrchr(path, '/');
|
|
if (strcmp(base ? base + 1 : path, "steamwebhelper") != 0) return NULL;
|
|
if (!strstr(path, "/steamrt64/")) {
|
|
char cwd[PATH_MAX];
|
|
size_t n;
|
|
if (path[0] == '/' || !getcwd(cwd, sizeof(cwd))) return NULL;
|
|
n = strlen(cwd);
|
|
if (n < 10 || strcmp(cwd + n - 10, "/steamrt64") != 0) return NULL;
|
|
}
|
|
int argc = 0;
|
|
while (argv && argv[argc]) argc++;
|
|
char **out = calloc((size_t)argc + 2, sizeof(char *));
|
|
if (!out) return NULL;
|
|
out[0] = (char *)want;
|
|
for (int i = 1; i < argc; i++) out[i] = argv[i];
|
|
*launcher = want;
|
|
return out;
|
|
}
|
|
|
|
int execve(const char *path, char *const argv_in[], char *const envp[]) {
|
|
char *const *argv = bl_webhelper_argv(path, argv_in);
|
|
static int (*real)(const char *, char *const[], char *const[]);
|
|
if (!real) real = (int (*)(const char *, char *const[], char *const[]))dlsym(RTLD_NEXT, "execve");
|
|
maybe_answer_lsof(path, argv);
|
|
const char *launcher = NULL;
|
|
char **redirected = web_helper_redirect(path, argv, &launcher);
|
|
if (redirected) {
|
|
real(launcher, redirected, envp);
|
|
free(redirected);
|
|
}
|
|
bl_binfmt_before(path, argv, envp);
|
|
real(path, argv, envp);
|
|
return bl_binfmt_retry(path, argv, envp, errno);
|
|
}
|
|
|
|
static int answer_lsof_via_spawn(const char *path, char *const argv[], pid_t *pid) {
|
|
pid_t child = fork();
|
|
if (child < 0) {
|
|
return errno;
|
|
}
|
|
if (child == 0) {
|
|
maybe_answer_lsof(path, argv);
|
|
_exit(127);
|
|
}
|
|
if (pid) {
|
|
*pid = child;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int execvp(const char *file, char *const argv_in[]) {
|
|
char *const *argv = bl_webhelper_argv(file, argv_in);
|
|
static int (*real)(const char *, char *const[]);
|
|
if (!real) real = (int (*)(const char *, char *const[]))dlsym(RTLD_NEXT, "execvp");
|
|
maybe_answer_lsof(file, argv);
|
|
bl_binfmt_before_search(file, argv, environ);
|
|
real(file, argv);
|
|
return bl_binfmt_after_search(file, argv, environ, errno);
|
|
}
|
|
|
|
int execv(const char *path, char *const argv_in[]) {
|
|
char *const *argv = bl_webhelper_argv(path, argv_in);
|
|
static int (*real)(const char *, char *const[]);
|
|
if (!real) real = (int (*)(const char *, char *const[]))dlsym(RTLD_NEXT, "execv");
|
|
maybe_answer_lsof(path, argv);
|
|
const char *launcher = NULL;
|
|
char **redirected = web_helper_redirect(path, argv, &launcher);
|
|
if (redirected) {
|
|
real(launcher, redirected);
|
|
free(redirected);
|
|
}
|
|
bl_binfmt_before(path, argv, environ);
|
|
real(path, argv);
|
|
return bl_binfmt_retry(path, argv, environ, errno);
|
|
}
|
|
|
|
int posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *fa,
|
|
const posix_spawnattr_t *attr, char *const argv[], char *const envp[]) {
|
|
const char *base = strrchr(path, '/');
|
|
base = base ? base + 1 : path;
|
|
if (strcmp(base, "lsof") == 0 && !fa) {
|
|
return answer_lsof_via_spawn(path, argv, pid);
|
|
}
|
|
static spawn_fn real;
|
|
if (!real) real = (spawn_fn)dlsym(RTLD_NEXT, "posix_spawn");
|
|
return bl_binfmt_spawn(pid, path, fa, attr, argv, envp, real);
|
|
}
|