Vulnerability: An issue was discovered in net/ipv6/ip6mr.c in the Linux kernel before 4.11. By setting a specific socket option, an attacker can control a pointer in kernel land and cause an inet_csk_listen_stop general protection fault, or potentially execute arbitrary code under certain circumstances. The issue can be triggered as root (e.g., inside a default LXC container or with the CAP_NET_ADMIN capability) or after namespace unsharing. This occurs because sk_type and protocol are not checked in the appropriate part of the ip6_mroute_* functions. NOTE: this affects Linux distributions that use 4.9.x longterm kernels before 4.9.187.
Vulnerability ID: CVE-2017-18509
CWE: CWE-20
Severity score: 10
Project: https://github.com/torvalds/linux
Published: 2019-08-13
File: net/ipv6/ip6mr.c
Commit message: ipv6: check sk sk_type and protocol early in ip_mroute_set/getsockopt

Commit 5e1859fbcc3c ("ipv4: ipmr: various fixes and cleanups") fixed
the issue for ipv4 ipmr:

  ip_mroute_setsockopt() & ip_mroute_getsockopt() should not
  access/set raw_sk(sk)->ipmr_table before making sure the socket
  is a raw socket, and protocol is IGMP

The same fix should be done for ipv6 ipmr as well.

This patch can fix the panic caused by overwriting the same offset
as ipmr_table as in raw_sk(sk) when accessing other type's socket
by ip_mroute_setsockopt().

Signed-off-by: Xin Long <lucien.xin@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Commit author: Xin Long

Vulnerable version (before fix):
 int ip6_mroute_setsockopt(struct sock *sk, int optname, char __user *optval, uns 	struct net *net = sock_net(sk); 	struct mr6_table *mrt;  	mrt = ip6mr_get_table(net, raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT); 	if (!mrt) 		return -ENOENT; int ip6_mroute_setsockopt(struct sock *sk, int optname, char __user *optval, uns  	switch (optname) { 	case MRT6_INIT: 		if (sk->sk_type != SOCK_RAW || 		    inet_sk(sk)->inet_num != IPPROTO_ICMPV6) 			return -EOPNOTSUPP; 		if (optlen < sizeof(int)) 			return -EINVAL;  int ip6_mroute_getsockopt(struct sock *sk, int optname, char __user *optval, 	struct net *net = sock_net(sk); 	struct mr6_table *mrt;  	mrt = ip6mr_get_table(net, raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT); 	if (!mrt) 		return -ENOENT;

Fixed version (after fix):
 int ip6_mroute_setsockopt(struct sock *sk, int optname, char __user *optval, uns 	struct net *net = sock_net(sk); 	struct mr6_table *mrt;  	if (sk->sk_type != SOCK_RAW || 	    inet_sk(sk)->inet_num != IPPROTO_ICMPV6) 		return -EOPNOTSUPP;  	mrt = ip6mr_get_table(net, raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT); 	if (!mrt) 		return -ENOENT; int ip6_mroute_setsockopt(struct sock *sk, int optname, char __user *optval, uns  	switch (optname) { 	case MRT6_INIT: 		if (optlen < sizeof(int)) 			return -EINVAL;  int ip6_mroute_getsockopt(struct sock *sk, int optname, char __user *optval, 	struct net *net = sock_net(sk); 	struct mr6_table *mrt;  	if (sk->sk_type != SOCK_RAW || 	    inet_sk(sk)->inet_num != IPPROTO_ICMPV6) 		return -EOPNOTSUPP;  	mrt = ip6mr_get_table(net, raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT); 	if (!mrt) 		return -ENOENT;

Patch:
patch: @@ -1666,6 +1666,10 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, char __user *optval, uns
 	struct net *net = sock_net(sk);
 	struct mr6_table *mrt;
 
+	if (sk->sk_type != SOCK_RAW ||
+	    inet_sk(sk)->inet_num != IPPROTO_ICMPV6)
+		return -EOPNOTSUPP;
+
 	mrt = ip6mr_get_table(net, raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT);
 	if (!mrt)
 		return -ENOENT;
@@ -1677,9 +1681,6 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, char __user *optval, uns
 
 	switch (optname) {
 	case MRT6_INIT:
-		if (sk->sk_type != SOCK_RAW ||
-		    inet_sk(sk)->inet_num != IPPROTO_ICMPV6)
-			return -EOPNOTSUPP;
 		if (optlen < sizeof(int))
 			return -EINVAL;
 
@@ -1815,6 +1816,10 @@ int ip6_mroute_getsockopt(struct sock *sk, int optname, char __user *optval,
 	struct net *net = sock_net(sk);
 	struct mr6_table *mrt;
 
+	if (sk->sk_type != SOCK_RAW ||
+	    inet_sk(sk)->inet_num != IPPROTO_ICMPV6)
+		return -EOPNOTSUPP;
+
 	mrt = ip6mr_get_table(net, raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT);
 	if (!mrt)
 		return -ENOENT;
path: net/ipv6/ip6mr.c