tcp repair: Fix unaligned access when repairing options (v2)
authorPavel Emelyanov <xemul@parallels.com>
Wed, 25 Apr 2012 23:43:04 +0000 (23:43 +0000)
committerDavid S. Miller <davem@davemloft.net>
Thu, 26 Apr 2012 10:13:51 +0000 (06:13 -0400)
Don't pick __u8/__u16 values directly from raw pointers, but instead use
an array of structures of code:value pairs. This is OK, since the buffer
we take options from is not an skb memory, but a user-to-kernel one.

For those options which don't require any value now, require this to be
zero (for potential future extension of this API).

v2: Changed tcp_repair_opt to use two __u32-s as spotted by David Laight.

Signed-off-by: Pavel Emelyanov <xemul@parallels.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/tcp.h
net/ipv4/tcp.c

index 9865936..278af9e 100644 (file)
@@ -111,6 +111,11 @@ enum {
 #define TCP_QUEUE_SEQ          21
 #define TCP_REPAIR_OPTIONS     22
 
+struct tcp_repair_opt {
+       __u32   opt_code;
+       __u32   opt_val;
+};
+
 enum {
        TCP_NO_QUEUE,
        TCP_RECV_QUEUE,
index de6a238..9670af3 100644 (file)
@@ -2283,60 +2283,40 @@ static inline int tcp_can_repair_sock(struct sock *sk)
                ((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_ESTABLISHED));
 }
 
-static int tcp_repair_options_est(struct tcp_sock *tp, char __user *optbuf, unsigned int len)
+static int tcp_repair_options_est(struct tcp_sock *tp,
+               struct tcp_repair_opt __user *optbuf, unsigned int len)
 {
-       /*
-        * Options are stored in CODE:VALUE form where CODE is 8bit and VALUE
-        * fits the respective TCPOLEN_ size
-        */
+       struct tcp_repair_opt opt;
 
-       while (len > 0) {
-               u8 opcode;
-
-               if (get_user(opcode, optbuf))
+       while (len >= sizeof(opt)) {
+               if (copy_from_user(&opt, optbuf, sizeof(opt)))
                        return -EFAULT;
 
                optbuf++;
-               len--;
-
-               switch (opcode) {
-               case TCPOPT_MSS: {
-                       u16 in_mss;
+               len -= sizeof(opt);
 
-                       if (len < sizeof(in_mss))
-                               return -ENODATA;
-                       if (get_user(in_mss, optbuf))
-                               return -EFAULT;
-
-                       tp->rx_opt.mss_clamp = in_mss;
-
-                       optbuf += sizeof(in_mss);
-                       len -= sizeof(in_mss);
+               switch (opt.opt_code) {
+               case TCPOPT_MSS:
+                       tp->rx_opt.mss_clamp = opt.opt_val;
                        break;
-               }
-               case TCPOPT_WINDOW: {
-                       u8 wscale;
-
-                       if (len < sizeof(wscale))
-                               return -ENODATA;
-                       if (get_user(wscale, optbuf))
-                               return -EFAULT;
-
-                       if (wscale > 14)
+               case TCPOPT_WINDOW:
+                       if (opt.opt_val > 14)
                                return -EFBIG;
 
-                       tp->rx_opt.snd_wscale = wscale;
-
-                       optbuf += sizeof(wscale);
-                       len -= sizeof(wscale);
+                       tp->rx_opt.snd_wscale = opt.opt_val;
                        break;
-               }
                case TCPOPT_SACK_PERM:
+                       if (opt.opt_val != 0)
+                               return -EINVAL;
+
                        tp->rx_opt.sack_ok |= TCP_SACK_SEEN;
                        if (sysctl_tcp_fack)
                                tcp_enable_fack(tp);
                        break;
                case TCPOPT_TIMESTAMP:
+                       if (opt.opt_val != 0)
+                               return -EINVAL;
+
                        tp->rx_opt.tstamp_ok = 1;
                        break;
                }
@@ -2557,7 +2537,9 @@ static int do_tcp_setsockopt(struct sock *sk, int level,
                if (!tp->repair)
                        err = -EINVAL;
                else if (sk->sk_state == TCP_ESTABLISHED)
-                       err = tcp_repair_options_est(tp, optval, optlen);
+                       err = tcp_repair_options_est(tp,
+                                       (struct tcp_repair_opt __user *)optval,
+                                       optlen);
                else
                        err = -EPERM;
                break;