xorl %eax, %eax

Linux kernel Tunnels Race Condition

with 3 comments

I read about this bug from Eugene Teo’s email to oss-security mailing list. The issue was discovered by Alexey Dobriyan and here is the buggy code from net/ipv4/ipip.c as seen in 2.6.32 release of the Linux kernel.

static int __init ipip_init(void)
{
        int err;

        printk(banner);

        if (xfrm4_tunnel_register(&ipip_handler, AF_INET)) {
                printk(KERN_INFO "ipip init: can't register tunnel\n");
                return -EAGAIN;
        }

        err = register_pernet_gen_device(&ipip_net_id, &ipip_net_ops);
        if (err)
                xfrm4_tunnel_deregister(&ipip_handler, AF_INET);

        return err;
}
       ...
module_init(ipip_init);

As you can read in the above module initialization routine, after printing the module’s banner using printk() function, the code will invoke xfrm4_tunnel_register() which is a function located at net/ipv4/tunnel4.c and it is used to register a new XFRM tunnel handler. In this case, that is the ipip_handler() routine for the ‘AF_INET’ family. The next call to register_pernet_gen_device() is used to initialize the initilization and cleanup callback functions for the ‘ipip_net_id’ network ID. If this fails it will unregister the previously registered tunnel handler.
As Alexey Dobriyan noticed, the receive hook of the new handler could be called right after the registration of the handler and before the registration of the network operations through register_pernet_gen_device(). If this is the case, then ipip_rcv() will be invoked since this is the registered handler as we can read below:

static struct xfrm_tunnel ipip_handler = {
        .handler        =       ipip_rcv,
        .err_handler    =       ipip_err,
        .priority       =       1,
};

Now, if we move to this routine we’ll read the following…

static int ipip_rcv(struct sk_buff *skb)
{
        struct ip_tunnel *tunnel;
        const struct iphdr *iph = ip_hdr(skb);

        read_lock(&ipip_lock);
        if ((tunnel = ipip_tunnel_lookup(dev_net(skb->dev),
                                        iph->saddr, iph->daddr)) != NULL) {
     ...
        return -1;
}

So, after performing the required locking it will immediately call ipip_tunnel_lookup() which starts by calling the code shown here:

static struct ip_tunnel * ipip_tunnel_lookup(struct net *net,
                __be32 remote, __be32 local)
{
        unsigned h0 = HASH(remote);
        unsigned h1 = HASH(local);
        struct ip_tunnel *t;
        struct ipip_net *ipn = net_generic(net, ipip_net_id);
     ...
        return NULL;
}

As you can read, it will invoke net_generic() of include/net/netns/generic.h passing the possibly unititialized ‘ipip_net_id’. This will lead to the following call to BUG_ON() macro…

struct net_generic {
        unsigned int len;
        struct rcu_head rcu;

        void *ptr[0];
};

static inline void *net_generic(struct net *net, int id)
{
        struct net_generic *ng;
        void *ptr;

        rcu_read_lock();
        ng = rcu_dereference(net->gen);
        BUG_ON(id == 0 || id > ng->len);
        ptr = ng->ptr[id - 1];
        rcu_read_unlock();

        return ptr;
}

Since the network operations may have not been initialized yet, the ‘id’ integer could result in triggering the above BUG_ON() macro and thus OOPSing the kernel. To fix this the following patch was applied:

 	printk(banner);
 
-	if (xfrm4_tunnel_register(&ipip_handler, AF_INET)) {
+	err = register_pernet_device(&ipip_net_ops);
+	if (err < 0)
+		return err;
+	err = xfrm4_tunnel_register(&ipip_handler, AF_INET);
+	if (err < 0) {
+		unregister_pernet_device(&ipip_net_ops);
 		printk(KERN_INFO "ipip init: can't register tunnel\n");
-		return -EAGAIN;
 	}
-
-	err = register_pernet_device(&ipip_net_ops);
-	if (err)
-		xfrm4_tunnel_deregister(&ipip_handler, AF_INET);
-
 	return err;

Basically, this code justs re-orders the initialization code in order to move the network operations registration first, before the handler callbacks registration. The exact same bug was present in IPv6 tunnels, SIT tunnels, XFRM6 tunnels and GRE protocol.

Written by xorl

February 21, 2010 at 23:43

Posted in bugs, linux

3 Responses

Subscribe to comments with RSS.

  1. It’s nice to see you’re back, dude!

    Gullit

    February 22, 2010 at 21:55

  2. xorl is back : )

    haroon atmaja

    February 23, 2010 at 07:33

  3. Thank you guys!
    Unfortunately, I’ll leave again tomorrow (http://twitter.com/xorlgr/status/9444342415).

    xorl

    February 23, 2010 at 22:34


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s