Kernel oops during netfilter memory allocation

From: Thomas Jarosch
Date: Mon Aug 20 2007 - 12:40:07 EST


Hey there,

I'm currently debugging a kernel oops with kernel 2.6.21.7 that occurs
from time to time with our netfilter accounting module ipt_ACCOUNT
(http://www.intra2net.com/de/produkte/opensource/ipt_account/).

What basically happens is that the module allocates memory
inside an interrupt handler like many other netfilter modules do.
Except that it does it very often if you have a large, busy network.

To me it looks like I found a bug in the kernel memory handler.
I've written a minimalistic netfilter module ipt_CRASH that
just allocates and frees memory to demonstrate the problem.

Here's a screenshot of the oops:
http://img231.imageshack.us/img231/9064/kerneloopsiptcrashzb1.jpg

Another shot of it with a "tainted" kernel:
http://img529.imageshack.us/img529/6681/kerneloopsey1.jpg

I would have included the backtrace as text, but unfortunately
the USB serial console dies before it outputs anything useful.

You need a fast machine with lots of RAM to reproduce it, I used a 3 Ghz
machine with 3 GB of RAM. ipt_CRASH allocates and frees memory for every
packet, a packet generator like "sendip" might help:
http://www.earth.li/projectpurple/progs/sendip.html

The machine dies after 15-30 minutes or as soon as I try to reboot it.

Please CC: answers, I'm only on netfilter-devel.

Thanks in advance,
Thomas

Attachment: crashit.sh
Description: application/shellscript

/* Shared library add-on to iptables to add CRASH support.
Author: Intra2net AG <opensource@xxxxxxxxxxxxx>
Licensed under the GPL v2.
*/

#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <syslog.h>
#include <getopt.h>
#include <iptables.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ipt_CRASH.h>

static struct option opts[] = {
{ .name = 0 }
};

/* Function which prints out usage message. */
static void help(void)
{
printf(
"CRASH v%s options:\n", IPTABLES_VERSION);
}

/* Initialize the target. */
static void
init(struct ipt_entry_target *t, unsigned int *nfcache)
{
/* Can't cache this */
*nfcache |= NFC_UNKNOWN;
}

/* Function which parses command options; returns true if it
ate an option */
static int
parse(int c, char **argv, int invert, unsigned int *flags,
const struct ipt_entry *entry,
struct ipt_entry_target **target)
{
return 0;
}

/* Final check; nothing. */
static void final_check(unsigned int flags)
{
}

static void print_it(const struct ipt_ip *ip,
const struct ipt_entry_target *target, char do_prefix)
{
if (!do_prefix)
printf("CRASH ");
}

/* Prints out the targinfo. */
static void
print(const struct ipt_ip *ip,
const struct ipt_entry_target *target,
int numeric)
{
print_it (ip, target, 0);
}

/* Saves the union ipt_targinfo in parsable form to stdout. */
static void
save(const struct ipt_ip *ip, const struct ipt_entry_target *target)
{
print_it(ip, target, 1);
}

static
struct iptables_target crash
= {
.next = NULL,
.name = "CRASH",
.version = IPTABLES_VERSION,
.size = IPT_ALIGN(sizeof(struct ipt_crash_info)),
.userspacesize = IPT_ALIGN(sizeof(struct ipt_crash_info)),
.help = &help,
.init = &init,
.parse = &parse,
.final_check = &final_check,
.print = &print,
.save = &save,
.extra_opts = opts
};

void _init(void)
{
register_target(&crash);
}
/***************************************************************************
* Copyright (C) 2007 by Intra2net AG *
* opensource@xxxxxxxxxxxxx *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License *
* version 2 as published by the Free Software Foundation; *
* *
***************************************************************************/

#include <linux/module.h>
#include <linux/version.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <net/icmp.h>
#include <net/udp.h>
#include <net/tcp.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ipt_CRASH.h>
#include <asm/semaphore.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/spinlock.h>
#include <asm/uaccess.h>

/* Spinlock used during memory allocation */
static DEFINE_SPINLOCK(ipt_crash_lock);

static unsigned int ipt_crash_target(struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
unsigned int hooknum,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,17)
const struct xt_target *target,
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
const void *targinfo)
#else
const void *targinfo,
void *userinfo)
#endif
{
char *data;

spin_lock_bh(&ipt_crash_lock);

if ((data = (char *)get_zeroed_page(GFP_ATOMIC)) == NULL) {
printk("ipt_CRASH: Out of memory!\n");
} else {
// Fill memory with pattern
int i, j = 123;
for (i = 0; i < PAGE_SIZE; i++) {
data[i] = j;
j++;
}

free_page((unsigned long)data);
}

spin_unlock_bh(&ipt_crash_lock);

return IPT_CONTINUE;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)
static struct xt_target xt_crash_reg = {
#else
static struct ipt_target ipt_crash_reg = {
#endif
.name = "CRASH",
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)
.family = AF_INET,
#endif
.target = ipt_crash_target,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,17)
.targetsize = sizeof(struct ipt_crash_info),
#endif
.me = THIS_MODULE
};

static int __init init(void)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)
if (xt_register_target(&xt_crash_reg))
#else
if (ipt_register_target(&ipt_crash_reg))
#endif
return -EINVAL;

return 0;
}

static void __exit fini(void)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)
xt_unregister_target(&xt_crash_reg);
#else
ipt_unregister_target(&ipt_crash_reg);
#endif
}

module_init(init);
module_exit(fini);
MODULE_LICENSE("GPL");
/***************************************************************************
* Copyright (C) 2004-2005 by Intra2net AG *
* opensource@xxxxxxxxxxxxx *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License *
* version 2 as published by the Free Software Foundation; *
* *
***************************************************************************/

#ifndef _IPT_CRASH_H
#define _IPT_CRASH_H

/* Structure for the userspace part of ipt_CRASH */
struct ipt_crash_info {
};

#endif /*_IPT_CRASH_H*/