packet: fix bitfield update race

commit a6361f0ca4b25460f2cdf3235ebe8115f622901e upstream.

Updates to the bitfields in struct packet_sock are not atomic.
Serialize these read-modify-write cycles.

Move po->running into a separate variable. Its writes are protected by
po->bind_lock (except for one startup case at packet_create). Also
replace a textual precondition warning with lockdep annotation.

All others are set only in packet_setsockopt. Serialize these
updates by holding the socket lock. Analogous to other field updates,
also hold the lock when testing whether a ring is active (pg_vec).

Fixes: 8dc4194474 ("[PACKET]: Add optional checksum computation for recvmsg")
Change-Id: I63f2f4842edd2ff320b074faf167df406d49b56f
Reported-by: DaeRyong Jeong <threeearcat@gmail.com>
Reported-by: Byoungyoung Lee <byoungyoung@purdue.edu>
Signed-off-by: Willem de Bruijn <willemb@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
[bwh: Backported to 3.16: adjust context]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
This commit is contained in:
Willem de Bruijn 2018-04-23 17:37:03 -04:00 committed by syphyr
parent f0f28197a7
commit 386afd6dc6
2 changed files with 49 additions and 21 deletions

View File

@ -261,11 +261,11 @@ static void packet_cached_dev_reset(struct packet_sock *po)
RCU_INIT_POINTER(po->cached_dev, NULL); RCU_INIT_POINTER(po->cached_dev, NULL);
} }
/* register_prot_hook must be invoked with the po->bind_lock held, /* __register_prot_hook must be invoked through register_prot_hook
* or from a context in which asynchronous accesses to the packet * or from a context in which asynchronous accesses to the packet
* socket is not possible (packet_create()). * socket is not possible (packet_create()).
*/ */
static void register_prot_hook(struct sock *sk) static void __register_prot_hook(struct sock *sk)
{ {
struct packet_sock *po = pkt_sk(sk); struct packet_sock *po = pkt_sk(sk);
@ -280,8 +280,13 @@ static void register_prot_hook(struct sock *sk)
} }
} }
/* {,__}unregister_prot_hook() must be invoked with the po->bind_lock static void register_prot_hook(struct sock *sk)
* held. If the sync parameter is true, we will temporarily drop {
lockdep_assert_held_once(&pkt_sk(sk)->bind_lock);
__register_prot_hook(sk);
}
/* If the sync parameter is true, we will temporarily drop
* the po->bind_lock and do a synchronize_net to make sure no * the po->bind_lock and do a synchronize_net to make sure no
* asynchronous packet processing paths still refer to the elements * asynchronous packet processing paths still refer to the elements
* of po->prot_hook. If the sync parameter is false, it is the * of po->prot_hook. If the sync parameter is false, it is the
@ -291,6 +296,8 @@ static void __unregister_prot_hook(struct sock *sk, bool sync)
{ {
struct packet_sock *po = pkt_sk(sk); struct packet_sock *po = pkt_sk(sk);
lockdep_assert_held_once(&po->bind_lock);
po->running = 0; po->running = 0;
if (po->fanout) if (po->fanout)
@ -2711,7 +2718,7 @@ static int packet_create(struct net *net, struct socket *sock, int protocol,
if (proto) { if (proto) {
po->prot_hook.type = proto; po->prot_hook.type = proto;
register_prot_hook(sk); __register_prot_hook(sk);
} }
mutex_lock(&net->packet.sklist_lock); mutex_lock(&net->packet.sklist_lock);
@ -3252,12 +3259,18 @@ packet_setsockopt(struct socket *sock, int level, int optname, char __user *optv
if (optlen != sizeof(val)) if (optlen != sizeof(val))
return -EINVAL; return -EINVAL;
if (po->rx_ring.pg_vec || po->tx_ring.pg_vec)
return -EBUSY;
if (copy_from_user(&val, optval, sizeof(val))) if (copy_from_user(&val, optval, sizeof(val)))
return -EFAULT; return -EFAULT;
po->tp_loss = !!val;
return 0; lock_sock(sk);
if (po->rx_ring.pg_vec || po->tx_ring.pg_vec) {
ret = -EBUSY;
} else {
po->tp_loss = !!val;
ret = 0;
}
release_sock(sk);
return ret;
} }
case PACKET_AUXDATA: case PACKET_AUXDATA:
{ {
@ -3268,7 +3281,9 @@ packet_setsockopt(struct socket *sock, int level, int optname, char __user *optv
if (copy_from_user(&val, optval, sizeof(val))) if (copy_from_user(&val, optval, sizeof(val)))
return -EFAULT; return -EFAULT;
lock_sock(sk);
po->auxdata = !!val; po->auxdata = !!val;
release_sock(sk);
return 0; return 0;
} }
case PACKET_ORIGDEV: case PACKET_ORIGDEV:
@ -3280,7 +3295,9 @@ packet_setsockopt(struct socket *sock, int level, int optname, char __user *optv
if (copy_from_user(&val, optval, sizeof(val))) if (copy_from_user(&val, optval, sizeof(val)))
return -EFAULT; return -EFAULT;
lock_sock(sk);
po->origdev = !!val; po->origdev = !!val;
release_sock(sk);
return 0; return 0;
} }
case PACKET_VNET_HDR: case PACKET_VNET_HDR:
@ -3289,15 +3306,20 @@ packet_setsockopt(struct socket *sock, int level, int optname, char __user *optv
if (sock->type != SOCK_RAW) if (sock->type != SOCK_RAW)
return -EINVAL; return -EINVAL;
if (po->rx_ring.pg_vec || po->tx_ring.pg_vec)
return -EBUSY;
if (optlen < sizeof(val)) if (optlen < sizeof(val))
return -EINVAL; return -EINVAL;
if (copy_from_user(&val, optval, sizeof(val))) if (copy_from_user(&val, optval, sizeof(val)))
return -EFAULT; return -EFAULT;
po->has_vnet_hdr = !!val; lock_sock(sk);
return 0; if (po->rx_ring.pg_vec || po->tx_ring.pg_vec) {
ret = -EBUSY;
} else {
po->has_vnet_hdr = !!val;
ret = 0;
}
release_sock(sk);
return ret;
} }
case PACKET_TIMESTAMP: case PACKET_TIMESTAMP:
{ {
@ -3328,11 +3350,17 @@ packet_setsockopt(struct socket *sock, int level, int optname, char __user *optv
if (optlen != sizeof(val)) if (optlen != sizeof(val))
return -EINVAL; return -EINVAL;
if (po->rx_ring.pg_vec || po->tx_ring.pg_vec)
return -EBUSY;
if (copy_from_user(&val, optval, sizeof(val))) if (copy_from_user(&val, optval, sizeof(val)))
return -EFAULT; return -EFAULT;
po->tp_tx_has_off = !!val;
lock_sock(sk);
if (po->rx_ring.pg_vec || po->tx_ring.pg_vec) {
ret = -EBUSY;
} else {
po->tp_tx_has_off = !!val;
ret = 0;
}
release_sock(sk);
return 0; return 0;
} }
default: default:

View File

@ -100,10 +100,12 @@ struct packet_sock {
int copy_thresh; int copy_thresh;
spinlock_t bind_lock; spinlock_t bind_lock;
struct mutex pg_vec_lock; struct mutex pg_vec_lock;
unsigned int running:1, /* prot_hook is attached*/ unsigned int running; /* bind_lock must be held */
auxdata:1, unsigned int auxdata:1, /* writer must hold sock lock */
origdev:1, origdev:1,
has_vnet_hdr:1; has_vnet_hdr:1,
tp_loss:1,
tp_tx_has_off:1;
int ifindex; /* bound device */ int ifindex; /* bound device */
__be16 num; __be16 num;
struct packet_mclist *mclist; struct packet_mclist *mclist;
@ -111,8 +113,6 @@ struct packet_sock {
enum tpacket_versions tp_version; enum tpacket_versions tp_version;
unsigned int tp_hdrlen; unsigned int tp_hdrlen;
unsigned int tp_reserve; unsigned int tp_reserve;
unsigned int tp_loss:1;
unsigned int tp_tx_has_off:1;
unsigned int tp_tstamp; unsigned int tp_tstamp;
struct net_device __rcu *cached_dev; struct net_device __rcu *cached_dev;
struct packet_type prot_hook ____cacheline_aligned_in_smp; struct packet_type prot_hook ____cacheline_aligned_in_smp;