s3:unix_msg: add fds-array to unix_dgram_send() for fd-passing
authorMichael Adam <obnox@samba.org>
Sat, 17 May 2014 22:22:19 +0000 (00:22 +0200)
committerMichael Adam <obnox@samba.org>
Wed, 24 Sep 2014 06:44:11 +0000 (08:44 +0200)
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Michael Adam <obnox@samba.org>
source3/lib/unix_msg/unix_msg.c

index bdd95a6b619608d5f0f276c2e74dcee99ed06525..3a9ce092828963ebd774b709a1f4147d4e5e665f 100644 (file)
@@ -42,6 +42,8 @@ struct unix_dgram_msg {
        int sock;
        ssize_t sent;
        int sys_errno;
+       size_t num_fds;
+       int *fds;
        size_t buflen;
        uint8_t buf[];
 };
@@ -421,6 +423,7 @@ static void unix_dgram_send_queue_free(struct unix_dgram_send_queue *q)
                struct unix_dgram_msg *msg;
                msg = q->msgs;
                DLIST_REMOVE(q->msgs, msg);
+               close_fd_array(msg->fds, msg->num_fds);
                free(msg);
        }
        close(q->sock);
@@ -442,28 +445,73 @@ static struct unix_dgram_send_queue *find_send_queue(
 }
 
 static int queue_msg(struct unix_dgram_send_queue *q,
-                    const struct iovec *iov, int iovlen)
+                    const struct iovec *iov, int iovlen,
+                    const int *fds, size_t num_fds)
 {
        struct unix_dgram_msg *msg;
        ssize_t buflen;
        size_t msglen;
+       size_t fds_size = sizeof(int) * num_fds;
+       int fds_copy[MIN(num_fds, INT8_MAX)];
+       size_t fds_padding = 0;
        int i;
+       size_t tmp;
+       int ret = -1;
+
+       if (num_fds > INT8_MAX) {
+               return EINVAL;
+       }
+
+       for (i = 0; i < num_fds; i++) {
+               fds_copy[i] = -1;
+       }
+
+       for (i = 0; i < num_fds; i++) {
+               fds_copy[i] = dup(fds[i]);
+               if (fds_copy[i] == -1) {
+                       ret = errno;
+                       goto fail;
+               }
+       }
 
        buflen = iov_buflen(iov, iovlen);
        if (buflen == -1) {
-               return EINVAL;
+               goto invalid;
        }
 
-       msglen = offsetof(struct unix_dgram_msg, buf) + buflen;
-       if ((msglen < buflen) ||
-           (msglen < offsetof(struct unix_dgram_msg, buf))) {
+       msglen = offsetof(struct unix_dgram_msg, buf);
+       tmp = msglen + buflen;
+       if ((tmp < msglen) || (tmp < buflen)) {
                /* overflow */
-               return EINVAL;
+               goto invalid;
+       }
+       msglen = tmp;
+
+       if (num_fds > 0) {
+               const size_t fds_align = sizeof(int) - 1;
+
+               tmp = msglen + fds_align;
+               if ((tmp < msglen) || (tmp < fds_align)) {
+                       /* overflow */
+                       goto invalid;
+               }
+               tmp &= ~fds_align;
+
+               fds_padding = tmp - msglen;
+               msglen = tmp;
+
+               tmp = msglen + fds_size;
+               if ((tmp < msglen) || (tmp < fds_size)) {
+                       /* overflow */
+                       goto invalid;
+               }
+               msglen = tmp;
        }
 
        msg = malloc(msglen);
        if (msg == NULL) {
-               return ENOMEM;
+               ret = ENOMEM;
+               goto fail;
        }
        msg->buflen = buflen;
        msg->sock = q->sock;
@@ -474,8 +522,23 @@ static int queue_msg(struct unix_dgram_send_queue *q,
                buflen += iov[i].iov_len;
        }
 
+       msg->num_fds = num_fds;
+       if (msg->num_fds > 0) {
+               void *fds_ptr = (void *)&msg->buf[buflen+fds_padding];
+               memcpy(fds_ptr, fds_copy, fds_size);
+               msg->fds = (int *)fds_ptr;
+       } else {
+               msg->fds = NULL;
+       }
+
        DLIST_ADD_END(q->msgs, msg, struct unix_dgram_msg);
        return 0;
+
+invalid:
+       ret = EINVAL;
+fail:
+       close_fd_array(fds_copy, num_fds);
+       return ret;
 }
 
 static void unix_dgram_send_job(void *private_data)
@@ -489,10 +552,35 @@ static void unix_dgram_send_job(void *private_data)
                .msg_iov = &iov,
                .msg_iovlen = 1,
        };
+#ifdef HAVE_STRUCT_MSGHDR_MSG_CONTROL
+       struct cmsghdr *cmsg;
+       size_t fds_size = sizeof(int) * dmsg->num_fds;
+       size_t cmsg_len = CMSG_LEN(fds_size);
+       size_t cmsg_space = CMSG_SPACE(fds_size);
+       char cmsg_buf[cmsg_space];
+
+       if (dmsg->num_fds > 0) {
+               void *fdptr;
+
+               memset(cmsg_buf, 0, cmsg_space);
+
+               msg.msg_control = cmsg_buf;
+               msg.msg_controllen = cmsg_space;
+               cmsg = CMSG_FIRSTHDR(&msg);
+               cmsg->cmsg_level = SOL_SOCKET;
+               cmsg->cmsg_type = SCM_RIGHTS;
+               cmsg->cmsg_len = cmsg_len;
+               fdptr = CMSG_DATA(cmsg);
+               memcpy(fdptr, dmsg->fds, fds_size);
+               msg.msg_controllen = cmsg->cmsg_len;
+       }
+#endif /*  HAVE_STRUCT_MSGHDR_MSG_CONTROL */
 
        do {
                dmsg->sent = sendmsg(dmsg->sock, &msg, 0);
        } while ((dmsg->sent == -1) && (errno == EINTR));
+
+       close_fd_array(dmsg->fds, dmsg->num_fds);
 }
 
 static void unix_dgram_job_finished(struct poll_watch *w, int fd, short events,
@@ -521,6 +609,7 @@ static void unix_dgram_job_finished(struct poll_watch *w, int fd, short events,
 
        msg = q->msgs;
        DLIST_REMOVE(q->msgs, msg);
+       close_fd_array(msg->fds, msg->num_fds);
        free(msg);
 
        if (q->msgs != NULL) {
@@ -536,11 +625,52 @@ static void unix_dgram_job_finished(struct poll_watch *w, int fd, short events,
 
 static int unix_dgram_send(struct unix_dgram_ctx *ctx,
                           const struct sockaddr_un *dst,
-                          const struct iovec *iov, int iovlen)
+                          const struct iovec *iov, int iovlen,
+                          const int *fds, size_t num_fds)
 {
        struct unix_dgram_send_queue *q;
        struct msghdr msg;
+#ifdef HAVE_STRUCT_MSGHDR_MSG_CONTROL
+       struct cmsghdr *cmsg;
+       size_t fds_size = sizeof(int) * num_fds;
+       size_t cmsg_len = CMSG_LEN(fds_size);
+       size_t cmsg_space = CMSG_SPACE(fds_size);
+       char cmsg_buf[cmsg_space];
+#endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL */
        int ret;
+       int i;
+
+       if (num_fds > INT8_MAX) {
+               return EINVAL;
+       }
+
+#ifndef HAVE_STRUCT_MSGHDR_MSG_CONTROL
+       if (num_fds > 0) {
+               return ENOSYS;
+       }
+#endif /* ! HAVE_STRUCT_MSGHDR_MSG_CONTROL */
+
+       for (i = 0; i < num_fds; i++) {
+               /*
+                * Make sure we only allow fd passing
+                * for communication channels,
+                * e.g. sockets, pipes, fifos, ...
+                */
+               ret = lseek(fds[i], 0, SEEK_CUR);
+               if (ret == -1 && errno == ESPIPE) {
+                       /* ok */
+                       continue;
+               }
+
+               /*
+                * Reject the message as we may need to call dup(),
+                * if we queue the message.
+                *
+                * That might result in unexpected behavior for the caller
+                * for files and broken posix locking.
+                */
+               return EINVAL;
+       }
 
        /*
         * To preserve message ordering, we have to queue a message when
@@ -548,7 +678,7 @@ static int unix_dgram_send(struct unix_dgram_ctx *ctx,
         */
        q = find_send_queue(ctx, dst->sun_path);
        if (q != NULL) {
-               return queue_msg(q, iov, iovlen);
+               return queue_msg(q, iov, iovlen, fds, num_fds);
        }
 
        /*
@@ -561,6 +691,23 @@ static int unix_dgram_send(struct unix_dgram_ctx *ctx,
                .msg_iov = discard_const_p(struct iovec, iov),
                .msg_iovlen = iovlen
        };
+#ifdef HAVE_STRUCT_MSGHDR_MSG_CONTROL
+       if (num_fds > 0) {
+               void *fdptr;
+
+               memset(cmsg_buf, 0, cmsg_space);
+
+               msg.msg_control = cmsg_buf;
+               msg.msg_controllen = cmsg_space;
+               cmsg = CMSG_FIRSTHDR(&msg);
+               cmsg->cmsg_level = SOL_SOCKET;
+               cmsg->cmsg_type = SCM_RIGHTS;
+               cmsg->cmsg_len = cmsg_len;
+               fdptr = CMSG_DATA(cmsg);
+               memcpy(fdptr, fds, fds_size);
+               msg.msg_controllen = cmsg->cmsg_len;
+       }
+#endif /*  HAVE_STRUCT_MSGHDR_MSG_CONTROL */
 
        ret = sendmsg(ctx->sock, &msg, 0);
        if (ret >= 0) {
@@ -574,7 +721,7 @@ static int unix_dgram_send(struct unix_dgram_ctx *ctx,
        if (ret != 0) {
                return ret;
        }
-       ret = queue_msg(q, iov, iovlen);
+       ret = queue_msg(q, iov, iovlen, fds, num_fds);
        if (ret != 0) {
                unix_dgram_send_queue_free(q);
                return ret;
@@ -730,7 +877,8 @@ int unix_msg_send(struct unix_msg_ctx *ctx, const struct sockaddr_un *dst,
                               sizeof(struct iovec) * iovlen);
                }
 
-               return unix_dgram_send(ctx->dgram, dst, iov_copy, iovlen+1);
+               return unix_dgram_send(ctx->dgram, dst, iov_copy, iovlen+1,
+                                      NULL, 0);
        }
 
        hdr = (struct unix_msg_hdr) {
@@ -786,7 +934,8 @@ int unix_msg_send(struct unix_msg_ctx *ctx, const struct sockaddr_un *dst,
                }
                sent += (fragment_len - sizeof(ctx->cookie) - sizeof(hdr));
 
-               ret = unix_dgram_send(ctx->dgram, dst, iov_copy, iov_index);
+               ret = unix_dgram_send(ctx->dgram, dst, iov_copy, iov_index,
+                                     NULL, 0);
                if (ret != 0) {
                        break;
                }