From 0c1bb4fbf1a07495aa163806e00ea56872c97286 Mon Sep 17 00:00:00 2001 From: Dima Dorfman <dd@FreeBSD.org> Date: Fri, 17 Aug 2001 22:01:18 +0000 Subject: [PATCH] Implement a LOCAL_PEERCRED socket option which returns a `struct xucred` with the credentials of the connected peer. Obviously this only works (and makes sense) on SOCK_STREAM sockets. This works for both the connect(2) and listen(2) callers. There is precise documentation of the semantics in unix(4). Reviewed by: dwmalone (eyeballed) --- share/man/man4/unix.4 | 31 +++++++++++++++- sys/kern/uipc_proto.c | 2 +- sys/kern/uipc_usrreq.c | 83 +++++++++++++++++++++++++++++++++++++++++- sys/sys/un.h | 4 ++ sys/sys/unpcb.h | 19 ++++++++++ 5 files changed, 135 insertions(+), 4 deletions(-) diff --git a/share/man/man4/unix.4 b/share/man/man4/unix.4 index 7356edb19b0d..53535b63d352 100644 --- a/share/man/man4/unix.4 +++ b/share/man/man4/unix.4 @@ -32,7 +32,7 @@ .\" @(#)unix.4 8.1 (Berkeley) 6/9/93 .\" $FreeBSD$ .\" -.Dd June 9, 1993 +.Dd July 15, 2001 .Dt UNIX 4 .Os .Sh NAME @@ -147,6 +147,35 @@ passed to a receiver. Descriptors that are awaiting delivery, or that are purposely not received, are automatically closed by the system when the destination socket is closed. +.Pp +The effective credentials (i.e., the user ID and group list) the of a +peer on a +.Dv SOCK_STREAM +socket may be obtained using the +.Dv LOCAL_PEERCRED +socket option. +This may be used by a server to obtain and verify the credentials of +its client, and vice versa by the client to verify the credentials +of the server. +These will arrive in the form of a filled in +.Ar struct xucred +(defined in +.Pa sys/ucred.h ) . +The credentials presented to the server (the +.Xr listen 2 +caller) are those of the client when it called +.Xr connect 2 ; +the credentials presented to the client (the +.Xr connect 2 +caller) are those of the server when it called +.Xr listen 2 . +This mechanism is reliable; there is no way for either party to influence +the credentials presented to its peer except by calling the appropriate +system call (e.g., +.Xr connect 2 +or +.Xr listen 2 ) +under different effective credentials. .Sh SEE ALSO .Xr socket 2 , .Xr intro 4 diff --git a/sys/kern/uipc_proto.c b/sys/kern/uipc_proto.c index ac01aafe781c..74dab7864a9a 100644 --- a/sys/kern/uipc_proto.c +++ b/sys/kern/uipc_proto.c @@ -51,7 +51,7 @@ static struct protosw localsw[] = { { SOCK_STREAM, &localdomain, 0, PR_CONNREQUIRED|PR_WANTRCVD|PR_RIGHTS, - 0, 0, 0, 0, + 0, 0, 0, &uipc_ctloutput, 0, 0, 0, 0, 0, &uipc_usrreqs diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c index 2907b4e64f0c..600fb5c766ec 100644 --- a/sys/kern/uipc_usrreq.c +++ b/sys/kern/uipc_usrreq.c @@ -91,6 +91,7 @@ static void unp_scan __P((struct mbuf *, void (*)(struct file *))); static void unp_mark __P((struct file *)); static void unp_discard __P((struct file *)); static int unp_internalize __P((struct mbuf *, struct proc *)); +static int unp_listen __P((struct unpcb *, struct proc *)); static int uipc_abort(struct socket *so) @@ -199,7 +200,7 @@ uipc_listen(struct socket *so, struct proc *p) if (unp == 0 || unp->unp_vnode == 0) return EINVAL; - return 0; + return unp_listen(unp, p); } static int @@ -434,6 +435,41 @@ struct pr_usrreqs uipc_usrreqs = { uipc_send, uipc_sense, uipc_shutdown, uipc_sockaddr, sosend, soreceive, sopoll }; + +int +uipc_ctloutput(so, sopt) + struct socket *so; + struct sockopt *sopt; +{ + struct unpcb *unp = sotounpcb(so); + int error; + + switch (sopt->sopt_dir) { + case SOPT_GET: + switch (sopt->sopt_name) { + case LOCAL_PEERCRED: + if (unp->unp_flags & UNP_HAVEPC) + error = sooptcopyout(sopt, &unp->unp_peercred, + sizeof(unp->unp_peercred)); + else { + if (so->so_type == SOCK_STREAM) + error = ENOTCONN; + else + error = EINVAL; + } + break; + default: + error = EOPNOTSUPP; + break; + } + break; + case SOPT_SET: + default: + error = EOPNOTSUPP; + break; + } + return (error); +} /* * Both send and receive buffers are allocated PIPSIZ bytes of buffering @@ -609,7 +645,7 @@ unp_connect(so, nam, p) register struct sockaddr_un *soun = (struct sockaddr_un *)nam; register struct vnode *vp; register struct socket *so2, *so3; - struct unpcb *unp2, *unp3; + struct unpcb *unp, *unp2, *unp3; int error, len; struct nameidata nd; char buf[SOCK_MAXADDRLEN]; @@ -648,12 +684,40 @@ unp_connect(so, nam, p) error = ECONNREFUSED; goto bad; } + unp = sotounpcb(so); unp2 = sotounpcb(so2); unp3 = sotounpcb(so3); if (unp2->unp_addr) unp3->unp_addr = (struct sockaddr_un *) dup_sockaddr((struct sockaddr *) unp2->unp_addr, 1); + + /* + * unp_peercred management: + * + * The connecter's (client's) credentials are copied + * from its process structure at the time of connect() + * (which is now). + */ + memset(&unp3->unp_peercred, '\0', sizeof(unp3->unp_peercred)); + unp3->unp_peercred.cr_uid = p->p_ucred->cr_uid; + unp3->unp_peercred.cr_ngroups = p->p_ucred->cr_ngroups; + memcpy(unp3->unp_peercred.cr_groups, p->p_ucred->cr_groups, + sizeof(unp3->unp_peercred.cr_groups)); + unp3->unp_flags |= UNP_HAVEPC; + /* + * The receiver's (server's) credentials are copied + * from the unp_peercred member of socket on which the + * former called listen(); unp_listen() cached that + * process's credentials at that time so we can use + * them now. + */ + KASSERT(unp2->unp_flags & UNP_HAVEPCCACHED, + ("unp_connect: listener without cached peercred")); + memcpy(&unp->unp_peercred, &unp2->unp_peercred, + sizeof(unp->unp_peercred)); + unp->unp_flags |= UNP_HAVEPC; + so2 = so3; } error = unp_connect2(so, so2); @@ -1244,6 +1308,21 @@ unp_dispose(m) unp_scan(m, unp_discard); } +static int +unp_listen(unp, p) + struct unpcb *unp; + struct proc *p; +{ + + bzero(&unp->unp_peercred, sizeof(unp->unp_peercred)); + unp->unp_peercred.cr_uid = p->p_ucred->cr_uid; + unp->unp_peercred.cr_ngroups = p->p_ucred->cr_ngroups; + bcopy(p->p_ucred->cr_groups, unp->unp_peercred.cr_groups, + sizeof(unp->unp_peercred.cr_groups)); + unp->unp_flags |= UNP_HAVEPCCACHED; + return (0); +} + static void unp_scan(m0, op) register struct mbuf *m0; diff --git a/sys/sys/un.h b/sys/sys/un.h index e7c370195a50..710f995b7614 100644 --- a/sys/sys/un.h +++ b/sys/sys/un.h @@ -46,12 +46,16 @@ struct sockaddr_un { char sun_path[104]; /* path name (gag) */ }; +/* Socket options. */ +#define LOCAL_PEERCRED 0x001 /* retrieve peer credentails */ + #ifdef _KERNEL struct mbuf; struct socket; int uipc_usrreq __P((struct socket *so, int req, struct mbuf *m, struct mbuf *nam, struct mbuf *control)); +int uipc_ctloutput __P((struct socket *so, struct sockopt *sopt)); int unp_connect2 __P((struct socket *so, struct socket *so2)); void unp_dispose __P((struct mbuf *m)); int unp_externalize __P((struct mbuf *rights)); diff --git a/sys/sys/unpcb.h b/sys/sys/unpcb.h index c0b8a373a197..cb9d1faab1c7 100644 --- a/sys/sys/unpcb.h +++ b/sys/sys/unpcb.h @@ -38,6 +38,7 @@ #define _SYS_UNPCB_H_ #include <sys/queue.h> +#include <sys/ucred.h> /* * Protocol control block for an active @@ -80,8 +81,26 @@ struct unpcb { int unp_cc; /* copy of rcv.sb_cc */ int unp_mbcnt; /* copy of rcv.sb_mbcnt */ unp_gen_t unp_gencnt; /* generation count of this instance */ + int unp_flags; /* flags */ + struct xucred unp_peercred; /* peer credentials, if applicable */ }; +/* + * Flags in unp_flags. + * + * UNP_HAVEPC - indicates that the unp_peercred member is filled in + * and is really the credentials of the connected peer. This is used + * to determine whether the contents should be sent to the user or + * not. + * + * UNP_HAVEPCCACHED - indicates that the unp_peercred member is filled + * in, but does *not* contain the credentials of the connected peer + * (there may not even be a peer). This is set in unp_listen() when + * it fills in unp_peercred for later consumption by unp_connect(). + */ +#define UNP_HAVEPC 0x001 +#define UNP_HAVEPCCACHED 0x002 + #define sotounpcb(so) ((struct unpcb *)((so)->so_pcb)) /* Hack alert -- this structure depends on <sys/socketvar.h>. */