netlink: Always copy on mmap TX.
Checking the file f_count and the nlk->mapped count is not completely
sufficient to prevent the mmap'd area contents from changing from
under us during netlink mmap sendmsg() operations.
Be careful to sample the header's length field only once, because this
could change from under us as well.
Fixes: 5fd96123ee
("netlink: implement memory mapped sendmsg()")
Signed-off-by: David S. Miller <davem@davemloft.net>
Acked-by: Daniel Borkmann <dborkman@redhat.com>
Acked-by: Thomas Graf <tgraf@suug.ch>
This commit is contained in:
parent
65891feac2
commit
4682a03586
|
@ -525,14 +525,14 @@ static int netlink_mmap(struct file *file, struct socket *sock,
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void netlink_frame_flush_dcache(const struct nl_mmap_hdr *hdr)
|
static void netlink_frame_flush_dcache(const struct nl_mmap_hdr *hdr, unsigned int nm_len)
|
||||||
{
|
{
|
||||||
#if ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE == 1
|
#if ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE == 1
|
||||||
struct page *p_start, *p_end;
|
struct page *p_start, *p_end;
|
||||||
|
|
||||||
/* First page is flushed through netlink_{get,set}_status */
|
/* First page is flushed through netlink_{get,set}_status */
|
||||||
p_start = pgvec_to_page(hdr + PAGE_SIZE);
|
p_start = pgvec_to_page(hdr + PAGE_SIZE);
|
||||||
p_end = pgvec_to_page((void *)hdr + NL_MMAP_HDRLEN + hdr->nm_len - 1);
|
p_end = pgvec_to_page((void *)hdr + NL_MMAP_HDRLEN + nm_len - 1);
|
||||||
while (p_start <= p_end) {
|
while (p_start <= p_end) {
|
||||||
flush_dcache_page(p_start);
|
flush_dcache_page(p_start);
|
||||||
p_start++;
|
p_start++;
|
||||||
|
@ -714,24 +714,16 @@ static int netlink_mmap_sendmsg(struct sock *sk, struct msghdr *msg,
|
||||||
struct nl_mmap_hdr *hdr;
|
struct nl_mmap_hdr *hdr;
|
||||||
struct sk_buff *skb;
|
struct sk_buff *skb;
|
||||||
unsigned int maxlen;
|
unsigned int maxlen;
|
||||||
bool excl = true;
|
|
||||||
int err = 0, len = 0;
|
int err = 0, len = 0;
|
||||||
|
|
||||||
/* Netlink messages are validated by the receiver before processing.
|
|
||||||
* In order to avoid userspace changing the contents of the message
|
|
||||||
* after validation, the socket and the ring may only be used by a
|
|
||||||
* single process, otherwise we fall back to copying.
|
|
||||||
*/
|
|
||||||
if (atomic_long_read(&sk->sk_socket->file->f_count) > 1 ||
|
|
||||||
atomic_read(&nlk->mapped) > 1)
|
|
||||||
excl = false;
|
|
||||||
|
|
||||||
mutex_lock(&nlk->pg_vec_lock);
|
mutex_lock(&nlk->pg_vec_lock);
|
||||||
|
|
||||||
ring = &nlk->tx_ring;
|
ring = &nlk->tx_ring;
|
||||||
maxlen = ring->frame_size - NL_MMAP_HDRLEN;
|
maxlen = ring->frame_size - NL_MMAP_HDRLEN;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
unsigned int nm_len;
|
||||||
|
|
||||||
hdr = netlink_current_frame(ring, NL_MMAP_STATUS_VALID);
|
hdr = netlink_current_frame(ring, NL_MMAP_STATUS_VALID);
|
||||||
if (hdr == NULL) {
|
if (hdr == NULL) {
|
||||||
if (!(msg->msg_flags & MSG_DONTWAIT) &&
|
if (!(msg->msg_flags & MSG_DONTWAIT) &&
|
||||||
|
@ -739,35 +731,23 @@ static int netlink_mmap_sendmsg(struct sock *sk, struct msghdr *msg,
|
||||||
schedule();
|
schedule();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (hdr->nm_len > maxlen) {
|
|
||||||
|
nm_len = ACCESS_ONCE(hdr->nm_len);
|
||||||
|
if (nm_len > maxlen) {
|
||||||
err = -EINVAL;
|
err = -EINVAL;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
netlink_frame_flush_dcache(hdr);
|
netlink_frame_flush_dcache(hdr, nm_len);
|
||||||
|
|
||||||
if (likely(dst_portid == 0 && dst_group == 0 && excl)) {
|
skb = alloc_skb(nm_len, GFP_KERNEL);
|
||||||
skb = alloc_skb_head(GFP_KERNEL);
|
if (skb == NULL) {
|
||||||
if (skb == NULL) {
|
err = -ENOBUFS;
|
||||||
err = -ENOBUFS;
|
goto out;
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
sock_hold(sk);
|
|
||||||
netlink_ring_setup_skb(skb, sk, ring, hdr);
|
|
||||||
NETLINK_CB(skb).flags |= NETLINK_SKB_TX;
|
|
||||||
__skb_put(skb, hdr->nm_len);
|
|
||||||
netlink_set_status(hdr, NL_MMAP_STATUS_RESERVED);
|
|
||||||
atomic_inc(&ring->pending);
|
|
||||||
} else {
|
|
||||||
skb = alloc_skb(hdr->nm_len, GFP_KERNEL);
|
|
||||||
if (skb == NULL) {
|
|
||||||
err = -ENOBUFS;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
__skb_put(skb, hdr->nm_len);
|
|
||||||
memcpy(skb->data, (void *)hdr + NL_MMAP_HDRLEN, hdr->nm_len);
|
|
||||||
netlink_set_status(hdr, NL_MMAP_STATUS_UNUSED);
|
|
||||||
}
|
}
|
||||||
|
__skb_put(skb, nm_len);
|
||||||
|
memcpy(skb->data, (void *)hdr + NL_MMAP_HDRLEN, nm_len);
|
||||||
|
netlink_set_status(hdr, NL_MMAP_STATUS_UNUSED);
|
||||||
|
|
||||||
netlink_increment_head(ring);
|
netlink_increment_head(ring);
|
||||||
|
|
||||||
|
@ -813,7 +793,7 @@ static void netlink_queue_mmaped_skb(struct sock *sk, struct sk_buff *skb)
|
||||||
hdr->nm_pid = NETLINK_CB(skb).creds.pid;
|
hdr->nm_pid = NETLINK_CB(skb).creds.pid;
|
||||||
hdr->nm_uid = from_kuid(sk_user_ns(sk), NETLINK_CB(skb).creds.uid);
|
hdr->nm_uid = from_kuid(sk_user_ns(sk), NETLINK_CB(skb).creds.uid);
|
||||||
hdr->nm_gid = from_kgid(sk_user_ns(sk), NETLINK_CB(skb).creds.gid);
|
hdr->nm_gid = from_kgid(sk_user_ns(sk), NETLINK_CB(skb).creds.gid);
|
||||||
netlink_frame_flush_dcache(hdr);
|
netlink_frame_flush_dcache(hdr, hdr->nm_len);
|
||||||
netlink_set_status(hdr, NL_MMAP_STATUS_VALID);
|
netlink_set_status(hdr, NL_MMAP_STATUS_VALID);
|
||||||
|
|
||||||
NETLINK_CB(skb).flags |= NETLINK_SKB_DELIVERED;
|
NETLINK_CB(skb).flags |= NETLINK_SKB_DELIVERED;
|
||||||
|
|
Loading…
Reference in New Issue