net: Fix use after free by removing length arg from sk_data_ready callbacks.
[linux.git] / net / core / skbuff.c
index 90b96a11b974d2697defcc9ccf4b14b65d704c49..1b62343f58378b3d8fc0e3ea048dbb45ce1e3a76 100644 (file)
@@ -3300,6 +3300,32 @@ __skb_to_sgvec(struct sk_buff *skb, struct scatterlist *sg, int offset, int len)
        return elt;
 }
 
+/* As compared with skb_to_sgvec, skb_to_sgvec_nomark only map skb to given
+ * sglist without mark the sg which contain last skb data as the end.
+ * So the caller can mannipulate sg list as will when padding new data after
+ * the first call without calling sg_unmark_end to expend sg list.
+ *
+ * Scenario to use skb_to_sgvec_nomark:
+ * 1. sg_init_table
+ * 2. skb_to_sgvec_nomark(payload1)
+ * 3. skb_to_sgvec_nomark(payload2)
+ *
+ * This is equivalent to:
+ * 1. sg_init_table
+ * 2. skb_to_sgvec(payload1)
+ * 3. sg_unmark_end
+ * 4. skb_to_sgvec(payload2)
+ *
+ * When mapping mutilple payload conditionally, skb_to_sgvec_nomark
+ * is more preferable.
+ */
+int skb_to_sgvec_nomark(struct sk_buff *skb, struct scatterlist *sg,
+                       int offset, int len)
+{
+       return __skb_to_sgvec(skb, sg, offset, len);
+}
+EXPORT_SYMBOL_GPL(skb_to_sgvec_nomark);
+
 int skb_to_sgvec(struct sk_buff *skb, struct scatterlist *sg, int offset, int len)
 {
        int nsg = __skb_to_sgvec(skb, sg, offset, len);
@@ -3432,8 +3458,6 @@ static void sock_rmem_free(struct sk_buff *skb)
  */
 int sock_queue_err_skb(struct sock *sk, struct sk_buff *skb)
 {
-       int len = skb->len;
-
        if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >=
            (unsigned int)sk->sk_rcvbuf)
                return -ENOMEM;
@@ -3448,7 +3472,7 @@ int sock_queue_err_skb(struct sock *sk, struct sk_buff *skb)
 
        skb_queue_tail(&sk->sk_error_queue, skb);
        if (!sock_flag(sk, SOCK_DEAD))
-               sk->sk_data_ready(sk, len);
+               sk->sk_data_ready(sk);
        return 0;
 }
 EXPORT_SYMBOL(sock_queue_err_skb);
@@ -3562,15 +3586,47 @@ static int skb_maybe_pull_tail(struct sk_buff *skb, unsigned int len,
        return 0;
 }
 
+#define MAX_TCP_HDR_LEN (15 * 4)
+
+static __sum16 *skb_checksum_setup_ip(struct sk_buff *skb,
+                                     typeof(IPPROTO_IP) proto,
+                                     unsigned int off)
+{
+       switch (proto) {
+               int err;
+
+       case IPPROTO_TCP:
+               err = skb_maybe_pull_tail(skb, off + sizeof(struct tcphdr),
+                                         off + MAX_TCP_HDR_LEN);
+               if (!err && !skb_partial_csum_set(skb, off,
+                                                 offsetof(struct tcphdr,
+                                                          check)))
+                       err = -EPROTO;
+               return err ? ERR_PTR(err) : &tcp_hdr(skb)->check;
+
+       case IPPROTO_UDP:
+               err = skb_maybe_pull_tail(skb, off + sizeof(struct udphdr),
+                                         off + sizeof(struct udphdr));
+               if (!err && !skb_partial_csum_set(skb, off,
+                                                 offsetof(struct udphdr,
+                                                          check)))
+                       err = -EPROTO;
+               return err ? ERR_PTR(err) : &udp_hdr(skb)->check;
+       }
+
+       return ERR_PTR(-EPROTO);
+}
+
 /* This value should be large enough to cover a tagged ethernet header plus
  * maximally sized IP and TCP or UDP headers.
  */
 #define MAX_IP_HDR_LEN 128
 
-static int skb_checksum_setup_ip(struct sk_buff *skb, bool recalculate)
+static int skb_checksum_setup_ipv4(struct sk_buff *skb, bool recalculate)
 {
        unsigned int off;
        bool fragment;
+       __sum16 *csum;
        int err;
 
        fragment = false;
@@ -3591,51 +3647,15 @@ static int skb_checksum_setup_ip(struct sk_buff *skb, bool recalculate)
        if (fragment)
                goto out;
 
-       switch (ip_hdr(skb)->protocol) {
-       case IPPROTO_TCP:
-               err = skb_maybe_pull_tail(skb,
-                                         off + sizeof(struct tcphdr),
-                                         MAX_IP_HDR_LEN);
-               if (err < 0)
-                       goto out;
-
-               if (!skb_partial_csum_set(skb, off,
-                                         offsetof(struct tcphdr, check))) {
-                       err = -EPROTO;
-                       goto out;
-               }
-
-               if (recalculate)
-                       tcp_hdr(skb)->check =
-                               ~csum_tcpudp_magic(ip_hdr(skb)->saddr,
-                                                  ip_hdr(skb)->daddr,
-                                                  skb->len - off,
-                                                  IPPROTO_TCP, 0);
-               break;
-       case IPPROTO_UDP:
-               err = skb_maybe_pull_tail(skb,
-                                         off + sizeof(struct udphdr),
-                                         MAX_IP_HDR_LEN);
-               if (err < 0)
-                       goto out;
-
-               if (!skb_partial_csum_set(skb, off,
-                                         offsetof(struct udphdr, check))) {
-                       err = -EPROTO;
-                       goto out;
-               }
-
-               if (recalculate)
-                       udp_hdr(skb)->check =
-                               ~csum_tcpudp_magic(ip_hdr(skb)->saddr,
-                                                  ip_hdr(skb)->daddr,
-                                                  skb->len - off,
-                                                  IPPROTO_UDP, 0);
-               break;
-       default:
-               goto out;
-       }
+       csum = skb_checksum_setup_ip(skb, ip_hdr(skb)->protocol, off);
+       if (IS_ERR(csum))
+               return PTR_ERR(csum);
 
+       if (recalculate)
+               *csum = ~csum_tcpudp_magic(ip_hdr(skb)->saddr,
+                                          ip_hdr(skb)->daddr,
+                                          skb->len - off,
+                                          ip_hdr(skb)->protocol, 0);
        err = 0;
 
 out:
@@ -3658,6 +3678,7 @@ static int skb_checksum_setup_ipv6(struct sk_buff *skb, bool recalculate)
        unsigned int len;
        bool fragment;
        bool done;
+       __sum16 *csum;
 
        fragment = false;
        done = false;
@@ -3735,51 +3756,14 @@ static int skb_checksum_setup_ipv6(struct sk_buff *skb, bool recalculate)
        if (!done || fragment)
                goto out;
 
-       switch (nexthdr) {
-       case IPPROTO_TCP:
-               err = skb_maybe_pull_tail(skb,
-                                         off + sizeof(struct tcphdr),
-                                         MAX_IPV6_HDR_LEN);
-               if (err < 0)
-                       goto out;
-
-               if (!skb_partial_csum_set(skb, off,
-                                         offsetof(struct tcphdr, check))) {
-                       err = -EPROTO;
-                       goto out;
-               }
-
-               if (recalculate)
-                       tcp_hdr(skb)->check =
-                               ~csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
-                                                &ipv6_hdr(skb)->daddr,
-                                                skb->len - off,
-                                                IPPROTO_TCP, 0);
-               break;
-       case IPPROTO_UDP:
-               err = skb_maybe_pull_tail(skb,
-                                         off + sizeof(struct udphdr),
-                                         MAX_IPV6_HDR_LEN);
-               if (err < 0)
-                       goto out;
-
-               if (!skb_partial_csum_set(skb, off,
-                                         offsetof(struct udphdr, check))) {
-                       err = -EPROTO;
-                       goto out;
-               }
-
-               if (recalculate)
-                       udp_hdr(skb)->check =
-                               ~csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
-                                                &ipv6_hdr(skb)->daddr,
-                                                skb->len - off,
-                                                IPPROTO_UDP, 0);
-               break;
-       default:
-               goto out;
-       }
+       csum = skb_checksum_setup_ip(skb, nexthdr, off);
+       if (IS_ERR(csum))
+               return PTR_ERR(csum);
 
+       if (recalculate)
+               *csum = ~csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
+                                        &ipv6_hdr(skb)->daddr,
+                                        skb->len - off, nexthdr, 0);
        err = 0;
 
 out:
@@ -3797,7 +3781,7 @@ int skb_checksum_setup(struct sk_buff *skb, bool recalculate)
 
        switch (skb->protocol) {
        case htons(ETH_P_IP):
-               err = skb_checksum_setup_ip(skb, recalculate);
+               err = skb_checksum_setup_ipv4(skb, recalculate);
                break;
 
        case htons(ETH_P_IPV6):
@@ -3951,12 +3935,14 @@ EXPORT_SYMBOL_GPL(skb_scrub_packet);
 unsigned int skb_gso_transport_seglen(const struct sk_buff *skb)
 {
        const struct skb_shared_info *shinfo = skb_shinfo(skb);
-       unsigned int hdr_len;
 
        if (likely(shinfo->gso_type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)))
-               hdr_len = tcp_hdrlen(skb);
-       else
-               hdr_len = sizeof(struct udphdr);
-       return hdr_len + shinfo->gso_size;
+               return tcp_hdrlen(skb) + shinfo->gso_size;
+
+       /* UFO sets gso_size to the size of the fragmentation
+        * payload, i.e. the size of the L4 (UDP) header is already
+        * accounted for.
+        */
+       return shinfo->gso_size;
 }
 EXPORT_SYMBOL_GPL(skb_gso_transport_seglen);