xorl %eax, %eax

CVE-2011-1768: Linux kernel Tunnels Remote Race Condition

leave a comment »

Continuing from the previous report, this one is identical race condition reported by Alexey Dobriyan. For example in the IPv6 tunnel initialization routine located in net/ipv6/ip6_tunnel.c file we have the following code.

/**
 * ip6_tunnel_init - register protocol and reserve needed resources
 *
 * Return: 0 on success
 **/

static int __init ip6_tunnel_init(void)
{
        int  err;

        if (xfrm6_tunnel_register(&ip4ip6_handler, AF_INET)) {
                printk(KERN_ERR "ip6_tunnel init: can't register ip4ip6\n");
                err = -EAGAIN;
                goto out;
        }

        if (xfrm6_tunnel_register(&ip6ip6_handler, AF_INET6)) {
                printk(KERN_ERR "ip6_tunnel init: can't register ip6ip6\n");
                err = -EAGAIN;
                goto unreg_ip4ip6;
        }

        err = register_pernet_gen_device(&ip6_tnl_net_id, &ip6_tnl_net_ops);
        if (err < 0)
                goto err_pernet;
        return 0;
err_pernet:
        xfrm6_tunnel_deregister(&ip6ip6_handler, AF_INET6);
unreg_ip4ip6:
        xfrm6_tunnel_deregister(&ip4ip6_handler, AF_INET);
out:
        return err;
}

As you can see, the function will first register the ‘ip4ip6_handler’ protocol handler routine and then proceed to registering the ‘ip6_tnl_net_ops’ callback functions. If a user triggers the call to one of the function pointers located in the aforementioned structure it will lead to access violation since the uninitialized pointer will attempt to access invalid memory.

The fix was to re-order the registration so that the structure containing the callback functions is the first one to be registered. Since tunneling affects numerous different protocols the following routines were patched.

IPIP Tunneling
Located in net/ipv4/ipip.c file.

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

IP6 Tunnels
Which is part of the net/ipv6/ip6_tunnel.c file.

-	if (xfrm6_tunnel_register(&ip4ip6_handler, AF_INET)) {
+	err = register_pernet_device(&ip6_tnl_net_ops);
+	if (err < 0)
+		goto out_pernet;
+
+	err = xfrm6_tunnel_register(&ip4ip6_handler, AF_INET);
+	if (err < 0) {
 		printk(KERN_ERR "ip6_tunnel init: can't register ip4ip6\n");
-		err = -EAGAIN;
-		goto out;
+		goto out_ip4ip6;
 	}
 
-	if (xfrm6_tunnel_register(&ip6ip6_handler, AF_INET6)) {
+	err = xfrm6_tunnel_register(&ip6ip6_handler, AF_INET6);
+	if (err < 0) {
 		printk(KERN_ERR "ip6_tunnel init: can't register ip6ip6\n");
-		err = -EAGAIN;
-		goto unreg_ip4ip6;
+		goto out_ip6ip6;
 	}
 
-	err = register_pernet_device(&ip6_tnl_net_ops);
-	if (err < 0)
-		goto err_pernet;
 	return 0;
-err_pernet:
-	xfrm6_tunnel_deregister(&ip6ip6_handler, AF_INET6);
-unreg_ip4ip6:
+
+out_ip6ip6:
 	xfrm6_tunnel_deregister(&ip4ip6_handler, AF_INET);
-out:
+out_ip4ip6:
+	unregister_pernet_device(&ip6_tnl_net_ops);
+out_pernet:
 	return err;
 }

IPv6 SIT
That is available in net/ipv6/sit.c

-	if (xfrm4_tunnel_register(&sit_handler, AF_INET6) < 0) {
-		printk(KERN_INFO "sit init: Can't add protocol\n");
-		return -EAGAIN;
-	}
-
 	err = register_pernet_device(&sit_net_ops);
 	if (err < 0)
-		xfrm4_tunnel_deregister(&sit_handler, AF_INET6);
-
+		return err;
+	err = xfrm4_tunnel_register(&sit_handler, AF_INET6);
+	if (err < 0) {
+		unregister_pernet_device(&sit_net_ops);
+		printk(KERN_INFO "sit init: Can't add protocol\n");
+	}
 	return err;

XFRM6 IPSec
In this case, the xfrm6_tunnel_spi_init() was removed since it is no longer used by xfrm6_tunnel_init() that was patched as shown below.

	int rv;
 
+	xfrm6_tunnel_spi_kmem = kmem_cache_create("xfrm6_tunnel_spi",
+						  sizeof(struct xfrm6_tunnel_spi),
+						  0, SLAB_HWCACHE_ALIGN,
+						  NULL);
+	if (!xfrm6_tunnel_spi_kmem)
+		return -ENOMEM;
+	rv = register_pernet_subsys(&xfrm6_tunnel_net_ops);
+	if (rv < 0)
+		goto out_pernet;
 	rv = xfrm_register_type(&xfrm6_tunnel_type, AF_INET6);
 	if (rv < 0)
-		goto err;
+		goto out_type;
 	rv = xfrm6_tunnel_register(&xfrm6_tunnel_handler, AF_INET6);
 	if (rv < 0)
-		goto unreg;
+		goto out_xfrm6;
 	rv = xfrm6_tunnel_register(&xfrm46_tunnel_handler, AF_INET);
 	if (rv < 0)
-		goto dereg6;
-	rv = xfrm6_tunnel_spi_init();
-	if (rv < 0)
-		goto dereg46;
-	rv = register_pernet_subsys(&xfrm6_tunnel_net_ops);
-	if (rv < 0)
-		goto deregspi;
+		goto out_xfrm46;
 	return 0;
 
-deregspi:
-	xfrm6_tunnel_spi_fini();
-dereg46:
-	xfrm6_tunnel_deregister(&xfrm46_tunnel_handler, AF_INET);
-dereg6:
+out_xfrm46:
 	xfrm6_tunnel_deregister(&xfrm6_tunnel_handler, AF_INET6);
-unreg:
+out_xfrm6:
 	xfrm_unregister_type(&xfrm6_tunnel_type, AF_INET6);
-err:
+out_type:
+	unregister_pernet_subsys(&xfrm6_tunnel_net_ops);
+out_pernet:
+	kmem_cache_destroy(xfrm6_tunnel_spi_kmem);
 	return rv;
 }
 
 static void __exit xfrm6_tunnel_fini(void)
 {
-	unregister_pernet_subsys(&xfrm6_tunnel_net_ops);
-	xfrm6_tunnel_spi_fini();
 	xfrm6_tunnel_deregister(&xfrm46_tunnel_handler, AF_INET);
 	xfrm6_tunnel_deregister(&xfrm6_tunnel_handler, AF_INET6);
 	xfrm_unregister_type(&xfrm6_tunnel_type, AF_INET6);
+	unregister_pernet_subsys(&xfrm6_tunnel_net_ops);
+	kmem_cache_destroy(xfrm6_tunnel_spi_kmem);
 }

Finally, as a side-note this race becomes truly precious on kernels allowing module auto-loading.

Written by xorl

May 14, 2011 at 21:56

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