inet: add RCU protection to inet->opt
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / net / ipv4 / ip_sockglue.c
index 9640900309bbb2a58762bac1e526e149767b972e..ab0c9efd1efabe7c003daef3f6d258ec38604817 100644 (file)
@@ -451,6 +451,11 @@ out:
 }
 
 
+static void opt_kfree_rcu(struct rcu_head *head)
+{
+       kfree(container_of(head, struct ip_options_rcu, rcu));
+}
+
 /*
  *     Socket option code for IP. This is the end of the line after any
  *     TCP,UDP etc options on an IP socket.
@@ -497,13 +502,16 @@ static int do_ip_setsockopt(struct sock *sk, int level,
        switch (optname) {
        case IP_OPTIONS:
        {
-               struct ip_options *opt = NULL;
+               struct ip_options_rcu *old, *opt = NULL;
+
                if (optlen > 40)
                        goto e_inval;
                err = ip_options_get_from_user(sock_net(sk), &opt,
                                               optval, optlen);
                if (err)
                        break;
+               old = rcu_dereference_protected(inet->inet_opt,
+                                               sock_owned_by_user(sk));
                if (inet->is_icsk) {
                        struct inet_connection_sock *icsk = inet_csk(sk);
 #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
@@ -512,17 +520,18 @@ static int do_ip_setsockopt(struct sock *sk, int level,
                               (TCPF_LISTEN | TCPF_CLOSE)) &&
                             inet->inet_daddr != LOOPBACK4_IPV6)) {
 #endif
-                               if (inet->opt)
-                                       icsk->icsk_ext_hdr_len -= inet->opt->optlen;
+                               if (old)
+                                       icsk->icsk_ext_hdr_len -= old->opt.optlen;
                                if (opt)
-                                       icsk->icsk_ext_hdr_len += opt->optlen;
+                                       icsk->icsk_ext_hdr_len += opt->opt.optlen;
                                icsk->icsk_sync_mss(sk, icsk->icsk_pmtu_cookie);
 #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
                        }
 #endif
                }
-               opt = xchg(&inet->opt, opt);
-               kfree(opt);
+               rcu_assign_pointer(inet->inet_opt, opt);
+               if (old)
+                       call_rcu(&old->rcu, opt_kfree_rcu);
                break;
        }
        case IP_PKTINFO:
@@ -1081,12 +1090,16 @@ static int do_ip_getsockopt(struct sock *sk, int level, int optname,
        case IP_OPTIONS:
        {
                unsigned char optbuf[sizeof(struct ip_options)+40];
-               struct ip_options * opt = (struct ip_options *)optbuf;
+               struct ip_options *opt = (struct ip_options *)optbuf;
+               struct ip_options_rcu *inet_opt;
+
+               inet_opt = rcu_dereference_protected(inet->inet_opt,
+                                                    sock_owned_by_user(sk));
                opt->optlen = 0;
-               if (inet->opt)
-                       memcpy(optbuf, inet->opt,
-                              sizeof(struct ip_options)+
-                              inet->opt->optlen);
+               if (inet_opt)
+                       memcpy(optbuf, &inet_opt->opt,
+                              sizeof(struct ip_options) +
+                              inet_opt->opt.optlen);
                release_sock(sk);
 
                if (opt->optlen == 0)