Author: avg
Date: Sat Mar 26 11:05:53 2011
New Revision: 220031
URL: http://svn.freebsd.org/changeset/base/220031

Log:
  linux compat: improve and fix sendmsg/recvmsg compatibility
  
  - implement baseic stubs for capget, capset, prctl PR_GET_KEEPCAPS
    and prctl PR_SET_KEEPCAPS.
  - add SCM_CREDS support to sendmsg and recvmsg
  - modify sendmsg to ignore control messages if not using UNIX
    domain sockets
  
  This should allow linux pulse audio daemon and client work on FreeBSD
  and interoperate with native counter-parts modulo the differences in
  pulseaudio versions.
  
  PR:           kern/149168
  Submitted by: John Wehle <j...@feith.com>
  Reviewed by:  netchild
  MFC after:    2 weeks

Modified:
  head/sys/compat/linux/linux_misc.c
  head/sys/compat/linux/linux_misc.h
  head/sys/compat/linux/linux_socket.c
  head/sys/compat/linux/linux_socket.h

Modified: head/sys/compat/linux/linux_misc.c
==============================================================================
--- head/sys/compat/linux/linux_misc.c  Sat Mar 26 10:59:24 2011        
(r220030)
+++ head/sys/compat/linux/linux_misc.c  Sat Mar 26 11:05:53 2011        
(r220031)
@@ -1679,6 +1679,100 @@ linux_exit_group(struct thread *td, stru
        return (0);
 }
 
+#define _LINUX_CAPABILITY_VERSION  0x19980330
+
+struct l_user_cap_header {
+       l_int   version;
+       l_int   pid;
+};
+
+struct l_user_cap_data {
+       l_int   effective;
+       l_int   permitted;
+       l_int   inheritable;
+};
+
+int
+linux_capget(struct thread *td, struct linux_capget_args *args)
+{
+       struct l_user_cap_header luch;
+       struct l_user_cap_data lucd;
+       int error;
+
+       if (args->hdrp == NULL)
+               return (EFAULT);
+
+       error = copyin(args->hdrp, &luch, sizeof(luch));
+       if (error != 0)
+               return (error);
+
+       if (luch.version != _LINUX_CAPABILITY_VERSION) {
+               luch.version = _LINUX_CAPABILITY_VERSION;
+               error = copyout(&luch, args->hdrp, sizeof(luch));
+               if (error)
+                       return (error);
+               return (EINVAL);
+       }
+
+       if (luch.pid)
+               return (EPERM);
+
+       if (args->datap) {
+               /*
+                * The current implementation doesn't support setting
+                * a capability (it's essentially a stub) so indicate
+                * that no capabilities are currently set or available
+                * to request.
+                */
+               bzero (&lucd, sizeof(lucd));
+               error = copyout(&lucd, args->datap, sizeof(lucd));
+       }
+
+       return (error);
+}
+
+int
+linux_capset(struct thread *td, struct linux_capset_args *args)
+{
+       struct l_user_cap_header luch;
+       struct l_user_cap_data lucd;
+       int error;
+
+       if (args->hdrp == NULL || args->datap == NULL)
+               return (EFAULT);
+
+       error = copyin(args->hdrp, &luch, sizeof(luch));
+       if (error != 0)
+               return (error);
+
+       if (luch.version != _LINUX_CAPABILITY_VERSION) {
+               luch.version = _LINUX_CAPABILITY_VERSION;
+               error = copyout(&luch, args->hdrp, sizeof(luch));
+               if (error)
+                       return (error);
+               return (EINVAL);
+       }
+
+       if (luch.pid)
+               return (EPERM);
+
+       error = copyin(args->datap, &lucd, sizeof(lucd));
+       if (error != 0)
+               return (error);
+
+       /* We currently don't support setting any capabilities. */
+       if (lucd.effective || lucd.permitted || lucd.inheritable) {
+               linux_msg(td,
+                         "capset effective=0x%x, permitted=0x%x, "
+                         "inheritable=0x%x is not implemented",
+                         (int)lucd.effective, (int)lucd.permitted,
+                         (int)lucd.inheritable);
+               return (EPERM);
+       }
+
+       return (0);
+}
+
 int
 linux_prctl(struct thread *td, struct linux_prctl_args *args)
 {
@@ -1712,6 +1806,21 @@ linux_prctl(struct thread *td, struct li
                    (void *)(register_t)args->arg2,
                    sizeof(pdeath_signal));
                break;
+       case LINUX_PR_GET_KEEPCAPS:
+               /*
+                * Indicate that we always clear the effective and
+                * permitted capability sets when the user id becomes
+                * non-zero (actually the capability sets are simply
+                * always zero in the current implementation).
+                */
+               td->td_retval[0] = 0;
+               break;
+       case LINUX_PR_SET_KEEPCAPS:
+               /*
+                * Ignore requests to keep the effective and permitted
+                * capability sets when the user id becomes non-zero.
+                */
+               break;
        case LINUX_PR_SET_NAME:
                /*
                 * To be on the safe side we need to make sure to not

Modified: head/sys/compat/linux/linux_misc.h
==============================================================================
--- head/sys/compat/linux/linux_misc.h  Sat Mar 26 10:59:24 2011        
(r220030)
+++ head/sys/compat/linux/linux_misc.h  Sat Mar 26 11:05:53 2011        
(r220031)
@@ -37,6 +37,8 @@
                                         * Second arg is a ptr to return the
                                         * signal.
                                         */
+#define        LINUX_PR_GET_KEEPCAPS   7       /* Get drop capabilities on 
setuid */
+#define        LINUX_PR_SET_KEEPCAPS   8       /* Set drop capabilities on 
setuid */
 #define        LINUX_PR_SET_NAME       15      /* Set process name. */
 #define        LINUX_PR_GET_NAME       16      /* Get process name. */
 

Modified: head/sys/compat/linux/linux_socket.c
==============================================================================
--- head/sys/compat/linux/linux_socket.c        Sat Mar 26 10:59:24 2011        
(r220030)
+++ head/sys/compat/linux/linux_socket.c        Sat Mar 26 11:05:53 2011        
(r220031)
@@ -433,6 +433,8 @@ linux_to_bsd_cmsg_type(int cmsg_type)
        switch (cmsg_type) {
        case LINUX_SCM_RIGHTS:
                return (SCM_RIGHTS);
+       case LINUX_SCM_CREDENTIALS:
+               return (SCM_CREDS);
        }
        return (-1);
 }
@@ -444,6 +446,8 @@ bsd_to_linux_cmsg_type(int cmsg_type)
        switch (cmsg_type) {
        case SCM_RIGHTS:
                return (LINUX_SCM_RIGHTS);
+       case SCM_CREDS:
+               return (LINUX_SCM_CREDENTIALS);
        }
        return (-1);
 }
@@ -459,7 +463,16 @@ linux_to_bsd_msghdr(struct msghdr *bhdr,
        bhdr->msg_iov           = PTRIN(lhdr->msg_iov);
        bhdr->msg_iovlen        = lhdr->msg_iovlen;
        bhdr->msg_control       = PTRIN(lhdr->msg_control);
-       bhdr->msg_controllen    = lhdr->msg_controllen;
+
+       /*
+        * msg_controllen is skipped since BSD and LINUX control messages
+        * are potentially different sizes (e.g. the cred structure used
+        * by SCM_CREDS is different between the two operating system).
+        *
+        * The caller can set it (if necessary) after converting all the
+        * control messages.
+        */
+
        bhdr->msg_flags         = linux_to_bsd_msg_flags(lhdr->msg_flags);
        return (0);
 }
@@ -472,7 +485,16 @@ bsd_to_linux_msghdr(const struct msghdr 
        lhdr->msg_iov           = PTROUT(bhdr->msg_iov);
        lhdr->msg_iovlen        = bhdr->msg_iovlen;
        lhdr->msg_control       = PTROUT(bhdr->msg_control);
-       lhdr->msg_controllen    = bhdr->msg_controllen;
+
+       /*
+        * msg_controllen is skipped since BSD and LINUX control messages
+        * are potentially different sizes (e.g. the cred structure used
+        * by SCM_CREDS is different between the two operating system).
+        *
+        * The caller can set it (if necessary) after converting all the
+        * control messages.
+        */
+
        /* msg_flags skipped */
        return (0);
 }
@@ -1092,6 +1114,7 @@ static int
 linux_sendmsg(struct thread *td, struct linux_sendmsg_args *args)
 {
        struct cmsghdr *cmsg;
+       struct cmsgcred cmcred;
        struct mbuf *control;
        struct msghdr msg;
        struct l_cmsghdr linux_cmsg;
@@ -1099,15 +1122,14 @@ linux_sendmsg(struct thread *td, struct 
        struct l_msghdr linux_msg;
        struct iovec *iov;
        socklen_t datalen;
+       struct sockaddr *sa;
+       sa_family_t sa_family;
        void *data;
        int error;
 
        error = copyin(PTRIN(args->msg), &linux_msg, sizeof(linux_msg));
        if (error)
                return (error);
-       error = linux_to_bsd_msghdr(&msg, &linux_msg);
-       if (error)
-               return (error);
 
        /*
         * Some Linux applications (ping) define a non-NULL control data
@@ -1116,8 +1138,12 @@ linux_sendmsg(struct thread *td, struct 
         * order to handle this case.  This should be checked, but allows the
         * Linux ping to work.
         */
-       if (msg.msg_control != NULL && msg.msg_controllen == 0)
-               msg.msg_control = NULL;
+       if (PTRIN(linux_msg.msg_control) != NULL && linux_msg.msg_controllen == 
0)
+               linux_msg.msg_control = PTROUT(NULL);
+
+       error = linux_to_bsd_msghdr(&msg, &linux_msg);
+       if (error)
+               return (error);
 
 #ifdef COMPAT_LINUX32
        error = linux32_copyiniov(PTRIN(msg.msg_iov), msg.msg_iovlen,
@@ -1128,13 +1154,21 @@ linux_sendmsg(struct thread *td, struct 
        if (error)
                return (error);
 
-       if (msg.msg_control != NULL) {
+       control = NULL;
+       cmsg = NULL;
+
+       if ((ptr_cmsg = LINUX_CMSG_FIRSTHDR(&linux_msg)) != NULL) {
+               error = kern_getsockname(td, args->s, &sa, &datalen);
+               if (error)
+                       goto bad;
+               sa_family = sa->sa_family;
+               free(sa, M_SONAME);
+
                error = ENOBUFS;
                cmsg = malloc(CMSG_HDRSZ, M_TEMP, M_WAITOK | M_ZERO);
                control = m_get(M_WAIT, MT_CONTROL);
                if (control == NULL)
                        goto bad;
-               ptr_cmsg = LINUX_CMSG_FIRSTHDR(&msg);
 
                do {
                        error = copyin(ptr_cmsg, &linux_cmsg,
@@ -1147,28 +1181,58 @@ linux_sendmsg(struct thread *td, struct 
                                goto bad;
 
                        /*
-                        * Now we support only SCM_RIGHTS, so return EINVAL
-                        * in any other cmsg_type
+                        * Now we support only SCM_RIGHTS and SCM_CRED,
+                        * so return EINVAL in any other cmsg_type
                         */
-                       if ((cmsg->cmsg_type =
-                           linux_to_bsd_cmsg_type(linux_cmsg.cmsg_type)) == -1)
-                               goto bad;
+                       cmsg->cmsg_type =
+                           linux_to_bsd_cmsg_type(linux_cmsg.cmsg_type);
                        cmsg->cmsg_level =
                            linux_to_bsd_sockopt_level(linux_cmsg.cmsg_level);
+                       if (cmsg->cmsg_type == -1
+                           || cmsg->cmsg_level != SOL_SOCKET)
+                               goto bad;
 
+                       /*
+                        * Some applications (e.g. pulseaudio) attempt to
+                        * send ancillary data even if the underlying protocol
+                        * doesn't support it which is not allowed in the
+                        * FreeBSD system call interface.
+                        */
+                       if (sa_family != AF_UNIX)
+                               continue;
+
+                       data = LINUX_CMSG_DATA(ptr_cmsg);
                        datalen = linux_cmsg.cmsg_len - L_CMSG_HDRSZ;
+
+                       switch (cmsg->cmsg_type)
+                       {
+                       case SCM_RIGHTS:
+                               break;
+
+                       case SCM_CREDS:
+                               data = &cmcred;
+                               datalen = sizeof(cmcred);
+
+                               /*
+                                * The lower levels will fill in the structure
+                                */
+                               bzero(data, datalen);
+                               break;
+                       }
+
                        cmsg->cmsg_len = CMSG_LEN(datalen);
-                       data = LINUX_CMSG_DATA(ptr_cmsg);
 
                        error = ENOBUFS;
                        if (!m_append(control, CMSG_HDRSZ, (c_caddr_t) cmsg))
                                goto bad;
                        if (!m_append(control, datalen, (c_caddr_t) data))
                                goto bad;
-               } while ((ptr_cmsg = LINUX_CMSG_NXTHDR(&msg, ptr_cmsg)));
-       } else {
-               control = NULL;
-               cmsg = NULL;
+               } while ((ptr_cmsg = LINUX_CMSG_NXTHDR(&linux_msg, ptr_cmsg)));
+
+               if (m_length(control, NULL) == 0) {
+                       m_freem(control);
+                       control = NULL;
+               }
        }
 
        msg.msg_iov = iov;
@@ -1193,9 +1257,11 @@ static int
 linux_recvmsg(struct thread *td, struct linux_recvmsg_args *args)
 {
        struct cmsghdr *cm;
+       struct cmsgcred *cmcred;
        struct msghdr msg;
        struct l_cmsghdr *linux_cmsg = NULL;
-       socklen_t datalen, outlen, clen;
+       struct l_ucred linux_ucred;
+       socklen_t datalen, outlen;
        struct l_msghdr linux_msg;
        struct iovec *iov, *uiov;
        struct mbuf *control = NULL;
@@ -1252,39 +1318,35 @@ linux_recvmsg(struct thread *td, struct 
                        goto bad;
        }
 
-       if (control) {
+       outbuf = PTRIN(linux_msg.msg_control);
+       outlen = 0;
 
+       if (control) {
                linux_cmsg = malloc(L_CMSG_HDRSZ, M_TEMP, M_WAITOK | M_ZERO);
-               outbuf = PTRIN(linux_msg.msg_control);
-               cm = mtod(control, struct cmsghdr *);
-               outlen = 0;
-               clen = control->m_len;
 
-               while (cm != NULL) {
+               msg.msg_control = mtod(control, struct cmsghdr *);
+               msg.msg_controllen = control->m_len;
+
+               cm = CMSG_FIRSTHDR(&msg);
 
-                       if ((linux_cmsg->cmsg_type =
-                           bsd_to_linux_cmsg_type(cm->cmsg_type)) == -1)
+               while (cm != NULL) {
+                       linux_cmsg->cmsg_type =
+                           bsd_to_linux_cmsg_type(cm->cmsg_type);
+                       linux_cmsg->cmsg_level =
+                           bsd_to_linux_sockopt_level(cm->cmsg_level);
+                       if (linux_cmsg->cmsg_type == -1
+                           || cm->cmsg_level != SOL_SOCKET)
                        {
                                error = EINVAL;
                                goto bad;
                        }
+
                        data = CMSG_DATA(cm);
                        datalen = (caddr_t)cm + cm->cmsg_len - (caddr_t)data;
 
-                       switch (linux_cmsg->cmsg_type)
+                       switch (cm->cmsg_type)
                        {
-                       case LINUX_SCM_RIGHTS:
-                               if (outlen + LINUX_CMSG_LEN(datalen) >
-                                   linux_msg.msg_controllen) {
-                                       if (outlen == 0) {
-                                               error = EMSGSIZE;
-                                               goto bad;
-                                       } else {
-                                               linux_msg.msg_flags |=
-                                                   LINUX_MSG_CTRUNC;
-                                               goto out;
-                                       }
-                               }
+                       case SCM_RIGHTS:
                                if (args->flags & LINUX_MSG_CMSG_CLOEXEC) {
                                        fds = datalen / sizeof(int);
                                        fdp = data;
@@ -1295,11 +1357,40 @@ linux_recvmsg(struct thread *td, struct 
                                        }
                                }
                                break;
+
+                       case SCM_CREDS:
+                               /*
+                                * Currently LOCAL_CREDS is never in
+                                * effect for Linux so no need to worry
+                                * about sockcred
+                                */
+                               if (datalen != sizeof (*cmcred)) {
+                                       error = EMSGSIZE;
+                                       goto bad;
+                               }
+                               cmcred = (struct cmsgcred *)data;
+                               bzero(&linux_ucred, sizeof(linux_ucred));
+                               linux_ucred.pid = cmcred->cmcred_pid;
+                               linux_ucred.uid = cmcred->cmcred_uid;
+                               linux_ucred.gid = cmcred->cmcred_gid;
+                               data = &linux_ucred;
+                               datalen = sizeof(linux_ucred);
+                               break;
+                       }
+
+                       if (outlen + LINUX_CMSG_LEN(datalen) >
+                           linux_msg.msg_controllen) {
+                               if (outlen == 0) {
+                                       error = EMSGSIZE;
+                                       goto bad;
+                               } else {
+                                       linux_msg.msg_flags |=
+                                           LINUX_MSG_CTRUNC;
+                                       goto out;
+                               }
                        }
 
                        linux_cmsg->cmsg_len = LINUX_CMSG_LEN(datalen);
-                       linux_cmsg->cmsg_level =
-                           bsd_to_linux_sockopt_level(cm->cmsg_level);
 
                        error = copyout(linux_cmsg, outbuf, L_CMSG_HDRSZ);
                        if (error)
@@ -1312,18 +1403,13 @@ linux_recvmsg(struct thread *td, struct 
 
                        outbuf += LINUX_CMSG_ALIGN(datalen);
                        outlen += LINUX_CMSG_LEN(datalen);
-                       linux_msg.msg_controllen = outlen;
 
-                       if (CMSG_SPACE(datalen) < clen) {
-                               clen -= CMSG_SPACE(datalen);
-                               cm = (struct cmsghdr *)
-                                   ((caddr_t)cm + CMSG_SPACE(datalen));
-                       } else
-                               cm = NULL;
+                       cm = CMSG_NXTHDR(&msg, cm);
                }
        }
 
 out:
+       linux_msg.msg_controllen = outlen;
        error = copyout(&linux_msg, PTRIN(args->msg), sizeof(linux_msg));
 
 bad:

Modified: head/sys/compat/linux/linux_socket.h
==============================================================================
--- head/sys/compat/linux/linux_socket.h        Sat Mar 26 10:59:24 2011        
(r220030)
+++ head/sys/compat/linux/linux_socket.h        Sat Mar 26 11:05:53 2011        
(r220031)
@@ -53,6 +53,7 @@
 /* Socket-level control message types */
 
 #define LINUX_SCM_RIGHTS       0x01
+#define LINUX_SCM_CREDENTIALS   0x02
 
 /* Ancilliary data object information macros */
 
@@ -66,13 +67,14 @@
 #define LINUX_CMSG_FIRSTHDR(msg) \
                                ((msg)->msg_controllen >= \
                                    sizeof(struct l_cmsghdr) ? \
-                                   (struct l_cmsghdr *)((msg)->msg_control) : \
+                                   (struct l_cmsghdr *) \
+                                       PTRIN((msg)->msg_control) : \
                                    (struct l_cmsghdr *)(NULL))
 #define LINUX_CMSG_NXTHDR(msg, cmsg) \
                                ((((char *)(cmsg) + \
                                    LINUX_CMSG_ALIGN((cmsg)->cmsg_len) + \
                                    sizeof(*(cmsg))) > \
-                                   (((char *)(msg)->msg_control) + \
+                                   (((char *)PTRIN((msg)->msg_control)) + \
                                    (msg)->msg_controllen)) ? \
                                    (struct l_cmsghdr *) NULL : \
                                    (struct l_cmsghdr *)((char *)(cmsg) + \
_______________________________________________
svn-src-all@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to