Linux kernel NET_NS IPv6 NULL Pointer Dereference
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.

Leave a comment