af_unix: Allow SO_PEERCRED to work across namespaces.
Eric W. Biederman [Sun, 13 Jun 2010 03:30:14 +0000 (03:30 +0000)]
Use struct pid and struct cred to store the peer credentials on struct
sock.  This gives enough information to convert the peer credential
information to a value relative to whatever namespace the socket is in
at the time.

This removes nasty surprises when using SO_PEERCRED on socket
connetions where the processes on either side are in different pid and
user namespaces.

Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
Acked-by: Daniel Lezcano <daniel.lezcano@free.fr>
Acked-by: Pavel Emelyanov <xemul@openvz.org>
Signed-off-by: David S. Miller <davem@davemloft.net>

include/net/sock.h
net/core/sock.c
net/unix/af_unix.c

index f8acf38..4f26f2f 100644 (file)
@@ -295,7 +295,8 @@ struct sock {
        unsigned short          sk_ack_backlog;
        unsigned short          sk_max_ack_backlog;
        __u32                   sk_priority;
-       struct ucred            sk_peercred;
+       struct pid              *sk_peer_pid;
+       const struct cred       *sk_peer_cred;
        long                    sk_rcvtimeo;
        long                    sk_sndtimeo;
        struct sk_filter        *sk_filter;
index db8335a..0229d55 100644 (file)
@@ -915,11 +915,15 @@ int sock_getsockopt(struct socket *sock, int level, int optname,
                break;
 
        case SO_PEERCRED:
-               if (len > sizeof(sk->sk_peercred))
-                       len = sizeof(sk->sk_peercred);
-               if (copy_to_user(optval, &sk->sk_peercred, len))
+       {
+               struct ucred peercred;
+               if (len > sizeof(peercred))
+                       len = sizeof(peercred);
+               cred_to_ucred(sk->sk_peer_pid, sk->sk_peer_cred, &peercred);
+               if (copy_to_user(optval, &peercred, len))
                        return -EFAULT;
                goto lenout;
+       }
 
        case SO_PEERNAME:
        {
@@ -1133,6 +1137,9 @@ static void __sk_free(struct sock *sk)
                printk(KERN_DEBUG "%s: optmem leakage (%d bytes) detected.\n",
                       __func__, atomic_read(&sk->sk_omem_alloc));
 
+       if (sk->sk_peer_cred)
+               put_cred(sk->sk_peer_cred);
+       put_pid(sk->sk_peer_pid);
        put_net(sock_net(sk));
        sk_prot_free(sk->sk_prot_creator, sk);
 }
@@ -1968,9 +1975,8 @@ void sock_init_data(struct socket *sock, struct sock *sk)
        sk->sk_sndmsg_page      =       NULL;
        sk->sk_sndmsg_off       =       0;
 
-       sk->sk_peercred.pid     =       0;
-       sk->sk_peercred.uid     =       -1;
-       sk->sk_peercred.gid     =       -1;
+       sk->sk_peer_pid         =       NULL;
+       sk->sk_peer_cred        =       NULL;
        sk->sk_write_pending    =       0;
        sk->sk_rcvlowat         =       1;
        sk->sk_rcvtimeo         =       MAX_SCHEDULE_TIMEOUT;
index fef2cc5..e1f1349 100644 (file)
@@ -450,11 +450,31 @@ static int unix_release_sock(struct sock *sk, int embrion)
        return 0;
 }
 
+static void init_peercred(struct sock *sk)
+{
+       put_pid(sk->sk_peer_pid);
+       if (sk->sk_peer_cred)
+               put_cred(sk->sk_peer_cred);
+       sk->sk_peer_pid  = get_pid(task_tgid(current));
+       sk->sk_peer_cred = get_current_cred();
+}
+
+static void copy_peercred(struct sock *sk, struct sock *peersk)
+{
+       put_pid(sk->sk_peer_pid);
+       if (sk->sk_peer_cred)
+               put_cred(sk->sk_peer_cred);
+       sk->sk_peer_pid  = get_pid(peersk->sk_peer_pid);
+       sk->sk_peer_cred = get_cred(peersk->sk_peer_cred);
+}
+
 static int unix_listen(struct socket *sock, int backlog)
 {
        int err;
        struct sock *sk = sock->sk;
        struct unix_sock *u = unix_sk(sk);
+       struct pid *old_pid = NULL;
+       const struct cred *old_cred = NULL;
 
        err = -EOPNOTSUPP;
        if (sock->type != SOCK_STREAM && sock->type != SOCK_SEQPACKET)
@@ -470,12 +490,14 @@ static int unix_listen(struct socket *sock, int backlog)
        sk->sk_max_ack_backlog  = backlog;
        sk->sk_state            = TCP_LISTEN;
        /* set credentials so connect can copy them */
-       sk->sk_peercred.pid     = task_tgid_vnr(current);
-       current_euid_egid(&sk->sk_peercred.uid, &sk->sk_peercred.gid);
+       init_peercred(sk);
        err = 0;
 
 out_unlock:
        unix_state_unlock(sk);
+       put_pid(old_pid);
+       if (old_cred)
+               put_cred(old_cred);
 out:
        return err;
 }
@@ -1140,8 +1162,7 @@ restart:
        unix_peer(newsk)        = sk;
        newsk->sk_state         = TCP_ESTABLISHED;
        newsk->sk_type          = sk->sk_type;
-       newsk->sk_peercred.pid  = task_tgid_vnr(current);
-       current_euid_egid(&newsk->sk_peercred.uid, &newsk->sk_peercred.gid);
+       init_peercred(newsk);
        newu = unix_sk(newsk);
        newsk->sk_wq            = &newu->peer_wq;
        otheru = unix_sk(other);
@@ -1157,7 +1178,7 @@ restart:
        }
 
        /* Set credentials */
-       sk->sk_peercred = other->sk_peercred;
+       copy_peercred(sk, other);
 
        sock->state     = SS_CONNECTED;
        sk->sk_state    = TCP_ESTABLISHED;
@@ -1199,10 +1220,8 @@ static int unix_socketpair(struct socket *socka, struct socket *sockb)
        sock_hold(skb);
        unix_peer(ska) = skb;
        unix_peer(skb) = ska;
-       ska->sk_peercred.pid = skb->sk_peercred.pid = task_tgid_vnr(current);
-       current_euid_egid(&skb->sk_peercred.uid, &skb->sk_peercred.gid);
-       ska->sk_peercred.uid = skb->sk_peercred.uid;
-       ska->sk_peercred.gid = skb->sk_peercred.gid;
+       init_peercred(ska);
+       init_peercred(skb);
 
        if (ska->sk_type != SOCK_DGRAM) {
                ska->sk_state = TCP_ESTABLISHED;