xorl %eax, %eax

Linux kernel NET_NS IPv6 NULL Pointer Dereference

leave a comment »

This vulnerability was reported by Pavel Emelyanov of OpenVZ and affects Linux kernel prior to 2.6.29 release. This is not a critical vulnerability since it only affects kernel releases configured with NET_NS (Network Namespace Support) option enabled. Here is the vulnerable function as seen on 2.6.28 release:

158 static int __inet6_check_established(struct inet_timewait_death_row *death_row,
159                                      struct sock *sk, const __u16 lport,
160                                      struct inet_timewait_sock **twp)
161 {


This function can be found at net/ipv6/inet6_hashtables.c and it’s responsible for checking whether an INET6 socket has established a connection. Its arguments are:
inet_timewait_death_row pointer
which is a short time ‘timewait’ calendar used on hash tables.
– sock pointer
the socket it’s dealing with.
unsigned short
source port number.
– inet_timewait_sock pointer to pointer
this is the Linux kernel’s TIME_WAIT socket state.
Now, here is the buggy code of the above routine…

174        struct sock *sk2;
     ...
176        struct inet_timewait_sock *tw;
     ...
181        /* Check TIME-WAIT sockets first. */
182        sk_for_each(sk2, node, &head->twchain) {
183                tw = inet_twsk(sk2);
184
185                if (INET6_TW_MATCH(sk2, net, hash, saddr, daddr, ports, dif)) {
186                        if (twsk_unique(sk, sk2, twp))
187                                goto unique;
     ...
191        }
192        tw = NULL;
     ...
200 unique:
201        /* Must record num and sport now. Otherwise we will see
202         * in hash table socket with a funny identity. */
     ...
211        if (twp != NULL) {
212                *twp = tw;
213                NET_INC_STATS_BH(twsk_net(tw), LINUX_MIB_TIMEWAITRECYCLED);
214        } else if (tw != NULL) {
215                /* Silly. Should hash-dance instead... */
216                inet_twsk_deschedule(tw, death_row);
217                NET_INC_STATS_BH(twsk_net(tw), LINUX_MIB_TIMEWAITRECYCLED);
218
219                inet_twsk_put(tw);
220        }
221        return 0;
222
223 not_unique:
224        write_unlock(lock);
225        return -EADDRNOTAVAIL;
226 }


Variable tw which is used to represent the TIME_WAIT sockets, is declared at line 176 and initialized at line 183 using inet_twsk() which simply does this:

186 static inline struct inet_timewait_sock *inet_twsk(const struct sock *sk)
187 {
188        return (struct inet_timewait_sock *)sk;
189 }


Then, if macro INET6_TW_MATCH() of include/linux/ipv6.h succeeds and inline function twsk_unique() which checks that the requested TIME_WAIT socket is unique returns a non-zero value, it will jump to unique label. In any other case, tw is initliazed to NULL (line 192). Later, twp is updated to contain the value of tw (line 212) which can be NULL under certain conditions. An immediate call to NET_INC_STATS_BH() from include/net/ip.h is being made, here is this macro:

172 #define NET_INC_STATS_BH(net, field)    SNMP_INC_STATS_BH((net)->mib.net_statistics, field)

Although the NULL pointer dereference could happen here since tw can be NULL, this is only the case when twsk_net() returns an offset from the actual tw pointer passed to it at line 213. Here is what this function does from include/net/inet_timewait_sock.h:

215 static inline
216 struct net *twsk_net(const struct inet_timewait_sock *twsk)
217 {
218 #ifdef CONFIG_NET_NS
219        return twsk->tw_net;
220 #else
221        return &init_net;
222 #endif
223 }


This is the reason why only systems configured with NET_NS are vulnerable to this attack. Assuming that tw is set to NULL, the call to the latter macro would be:

NET_INC_STATS_BH(((NULL)->tw_net)), LINUX_MIB_TIMEWAITRECYCLED);

Where the second argument is an enumeration member found at include/linux/snmp.h like this:

144 /* linux mib definitions */
145 enum
146 {
      ...
159        LINUX_MIB_TIMEWAITRECYCLED,             /* TimeWaitRecycled */
      ...
220 };


To fix this the following patch was applied:

                *twp = tw;
-               NET_INC_STATS_BH(twsk_net(tw), LINUX_MIB_TIMEWAITRECYCLED);
+               NET_INC_STATS_BH(net, LINUX_MIB_TIMEWAITRECYCLED);
        } else if (tw != NULL) {
                /* Silly. Should hash-dance instead... */
                inet_twsk_deschedule(tw, death_row);
-               NET_INC_STATS_BH(twsk_net(tw), LINUX_MIB_TIMEWAITRECYCLED);
+               NET_INC_STATS_BH(net, LINUX_MIB_TIMEWAITRECYCLED);


Where pointer net is a variable defined early in __inet6_check_established() routine to store the network of the requested socket like this:

169        struct net *net = sock_net(sk);

According to Pavel Emelyanov and Dave S. Miller this bug was introduced on Linux kernel 2.6.27 release.

Written by xorl

April 21, 2009 at 12:41

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