af_unix: fix races in sk_peer_pid and sk_peer_cred accesses
[ Upstream commit 35306eb23814444bd4021f8a1c3047d3cb0c8b2b ] Jann Horn reported that SO_PEERCRED and SO_PEERGROUPS implementations are racy, as af_unix can concurrently change sk_peer_pid and sk_peer_cred. In order to fix this issue, this patch adds a new spinlock that needs to be used whenever these fields are read or written. Jann also pointed out that l2cap_sock_get_peer_pid_cb() is currently reading sk->sk_peer_pid which makes no sense, as this field is only possibly set by AF_UNIX sockets. We will have to clean this in a separate patch. This could be done by revertingb48596d1dc
"Bluetooth: L2CAP: Add get_peer_pid callback" or implementing what was truly expected. Fixes:109f6e39fa
("af_unix: Allow SO_PEERCRED to work across namespaces.") Signed-off-by: Eric Dumazet <edumazet@google.com> Reported-by: Jann Horn <jannh@google.com> Cc: Eric W. Biederman <ebiederm@xmission.com> Cc: Luiz Augusto von Dentz <luiz.von.dentz@intel.com> Cc: Marcel Holtmann <marcel@holtmann.org> Signed-off-by: David S. Miller <davem@davemloft.net> Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:

committed by
Greg Kroah-Hartman

parent
d0d520c19e
commit
3db53827a0
@@ -479,8 +479,10 @@ struct sock {
|
|||||||
u32 sk_ack_backlog;
|
u32 sk_ack_backlog;
|
||||||
u32 sk_max_ack_backlog;
|
u32 sk_max_ack_backlog;
|
||||||
kuid_t sk_uid;
|
kuid_t sk_uid;
|
||||||
|
spinlock_t sk_peer_lock;
|
||||||
struct pid *sk_peer_pid;
|
struct pid *sk_peer_pid;
|
||||||
const struct cred *sk_peer_cred;
|
const struct cred *sk_peer_cred;
|
||||||
|
|
||||||
long sk_rcvtimeo;
|
long sk_rcvtimeo;
|
||||||
ktime_t sk_stamp;
|
ktime_t sk_stamp;
|
||||||
#if BITS_PER_LONG==32
|
#if BITS_PER_LONG==32
|
||||||
|
@@ -1255,6 +1255,16 @@ set_sndbuf:
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL(sock_setsockopt);
|
EXPORT_SYMBOL(sock_setsockopt);
|
||||||
|
|
||||||
|
static const struct cred *sk_get_peer_cred(struct sock *sk)
|
||||||
|
{
|
||||||
|
const struct cred *cred;
|
||||||
|
|
||||||
|
spin_lock(&sk->sk_peer_lock);
|
||||||
|
cred = get_cred(sk->sk_peer_cred);
|
||||||
|
spin_unlock(&sk->sk_peer_lock);
|
||||||
|
|
||||||
|
return cred;
|
||||||
|
}
|
||||||
|
|
||||||
static void cred_to_ucred(struct pid *pid, const struct cred *cred,
|
static void cred_to_ucred(struct pid *pid, const struct cred *cred,
|
||||||
struct ucred *ucred)
|
struct ucred *ucred)
|
||||||
@@ -1428,7 +1438,11 @@ int sock_getsockopt(struct socket *sock, int level, int optname,
|
|||||||
struct ucred peercred;
|
struct ucred peercred;
|
||||||
if (len > sizeof(peercred))
|
if (len > sizeof(peercred))
|
||||||
len = sizeof(peercred);
|
len = sizeof(peercred);
|
||||||
|
|
||||||
|
spin_lock(&sk->sk_peer_lock);
|
||||||
cred_to_ucred(sk->sk_peer_pid, sk->sk_peer_cred, &peercred);
|
cred_to_ucred(sk->sk_peer_pid, sk->sk_peer_cred, &peercred);
|
||||||
|
spin_unlock(&sk->sk_peer_lock);
|
||||||
|
|
||||||
if (copy_to_user(optval, &peercred, len))
|
if (copy_to_user(optval, &peercred, len))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
goto lenout;
|
goto lenout;
|
||||||
@@ -1436,20 +1450,23 @@ int sock_getsockopt(struct socket *sock, int level, int optname,
|
|||||||
|
|
||||||
case SO_PEERGROUPS:
|
case SO_PEERGROUPS:
|
||||||
{
|
{
|
||||||
|
const struct cred *cred;
|
||||||
int ret, n;
|
int ret, n;
|
||||||
|
|
||||||
if (!sk->sk_peer_cred)
|
cred = sk_get_peer_cred(sk);
|
||||||
|
if (!cred)
|
||||||
return -ENODATA;
|
return -ENODATA;
|
||||||
|
|
||||||
n = sk->sk_peer_cred->group_info->ngroups;
|
n = cred->group_info->ngroups;
|
||||||
if (len < n * sizeof(gid_t)) {
|
if (len < n * sizeof(gid_t)) {
|
||||||
len = n * sizeof(gid_t);
|
len = n * sizeof(gid_t);
|
||||||
|
put_cred(cred);
|
||||||
return put_user(len, optlen) ? -EFAULT : -ERANGE;
|
return put_user(len, optlen) ? -EFAULT : -ERANGE;
|
||||||
}
|
}
|
||||||
len = n * sizeof(gid_t);
|
len = n * sizeof(gid_t);
|
||||||
|
|
||||||
ret = groups_to_user((gid_t __user *)optval,
|
ret = groups_to_user((gid_t __user *)optval, cred->group_info);
|
||||||
sk->sk_peer_cred->group_info);
|
put_cred(cred);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
goto lenout;
|
goto lenout;
|
||||||
@@ -1788,9 +1805,10 @@ static void __sk_destruct(struct rcu_head *head)
|
|||||||
sk->sk_frag.page = NULL;
|
sk->sk_frag.page = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sk->sk_peer_cred)
|
/* We do not need to acquire sk->sk_peer_lock, we are the last user. */
|
||||||
put_cred(sk->sk_peer_cred);
|
put_cred(sk->sk_peer_cred);
|
||||||
put_pid(sk->sk_peer_pid);
|
put_pid(sk->sk_peer_pid);
|
||||||
|
|
||||||
if (likely(sk->sk_net_refcnt))
|
if (likely(sk->sk_net_refcnt))
|
||||||
put_net(sock_net(sk));
|
put_net(sock_net(sk));
|
||||||
sk_prot_free(sk->sk_prot_creator, sk);
|
sk_prot_free(sk->sk_prot_creator, sk);
|
||||||
@@ -3000,6 +3018,8 @@ void sock_init_data(struct socket *sock, struct sock *sk)
|
|||||||
|
|
||||||
sk->sk_peer_pid = NULL;
|
sk->sk_peer_pid = NULL;
|
||||||
sk->sk_peer_cred = NULL;
|
sk->sk_peer_cred = NULL;
|
||||||
|
spin_lock_init(&sk->sk_peer_lock);
|
||||||
|
|
||||||
sk->sk_write_pending = 0;
|
sk->sk_write_pending = 0;
|
||||||
sk->sk_rcvlowat = 1;
|
sk->sk_rcvlowat = 1;
|
||||||
sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT;
|
sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT;
|
||||||
|
@@ -593,20 +593,42 @@ static void unix_release_sock(struct sock *sk, int embrion)
|
|||||||
|
|
||||||
static void init_peercred(struct sock *sk)
|
static void init_peercred(struct sock *sk)
|
||||||
{
|
{
|
||||||
put_pid(sk->sk_peer_pid);
|
const struct cred *old_cred;
|
||||||
if (sk->sk_peer_cred)
|
struct pid *old_pid;
|
||||||
put_cred(sk->sk_peer_cred);
|
|
||||||
|
spin_lock(&sk->sk_peer_lock);
|
||||||
|
old_pid = sk->sk_peer_pid;
|
||||||
|
old_cred = sk->sk_peer_cred;
|
||||||
sk->sk_peer_pid = get_pid(task_tgid(current));
|
sk->sk_peer_pid = get_pid(task_tgid(current));
|
||||||
sk->sk_peer_cred = get_current_cred();
|
sk->sk_peer_cred = get_current_cred();
|
||||||
|
spin_unlock(&sk->sk_peer_lock);
|
||||||
|
|
||||||
|
put_pid(old_pid);
|
||||||
|
put_cred(old_cred);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void copy_peercred(struct sock *sk, struct sock *peersk)
|
static void copy_peercred(struct sock *sk, struct sock *peersk)
|
||||||
{
|
{
|
||||||
put_pid(sk->sk_peer_pid);
|
const struct cred *old_cred;
|
||||||
if (sk->sk_peer_cred)
|
struct pid *old_pid;
|
||||||
put_cred(sk->sk_peer_cred);
|
|
||||||
|
if (sk < peersk) {
|
||||||
|
spin_lock(&sk->sk_peer_lock);
|
||||||
|
spin_lock_nested(&peersk->sk_peer_lock, SINGLE_DEPTH_NESTING);
|
||||||
|
} else {
|
||||||
|
spin_lock(&peersk->sk_peer_lock);
|
||||||
|
spin_lock_nested(&sk->sk_peer_lock, SINGLE_DEPTH_NESTING);
|
||||||
|
}
|
||||||
|
old_pid = sk->sk_peer_pid;
|
||||||
|
old_cred = sk->sk_peer_cred;
|
||||||
sk->sk_peer_pid = get_pid(peersk->sk_peer_pid);
|
sk->sk_peer_pid = get_pid(peersk->sk_peer_pid);
|
||||||
sk->sk_peer_cred = get_cred(peersk->sk_peer_cred);
|
sk->sk_peer_cred = get_cred(peersk->sk_peer_cred);
|
||||||
|
|
||||||
|
spin_unlock(&sk->sk_peer_lock);
|
||||||
|
spin_unlock(&peersk->sk_peer_lock);
|
||||||
|
|
||||||
|
put_pid(old_pid);
|
||||||
|
put_cred(old_cred);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int unix_listen(struct socket *sock, int backlog)
|
static int unix_listen(struct socket *sock, int backlog)
|
||||||
|
Reference in New Issue
Block a user