xorl %eax, %eax

CVE-2011-1767: Linux kernel IP GRE Remote Race Condition

leave a comment »

This is a very interesting bug reported by Alexey Dobriyan as you can read here, the IP GRE module initialization routine contains the following code from net/ipv4/ip_gre.c.

/*
 *      And now the modules code and kernel interface.
 */

static int __init ipgre_init(void)
{
        int err;

        printk(KERN_INFO "GRE over IPv4 tunneling driver\n");

        if (inet_add_protocol(&ipgre_protocol, IPPROTO_GRE) < 0) {
                printk(KERN_INFO "ipgre init: can't add protocol\n");
                return -EAGAIN;
        }

        err = register_pernet_gen_device(&ipgre_net_id, &ipgre_net_ops);
        if (err < 0)
                goto gen_device_failed;

        err = rtnl_link_register(&ipgre_link_ops);
        if (err < 0)
                goto rtnl_link_failed;

        err = rtnl_link_register(&ipgre_tap_ops);
        if (err < 0)
                goto tap_ops_failed;

out:
        return err;

tap_ops_failed:
        rtnl_link_unregister(&ipgre_link_ops);
rtnl_link_failed:
        unregister_pernet_gen_device(ipgre_net_id, &ipgre_net_ops);
gen_device_failed:
        inet_del_protocol(&ipgre_protocol, IPPROTO_GRE);
        goto out;
}

As you can see, the initialization of the IP GRE kernel module will initially register the ‘IPPROTO_GRE’ protocol and next register the callback functions stored in ‘ipgre_net_ops’ structure shown below.

static struct pernet_operations ipgre_net_ops = {
        .init = ipgre_init_net,
        .exit = ipgre_exit_net,
};

So, if an attacker is able to send a packet between the two initializations (the protocol and the ‘ipgre_net_ops’ structure) he/she will trigger a NULL pointer dereference due to the kernel’s attempt to execute the initialitazion routine of the aforementioned structure.
This becomes even more interesting on systems supporting module auto-loading.

As Alexey Dobriyan pointed out, the dereference will be triggered in net_generic() which is located in the include/net/netns/generic.h header file shown here.

/*
 * Generic net pointers are to be used by modules to put some private
 * stuff on the struct net without explicit struct net modification
 *
 * The rules are simple:
 * 1. register the ops with register_pernet_gen_device to get the id
 *    of your private pointer;
 * 2. call net_assign_generic() to put the private data on the struct
 *    net (most preferably this should be done in the ->init callback
 *    of the ops registered);
 * 3. do not change this pointer while the net is alive;
 * 4. do not try to have any private reference on the net_generic object.
 *
 * After accomplishing all of the above, the private pointer can be
 * accessed with the net_generic() call.
 */

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;
}

Obviously, the patch was to re-order the registration as you can see here.

 	printk(KERN_INFO "GRE over IPv4 tunneling driver\n");
 
-	if (inet_add_protocol(&ipgre_protocol, IPPROTO_GRE) < 0) {
-		printk(KERN_INFO "ipgre init: can't add protocol\n");
-		return -EAGAIN;
-	}
-
 	err = register_pernet_device(&ipgre_net_ops);
 	if (err < 0)
-		goto gen_device_failed;
+		return err;
+
+	err = inet_add_protocol(&ipgre_protocol, IPPROTO_GRE);
+	if (err < 0) {
+		printk(KERN_INFO "ipgre init: can't add protocol\n");
+		goto add_proto_failed;
+	}
 
 	err = rtnl_link_register(&ipgre_link_ops);

And since a new label was added, at the end of the routine we can also notice the next changes.

 tap_ops_failed:
 	rtnl_link_unregister(&ipgre_link_ops);
 rtnl_link_failed:
-	unregister_pernet_device(&ipgre_net_ops);
-gen_device_failed:
 	inet_del_protocol(&ipgre_protocol, IPPROTO_GRE);
+add_proto_failed:
+	unregister_pernet_device(&ipgre_net_ops);
 	goto out;

Similarly, the exit routine for IP GRE was also updated in a similar manner.

 	rtnl_link_unregister(&ipgre_link_ops);
-	unregister_pernet_device(&ipgre_net_ops);
 	if (inet_del_protocol(&ipgre_protocol, IPPROTO_GRE) < 0)
 		printk(KERN_INFO "ipgre close: can't remove protocol\n");
+	unregister_pernet_device(&ipgre_net_ops);
 }

Written by xorl

May 14, 2011 at 21:48

Posted in bugs, linux

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