net/packet: tpacket_rcv: avoid a producer race condition
[linux-2.6-microblaze.git] / net / packet / af_packet.c
index e5b0986..29bd405 100644 (file)
@@ -2173,6 +2173,7 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev,
        struct timespec64 ts;
        __u32 ts_status;
        bool is_drop_n_account = false;
+       unsigned int slot_id = 0;
        bool do_vnet = false;
 
        /* struct tpacket{2,3}_hdr is aligned to a multiple of TPACKET_ALIGNMENT.
@@ -2275,6 +2276,13 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev,
        if (!h.raw)
                goto drop_n_account;
 
+       if (po->tp_version <= TPACKET_V2) {
+               slot_id = po->rx_ring.head;
+               if (test_bit(slot_id, po->rx_ring.rx_owner_map))
+                       goto drop_n_account;
+               __set_bit(slot_id, po->rx_ring.rx_owner_map);
+       }
+
        if (do_vnet &&
            virtio_net_hdr_from_skb(skb, h.raw + macoff -
                                    sizeof(struct virtio_net_hdr),
@@ -2380,7 +2388,10 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev,
 #endif
 
        if (po->tp_version <= TPACKET_V2) {
+               spin_lock(&sk->sk_receive_queue.lock);
                __packet_set_status(po, h.raw, status);
+               __clear_bit(slot_id, po->rx_ring.rx_owner_map);
+               spin_unlock(&sk->sk_receive_queue.lock);
                sk->sk_data_ready(sk);
        } else {
                prb_clear_blk_fill_status(&po->rx_ring);
@@ -4277,6 +4288,7 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u,
 {
        struct pgv *pg_vec = NULL;
        struct packet_sock *po = pkt_sk(sk);
+       unsigned long *rx_owner_map = NULL;
        int was_running, order = 0;
        struct packet_ring_buffer *rb;
        struct sk_buff_head *rb_queue;
@@ -4362,6 +4374,12 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u,
                        }
                        break;
                default:
+                       if (!tx_ring) {
+                               rx_owner_map = bitmap_alloc(req->tp_frame_nr,
+                                       GFP_KERNEL | __GFP_NOWARN | __GFP_ZERO);
+                               if (!rx_owner_map)
+                                       goto out_free_pg_vec;
+                       }
                        break;
                }
        }
@@ -4391,6 +4409,8 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u,
                err = 0;
                spin_lock_bh(&rb_queue->lock);
                swap(rb->pg_vec, pg_vec);
+               if (po->tp_version <= TPACKET_V2)
+                       swap(rb->rx_owner_map, rx_owner_map);
                rb->frame_max = (req->tp_frame_nr - 1);
                rb->head = 0;
                rb->frame_size = req->tp_frame_size;
@@ -4422,6 +4442,7 @@ static int packet_set_ring(struct sock *sk, union tpacket_req_u *req_u,
        }
 
 out_free_pg_vec:
+       bitmap_free(rx_owner_map);
        if (pg_vec)
                free_pg_vec(pg_vec, order, req->tp_block_nr);
 out: