Good morning,

The patch attached below adds a new socket option, SO_PASSCRED, for
datagram sockets in the Unix domain. This socket option causes an
ancillary message of type SCM_CREDS to be attached by the kernel to
the datagram filled in with the credentials of the datagram sender.

SCM_CREDS messages may not be attached by the sender of the datagram
themselves (as is possible on Linux, where root may falsify that
message).

For now, this only works for datagram sockets. As stream and
sequenced-packet sockets may have peer credentials queried with
SO_PEERCRED, there is much less utility for those.

Kind regards,
David Mackay

diff --git sys/kern/uipc_socket.c sys/kern/uipc_socket.c
index 6f3f2ce4b..37a46032b 100644
--- sys/kern/uipc_socket.c
+++ sys/kern/uipc_socket.c
@@ -1866,6 +1866,19 @@ sosetopt(struct socket *so, int level, int optname, 
struct mbuf *m)
                        break;
 #endif /* SOCKET_SPLICE */
 
+               case SO_PASSCRED:
+                       if (so->so_proto->pr_protocol == AF_UNIX) {
+                               struct unpcb *unp = sotounpcb(so);
+                               if (m == NULL || m->m_len < sizeof (int))
+                                       return (EINVAL);
+                               if (*mtod(m, int *))
+                                       unp->unp_flags |= UNP_ADDCRED;
+                               else
+                                       unp->unp_flags &= ~UNP_ADDCRED;
+                               return (0);
+                       }
+                       return (EOPNOTSUPP);
+
                default:
                        error = ENOPROTOOPT;
                        break;
@@ -2017,6 +2030,14 @@ sogetopt(struct socket *so, int level, int optname, 
struct mbuf *m)
                        }
                        return (EOPNOTSUPP);
 
+               case SO_PASSCRED:
+                       if (so->so_proto->pr_protocol == AF_UNIX) {
+                               struct unpcb *unp = sotounpcb(so);
+                               *mtod(m, int *) = unp->unp_flags & UNP_ADDCRED;
+                               return (0);
+                       }
+                       return (EOPNOTSUPP);
+
                default:
                        return (ENOPROTOOPT);
                }
diff --git sys/kern/uipc_usrreq.c sys/kern/uipc_usrreq.c
index 83e62bad6..b4449fbe2 100644
--- sys/kern/uipc_usrreq.c
+++ sys/kern/uipc_usrreq.c
@@ -124,6 +124,45 @@ uipc_setaddr(const struct unpcb *unp, struct mbuf *nam)
        }
 }
 
+struct mbuf *
+unp_addcred(struct mbuf *control)
+{
+       struct cmsghdr *cm;
+       struct cmsgcred *cmcred;
+       struct mbuf *m, *n;
+       void *p;
+       int i;
+
+       m = sbcreatecontrol(&p, sizeof(struct cmsgcred), SCM_CREDS, SOL_SOCKET);
+       if (m == NULL)
+               return control;
+
+       m->m_next = NULL;
+
+       if (control == NULL)
+               control = m;
+       else {
+               n = control;
+               while (n->m_next != NULL)
+                       n = n->m_next;
+               /* append the SCM_CREDS mbuf to the end */
+               n->m_next = m;
+       }
+
+       cm = mtod(m, struct cmsghdr *);
+       cmcred = (struct cmsgcred *)CMSG_DATA(cm);
+
+       cmcred->cmcred_pid = curproc->p_p->ps_pid;
+       cmcred->cmcred_uid = curproc->p_ucred->cr_ruid;
+       cmcred->cmcred_euid = curproc->p_ucred->cr_uid;
+       cmcred->cmcred_gid = curproc->p_ucred->cr_rgid;
+       cmcred->cmcred_ngroups = MIN(curproc->p_ucred->cr_ngroups, CMGROUP_MAX);
+       for (i = 0; i < cmcred->cmcred_ngroups; i++)
+               cmcred->cmcred_groups[i] = curproc->p_ucred->cr_groups[i];
+
+       return (control);
+}
+
 int
 uipc_usrreq(struct socket *so, int req, struct mbuf *m, struct mbuf *nam,
     struct mbuf *control, struct proc *p)
@@ -245,6 +284,8 @@ uipc_usrreq(struct socket *so, int req, struct mbuf *m, 
struct mbuf *nam,
                                from = mtod(unp->unp_addr, struct sockaddr *);
                        else
                                from = &sun_noname;
+                       if (unp->unp_conn->unp_flags & UNP_ADDCRED)
+                               control = unp_addcred(control);
                        if (sbappendaddr(so2, &so2->so_rcv, from, m, control)) {
                                sorwakeup(so2);
                                m = NULL;
@@ -819,19 +860,23 @@ unp_externalize(struct mbuf *rights, socklen_t 
controllen, int flags)
 
        rw_assert_wrlock(&unp_lock);
 
+       if (controllen < CMSG_ALIGN(sizeof(struct cmsghdr)))
+               controllen = 0;
+       else
+               controllen -= CMSG_ALIGN(sizeof(struct cmsghdr));
+
        /*
-        * This code only works because SCM_RIGHTS is the only supported
-        * control message type on unix sockets. Enforce this here.
+        * These checks are applicable only to SCM_RIGHTS control messages. If
+        * this is not an SCM_RIGHTS message, there are no file descriptors to
+        * deal with in it. Any other control messages are rejected during
+        * internalisation, and therefore can only have been attached by the
+        * kernel (e.g. SCM_CREDS), so skip them.
         */
        if (cm->cmsg_type != SCM_RIGHTS || cm->cmsg_level != SOL_SOCKET)
-               return EINVAL;
+               return 0;
 
        nfds = (cm->cmsg_len - CMSG_ALIGN(sizeof(*cm))) /
-           sizeof(struct fdpass);
-       if (controllen < CMSG_ALIGN(sizeof(struct cmsghdr)))
-               controllen = 0;
-       else
-               controllen -= CMSG_ALIGN(sizeof(struct cmsghdr));
+              sizeof(struct fdpass);
        if (nfds > controllen / sizeof(int)) {
                error = EMSGSIZE;
                goto restart;
diff --git sys/sys/socket.h sys/sys/socket.h
index a8a11a988..b6d4dfd8f 100644
--- sys/sys/socket.h
+++ sys/sys/socket.h
@@ -115,6 +115,7 @@ typedef     __sa_family_t   sa_family_t;    /* sockaddr 
address family type */
 #define        SO_SPLICE       0x1023          /* splice data to other socket 
*/
 #define        SO_DOMAIN       0x1024          /* get socket domain */
 #define        SO_PROTOCOL     0x1025          /* get socket protocol */
+#define SO_PASSCRED    0x1026          /* attach credentials */
 
 /*
  * Structure used for manipulating linger option.
@@ -548,8 +549,29 @@ struct cmsghdr {
 /* Length of the space taken up by a padded control message of length len */
 #define        CMSG_SPACE(len) (_ALIGN(sizeof(struct cmsghdr)) + _ALIGN(len))
 
+/*
+ * Only 16 groups are sent because struct mbuf is a fixed size; these must fit.
+ */
+#define CMGROUP_MAX 16
+
+/*
+ * Credentials as attached to control messages of type SCM_CREDS.
+ * The sender may not atttach this message themselves (it will be rejected
+ * during internalisation); the kernel attaches it if SO_PASSCRED socket option
+ * was set.
+ */
+struct cmsgcred {
+       pid_t cmcred_pid;                 /* sender PID */
+       uid_t cmcred_uid;                 /* sender real UID */
+       uid_t cmcred_euid;                /* sender effective UID */
+       gid_t cmcred_gid;                 /* sender real GID*/
+       short cmcred_ngroups;             /* number of GIDs */
+       gid_t cmcred_groups[CMGROUP_MAX]; /* GIDs */
+};
+
 /* "Socket"-level control message types: */
-#define        SCM_RIGHTS      0x01            /* access rights (array of int) 
*/
+#define SCM_RIGHTS     0x01            /* access rights (array of int) */
+#define SCM_CREDS      0x02            /* sender creds (struct cmsgcred) */
 #define        SCM_TIMESTAMP   0x04            /* timestamp (struct timeval) */
 
 #ifndef _KERNEL
diff --git sys/sys/unpcb.h sys/sys/unpcb.h
index 0d41e1437..059606910 100644
--- sys/sys/unpcb.h
+++ sys/sys/unpcb.h
@@ -89,6 +89,7 @@ struct        unpcb {
 #define UNP_GCDEAD     0x10            /* unref'd in this pass */
 #define UNP_BINDING    0x20            /* unp is binding now */
 #define UNP_CONNECTING 0x40            /* unp is connecting now */
+#define UNP_ADDCRED    0x80            /* should attach cmsgcred */
 
 #define        sotounpcb(so)   ((struct unpcb *)((so)->so_pcb))

Reply via email to