summaryrefslogtreecommitdiffstats
path: root/sys/compat/linux
diff options
context:
space:
mode:
authoravg <avg@FreeBSD.org>2011-03-26 11:05:53 +0000
committeravg <avg@FreeBSD.org>2011-03-26 11:05:53 +0000
commita92365858357b636caacb1f576fcb0c622aebbec (patch)
treedaedfe407e038d5ae066e6a7e69e93ed5a267423 /sys/compat/linux
parentae4ae2c803e333c9798cad1469c5f0cf31c9bb41 (diff)
downloadFreeBSD-src-a92365858357b636caacb1f576fcb0c622aebbec.zip
FreeBSD-src-a92365858357b636caacb1f576fcb0c622aebbec.tar.gz
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 <john@feith.com> Reviewed by: netchild MFC after: 2 weeks
Diffstat (limited to 'sys/compat/linux')
-rw-r--r--sys/compat/linux/linux_misc.c109
-rw-r--r--sys/compat/linux/linux_misc.h2
-rw-r--r--sys/compat/linux/linux_socket.c188
-rw-r--r--sys/compat/linux/linux_socket.h6
4 files changed, 252 insertions, 53 deletions
diff --git a/sys/compat/linux/linux_misc.c b/sys/compat/linux/linux_misc.c
index a721a6e..7b261d6 100644
--- a/sys/compat/linux/linux_misc.c
+++ b/sys/compat/linux/linux_misc.c
@@ -1679,6 +1679,100 @@ linux_exit_group(struct thread *td, struct linux_exit_group_args *args)
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 linux_prctl_args *args)
(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
diff --git a/sys/compat/linux/linux_misc.h b/sys/compat/linux/linux_misc.h
index c7ac5ac..b771825 100644
--- a/sys/compat/linux/linux_misc.h
+++ b/sys/compat/linux/linux_misc.h
@@ -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. */
diff --git a/sys/compat/linux/linux_socket.c b/sys/compat/linux/linux_socket.c
index d94d926..6940e45 100644
--- a/sys/compat/linux/linux_socket.c
+++ b/sys/compat/linux/linux_socket.c
@@ -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, const struct l_msghdr *lhdr)
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 *bhdr, struct l_msghdr *lhdr)
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 linux_sendmsg_args *args)
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 linux_sendmsg_args *args)
* 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 linux_sendmsg_args *args)
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 linux_sendmsg_args *args)
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 linux_recvmsg_args *args)
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 linux_recvmsg_args *args)
}
}
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 linux_recvmsg_args *args)
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:
diff --git a/sys/compat/linux/linux_socket.h b/sys/compat/linux/linux_socket.h
index c716f02..1fe53ed 100644
--- a/sys/compat/linux/linux_socket.h
+++ b/sys/compat/linux/linux_socket.h
@@ -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) + \
OpenPOWER on IntegriCloud