af-unix: passcred support for sendpage

sendpage did not care about credentials at all. This could lead to
situations in which because of fd passing between processes we could
append data to skbs with different scm data. It is illegal to splice those
skbs together. Instead we have to allocate a new skb and if requested
fill out the scm details.

Fixes: 869e7c6248 ("net: af_unix: implement stream sendpage support")
Reported-by: Al Viro <viro@zeniv.linux.org.uk>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Eric Dumazet <edumazet@google.com>
Signed-off-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Hannes Frederic Sowa 2015-11-26 12:08:18 +01:00 коммит произвёл David S. Miller
Родитель 6e0f0331a3
Коммит 9490f886b1
1 изменённых файлов: 64 добавлений и 15 удалений

Просмотреть файл

@ -1551,6 +1551,14 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen
return err; return err;
} }
static bool unix_passcred_enabled(const struct socket *sock,
const struct sock *other)
{
return test_bit(SOCK_PASSCRED, &sock->flags) ||
!other->sk_socket ||
test_bit(SOCK_PASSCRED, &other->sk_socket->flags);
}
/* /*
* Some apps rely on write() giving SCM_CREDENTIALS * Some apps rely on write() giving SCM_CREDENTIALS
* We include credentials if source or destination socket * We include credentials if source or destination socket
@ -1561,14 +1569,41 @@ static void maybe_add_creds(struct sk_buff *skb, const struct socket *sock,
{ {
if (UNIXCB(skb).pid) if (UNIXCB(skb).pid)
return; return;
if (test_bit(SOCK_PASSCRED, &sock->flags) || if (unix_passcred_enabled(sock, other)) {
!other->sk_socket ||
test_bit(SOCK_PASSCRED, &other->sk_socket->flags)) {
UNIXCB(skb).pid = get_pid(task_tgid(current)); UNIXCB(skb).pid = get_pid(task_tgid(current));
current_uid_gid(&UNIXCB(skb).uid, &UNIXCB(skb).gid); current_uid_gid(&UNIXCB(skb).uid, &UNIXCB(skb).gid);
} }
} }
static int maybe_init_creds(struct scm_cookie *scm,
struct socket *socket,
const struct sock *other)
{
int err;
struct msghdr msg = { .msg_controllen = 0 };
err = scm_send(socket, &msg, scm, false);
if (err)
return err;
if (unix_passcred_enabled(socket, other)) {
scm->pid = get_pid(task_tgid(current));
current_uid_gid(&scm->creds.uid, &scm->creds.gid);
}
return err;
}
static bool unix_skb_scm_eq(struct sk_buff *skb,
struct scm_cookie *scm)
{
const struct unix_skb_parms *u = &UNIXCB(skb);
return u->pid == scm->pid &&
uid_eq(u->uid, scm->creds.uid) &&
gid_eq(u->gid, scm->creds.gid) &&
unix_secdata_eq(scm, skb);
}
/* /*
* Send AF_UNIX data. * Send AF_UNIX data.
*/ */
@ -1884,8 +1919,10 @@ out_err:
static ssize_t unix_stream_sendpage(struct socket *socket, struct page *page, static ssize_t unix_stream_sendpage(struct socket *socket, struct page *page,
int offset, size_t size, int flags) int offset, size_t size, int flags)
{ {
int err = 0; int err;
bool send_sigpipe = true; bool send_sigpipe = false;
bool init_scm = true;
struct scm_cookie scm;
struct sock *other, *sk = socket->sk; struct sock *other, *sk = socket->sk;
struct sk_buff *skb, *newskb = NULL, *tail = NULL; struct sk_buff *skb, *newskb = NULL, *tail = NULL;
@ -1903,7 +1940,7 @@ alloc_skb:
newskb = sock_alloc_send_pskb(sk, 0, 0, flags & MSG_DONTWAIT, newskb = sock_alloc_send_pskb(sk, 0, 0, flags & MSG_DONTWAIT,
&err, 0); &err, 0);
if (!newskb) if (!newskb)
return err; goto err;
} }
/* we must acquire readlock as we modify already present /* we must acquire readlock as we modify already present
@ -1912,12 +1949,12 @@ alloc_skb:
err = mutex_lock_interruptible(&unix_sk(other)->readlock); err = mutex_lock_interruptible(&unix_sk(other)->readlock);
if (err) { if (err) {
err = flags & MSG_DONTWAIT ? -EAGAIN : -ERESTARTSYS; err = flags & MSG_DONTWAIT ? -EAGAIN : -ERESTARTSYS;
send_sigpipe = false;
goto err; goto err;
} }
if (sk->sk_shutdown & SEND_SHUTDOWN) { if (sk->sk_shutdown & SEND_SHUTDOWN) {
err = -EPIPE; err = -EPIPE;
send_sigpipe = true;
goto err_unlock; goto err_unlock;
} }
@ -1926,17 +1963,27 @@ alloc_skb:
if (sock_flag(other, SOCK_DEAD) || if (sock_flag(other, SOCK_DEAD) ||
other->sk_shutdown & RCV_SHUTDOWN) { other->sk_shutdown & RCV_SHUTDOWN) {
err = -EPIPE; err = -EPIPE;
send_sigpipe = true;
goto err_state_unlock; goto err_state_unlock;
} }
if (init_scm) {
err = maybe_init_creds(&scm, socket, other);
if (err)
goto err_state_unlock;
init_scm = false;
}
skb = skb_peek_tail(&other->sk_receive_queue); skb = skb_peek_tail(&other->sk_receive_queue);
if (tail && tail == skb) { if (tail && tail == skb) {
skb = newskb; skb = newskb;
} else if (!skb) { } else if (!skb || !unix_skb_scm_eq(skb, &scm)) {
if (newskb) if (newskb) {
skb = newskb; skb = newskb;
else } else {
tail = skb;
goto alloc_skb; goto alloc_skb;
}
} else if (newskb) { } else if (newskb) {
/* this is fast path, we don't necessarily need to /* this is fast path, we don't necessarily need to
* call to kfree_skb even though with newskb == NULL * call to kfree_skb even though with newskb == NULL
@ -1957,6 +2004,9 @@ alloc_skb:
atomic_add(size, &sk->sk_wmem_alloc); atomic_add(size, &sk->sk_wmem_alloc);
if (newskb) { if (newskb) {
err = unix_scm_to_skb(&scm, skb, false);
if (err)
goto err_state_unlock;
spin_lock(&other->sk_receive_queue.lock); spin_lock(&other->sk_receive_queue.lock);
__skb_queue_tail(&other->sk_receive_queue, newskb); __skb_queue_tail(&other->sk_receive_queue, newskb);
spin_unlock(&other->sk_receive_queue.lock); spin_unlock(&other->sk_receive_queue.lock);
@ -1966,7 +2016,7 @@ alloc_skb:
mutex_unlock(&unix_sk(other)->readlock); mutex_unlock(&unix_sk(other)->readlock);
other->sk_data_ready(other); other->sk_data_ready(other);
scm_destroy(&scm);
return size; return size;
err_state_unlock: err_state_unlock:
@ -1977,6 +2027,8 @@ err:
kfree_skb(newskb); kfree_skb(newskb);
if (send_sigpipe && !(flags & MSG_NOSIGNAL)) if (send_sigpipe && !(flags & MSG_NOSIGNAL))
send_sig(SIGPIPE, current, 0); send_sig(SIGPIPE, current, 0);
if (!init_scm)
scm_destroy(&scm);
return err; return err;
} }
@ -2280,10 +2332,7 @@ unlock:
if (check_creds) { if (check_creds) {
/* Never glue messages from different writers */ /* Never glue messages from different writers */
if ((UNIXCB(skb).pid != scm.pid) || if (!unix_skb_scm_eq(skb, &scm))
!uid_eq(UNIXCB(skb).uid, scm.creds.uid) ||
!gid_eq(UNIXCB(skb).gid, scm.creds.gid) ||
!unix_secdata_eq(&scm, skb))
break; break;
} else if (test_bit(SOCK_PASSCRED, &sock->flags)) { } else if (test_bit(SOCK_PASSCRED, &sock->flags)) {
/* Copy credentials */ /* Copy credentials */