__nf_ct_delete_from_lists crash, with bisected guilty commit found

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

I'm attaching a test kernel module that reproduces this crash I'm seeing:

BUG: kernel NULL pointer dereference, address: 0000000000000000
[...]
[ 9120.221368] Call Trace: [ 9120.221530] <TASK>
[ 9120.221675]  nf_ct_delete.part.0+0xa9/0x220 [nf_conntrack]
[ 9120.222022]  nf_ct_delete+0x21/0x30 [nf_conntrack]
[ 9120.222334]  my_hook.cold+0x6b/0xbd [netfilter_postrouting]
[ 9120.222685]  nf_hook_slow+0x45/0xf0
[ 9120.222920]  ip_output+0x121/0x1b0
[ 9120.223145]  ? path_openat+0x534/0x10a0
[ 9120.223394]  ? ip_finish_output2+0x590/0x590
[ 9120.223668]  __ip_queue_xmit+0x557/0x5b0
[ 9120.223921]  ip_queue_xmit+0x15/0x20
[ 9120.224153]  __tcp_transmit_skb+0xae1/0xcb0
[ 9120.224444]  ? srso_alias_return_thunk+0x5/0x7f

Steps to reproduce:

1. Build the kernel module.
2. insmod it.
3. iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
4. Send a lot of data (I just use iperf -c <IP> -t 300).

It should crash immediately.

Maybe this is what you're trying to fix in "[PATCH nf 0/4] netfilter: conntrack: fix obscure confirmed race"?

Thanks,
Razvan
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/skbuff.h>
#include <net/netfilter/nf_conntrack.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Test Inc.");
MODULE_DESCRIPTION("Netfilter post routing hook");
MODULE_VERSION("1.0");

// Maximum number of skbs to keep in the list
#define MAX_SKB_LIST_SIZE 2000

static atomic_t wipe_flag = ATOMIC_INIT(0);

// Structure to hold skb copy in RCU list
struct skb_list_entry {
    struct list_head list;
    struct rcu_head rcu;
    struct sk_buff *skb_copy;
};

// RCU protected list head and control variables
static LIST_HEAD(skb_list);
static DEFINE_SPINLOCK(skb_list_lock);
static atomic_t skb_list_size = ATOMIC_INIT(0);
static atomic_t skb_copies_added = ATOMIC_INIT(0);

// RCU callback for freeing skb list entries
static void free_skb_entry_rcu(struct rcu_head *head)
{
    struct skb_list_entry *entry = container_of(head, struct skb_list_entry, rcu);

    if (entry->skb_copy)
        kfree_skb(entry->skb_copy);
    kfree(entry);
    atomic_dec(&skb_list_size);
}

// Function to add skb copy to RCU list
static int add_skb_to_list(struct sk_buff *skb)
{
    struct skb_list_entry *entry;
    struct skb_list_entry *oldest_entry;

    // Check if we need to remove old entries
    if (atomic_read(&skb_list_size) >= MAX_SKB_LIST_SIZE) {
        spin_lock_bh(&skb_list_lock);
        if (!list_empty(&skb_list)) {
            oldest_entry = list_first_entry(&skb_list, struct skb_list_entry, list);
            list_del_rcu(&oldest_entry->list);
            spin_unlock_bh(&skb_list_lock);
            call_rcu(&oldest_entry->rcu, free_skb_entry_rcu);
        } else {
            spin_unlock_bh(&skb_list_lock);
        }
    }

    // Allocate new entry
    entry = kmalloc(sizeof(struct skb_list_entry), GFP_ATOMIC);
    if (!entry) {
        printk(KERN_ERR "POST_ROUTING: Failed to allocate memory for skb entry\n");
        return -ENOMEM;
    }

    // Create skb copy
    entry->skb_copy = skb_copy(skb, GFP_ATOMIC);

    if (!entry->skb_copy) {
        kfree(entry);
        printk(KERN_ERR "POST_ROUTING: Failed to copy skb\n");
        return -ENOMEM;
    }

    // Initialize entry
    INIT_LIST_HEAD(&entry->list);

    // Add to RCU list
    spin_lock_bh(&skb_list_lock);
    list_add_tail_rcu(&entry->list, &skb_list);
    spin_unlock_bh(&skb_list_lock);

    atomic_inc(&skb_list_size);

    return 0;
}

// Function to cleanup all entries in the list
static void cleanup_skb_list(void)
{
    struct skb_list_entry *entry, *tmp;

    spin_lock_bh(&skb_list_lock);
    list_for_each_entry_safe(entry, tmp, &skb_list, list) {
        list_del_rcu(&entry->list);
        call_rcu(&entry->rcu, free_skb_entry_rcu);
    }
    spin_unlock_bh(&skb_list_lock);
}

/*
 * Wipe conntrack off of skb and delete it
 */
static inline void wipe_conntrack(struct sk_buff *skb)
{
    enum ip_conntrack_info ctinfo;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);

    if (ct) {
        printk("nf_ct_is_confirmed(): %d, ct: %p\n",
                nf_ct_is_confirmed(ct), ct);
        if (nf_ct_is_confirmed(ct))
            nf_ct_delete(ct, 0, 0);
        nf_reset_ct(skb);
    }
}

static void wipe_conntrack_from_skb_list(void)
{
    struct skb_list_entry *entry;

    int old = atomic_cmpxchg(&wipe_flag, 0, 1);

    if (old != 0)
        return;

    rcu_read_lock();
    list_for_each_entry_rcu(entry, &skb_list, list) {
        if (entry->skb_copy)
            wipe_conntrack(entry->skb_copy);
    }
    rcu_read_unlock();

    atomic_set(&wipe_flag, 0);
}

// Hook function prototype
static unsigned int my_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state);

// Netfilter hook operations structure
static struct nf_hook_ops post_routing_ops = {
    .hook = my_hook,
    .hooknum = NF_INET_POST_ROUTING,
    .pf = NFPROTO_IPV4,
    .priority = NF_IP_PRI_FIRST,
};

#define WIPE_THRESHOLD 500

// Hook function implementation
static unsigned int my_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
    // Add skb copy to RCU list
    if (add_skb_to_list(skb) != 0)
        printk(KERN_ERR "POST_ROUTING: Failed to add skb to list\n");

    if (atomic_inc_return(&skb_copies_added) % WIPE_THRESHOLD == 0) {
        printk("%d skb copies added, we'll wipe their conntracks\n", WIPE_THRESHOLD);
        wipe_conntrack_from_skb_list();
    }

    // Accept the packet (let it continue)
    return NF_ACCEPT;
}

// Module initialization function
static int __init netfilter_post_routing_init(void)
{
    int ret;
    
    printk(KERN_INFO "Netfilter post routing hook module loaded\n");
    
    // Register the hook
    ret = nf_register_net_hook(&init_net, &post_routing_ops);
    if (ret < 0) {
        printk(KERN_ERR "Failed to register netfilter hook: %d\n", ret);
        return ret;
    }
    
    printk(KERN_INFO "Post routing hook registered successfully\n");
    return 0;
}

// Module cleanup function
static void __exit netfilter_post_routing_exit(void)
{
    // Unregister the hook
    nf_unregister_net_hook(&init_net, &post_routing_ops);

    // Clean up all entries in the RCU list
    cleanup_skb_list();
    
    // Wait for all RCU callbacks to complete
    rcu_barrier();

    printk(KERN_INFO "Netfilter post routing hook module unloaded\n");
}

// Register module entry and exit points
module_init(netfilter_post_routing_init);
module_exit(netfilter_post_routing_exit);
obj-m += netfilter_postrouting.o
KDIR := /lib/modules/$(shell uname -r)/build

all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean

[Index of Archives]     [Netfitler Users]     [Berkeley Packet Filter]     [LARTC]     [Bugtraq]     [Yosemite Forum]

  Powered by Linux