diff options
-rw-r--r-- | lib/libutil/Symbols.map | 6 | ||||
-rw-r--r-- | lib/libutil/imsg-buffer.c | 247 | ||||
-rw-r--r-- | lib/libutil/imsg.c | 228 | ||||
-rw-r--r-- | lib/libutil/imsg.h | 29 |
4 files changed, 325 insertions, 185 deletions
diff --git a/lib/libutil/Symbols.map b/lib/libutil/Symbols.map index 55d01cbba0e..79a3f2e524f 100644 --- a/lib/libutil/Symbols.map +++ b/lib/libutil/Symbols.map @@ -48,6 +48,7 @@ ibuf_get_string; ibuf_left; ibuf_open; + ibuf_read; ibuf_reserve; ibuf_rewind; ibuf_seek; @@ -79,11 +80,13 @@ imsg_get_len; imsg_get_pid; imsg_get_type; + imsgbuf_allow_fdpass; imsgbuf_clear; imsgbuf_flush; imsgbuf_init; imsgbuf_queuelen; imsgbuf_read; + imsgbuf_set_maxsize; imsgbuf_write; isduid; login; @@ -94,8 +97,11 @@ logwtmp; msgbuf_clear; msgbuf_free; + msgbuf_get; msgbuf_new; + msgbuf_new_reader; msgbuf_queuelen; + msgbuf_read; msgbuf_write; ober_add_bitstring; ober_add_boolean; diff --git a/lib/libutil/imsg-buffer.c b/lib/libutil/imsg-buffer.c index d0d5bbf5b6b..c43da77f8af 100644 --- a/lib/libutil/imsg-buffer.c +++ b/lib/libutil/imsg-buffer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: imsg-buffer.c,v 1.27 2024/11/21 13:01:07 claudio Exp $ */ +/* $OpenBSD: imsg-buffer.c,v 1.28 2024/11/21 13:03:21 claudio Exp $ */ /* * Copyright (c) 2023 Claudio Jeker <claudio@openbsd.org> @@ -34,9 +34,17 @@ struct msgbuf { TAILQ_HEAD(, ibuf) bufs; + TAILQ_HEAD(, ibuf) rbufs; uint32_t queued; + char *rbuf; + struct ibuf *rpmsg; + ssize_t (*readhdr)(struct ibuf *, void *); + void *rarg; + size_t roff; + size_t hdrsize; }; +static void msgbuf_read_enqueue(struct msgbuf *, struct ibuf *); static void msgbuf_enqueue(struct msgbuf *, struct ibuf *); static void msgbuf_dequeue(struct msgbuf *, struct ibuf *); static void msgbuf_drain(struct msgbuf *, size_t); @@ -560,15 +568,46 @@ msgbuf_new(void) return (NULL); msgbuf->queued = 0; TAILQ_INIT(&msgbuf->bufs); + TAILQ_INIT(&msgbuf->rbufs); return msgbuf; } +struct msgbuf * +msgbuf_new_reader(size_t hdrsz, ssize_t (*readhdr)(struct ibuf *, void *), + void *arg) +{ + struct msgbuf *msgbuf; + char *buf; + + if (hdrsz == 0 || hdrsz > IBUF_READ_SIZE / 2) { + errno = EINVAL; + return (NULL); + } + + if ((buf = malloc(IBUF_READ_SIZE)) == NULL) + return (NULL); + + msgbuf = msgbuf_new(); + if (msgbuf == NULL) { + free(buf); + return (NULL); + } + + msgbuf->rbuf = buf; + msgbuf->hdrsize = hdrsz; + msgbuf->readhdr = readhdr; + msgbuf->rarg = arg; + + return (msgbuf); +} + void msgbuf_free(struct msgbuf *msgbuf) { if (msgbuf != NULL) msgbuf_clear(msgbuf); + free(msgbuf->rbuf); free(msgbuf); } @@ -583,8 +622,29 @@ msgbuf_clear(struct msgbuf *msgbuf) { struct ibuf *buf; + /* write side */ while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL) msgbuf_dequeue(msgbuf, buf); + msgbuf->queued = 0; + + /* read side */ + while ((buf = TAILQ_FIRST(&msgbuf->rbufs)) != NULL) { + TAILQ_REMOVE(&msgbuf->rbufs, buf, entry); + ibuf_free(buf); + } + msgbuf->roff = 0; + ibuf_free(msgbuf->rpmsg); + msgbuf->rpmsg = NULL; +} + +struct ibuf * +msgbuf_get(struct msgbuf *msgbuf) +{ + struct ibuf *buf; + + if ((buf = TAILQ_FIRST(&msgbuf->rbufs)) != NULL) + TAILQ_REMOVE(&msgbuf->rbufs, buf, entry); + return buf; } int @@ -606,7 +666,7 @@ ibuf_write(int fd, struct msgbuf *msgbuf) if (i == 0) return (0); /* nothing queued */ -again: + again: if ((n = writev(fd, iov, i)) == -1) { if (errno == EINTR) goto again; @@ -665,7 +725,7 @@ msgbuf_write(int fd, struct msgbuf *msgbuf) *(int *)CMSG_DATA(cmsg) = buf0->fd; } -again: + again: if ((n = sendmsg(fd, &msg, 0)) == -1) { if (errno == EINTR) goto again; @@ -689,6 +749,187 @@ again: return (0); } +static int +ibuf_read_process(struct msgbuf *msgbuf, int fd) +{ + struct ibuf rbuf, msg; + ssize_t sz; + + ibuf_from_buffer(&rbuf, msgbuf->rbuf, msgbuf->roff); + + /* fds must be passed at start of message of at least hdrsize bytes */ + if (msgbuf->rpmsg != NULL && fd != -1) { + close(fd); + fd = -1; + } + + do { + if (msgbuf->rpmsg == NULL) { + if (ibuf_size(&rbuf) < msgbuf->hdrsize) { + if (fd != -1) { + close(fd); + fd = -1; + } + break; + } + /* get size from header */ + ibuf_from_buffer(&msg, ibuf_data(&rbuf), + msgbuf->hdrsize); + sz = msgbuf->readhdr(&msg, msgbuf->rarg); + if (sz == -1) + goto fail; + if ((msgbuf->rpmsg = ibuf_open(sz)) == NULL) + goto fail; + if (fd != -1) { + ibuf_fd_set(msgbuf->rpmsg, fd); + fd = -1; + } + } + + if (ibuf_left(msgbuf->rpmsg) <= ibuf_size(&rbuf)) + sz = ibuf_left(msgbuf->rpmsg); + else + sz = ibuf_size(&rbuf); + + /* neither call below can fail */ + if (ibuf_get_ibuf(&rbuf, sz, &msg) == -1 || + ibuf_add_ibuf(msgbuf->rpmsg, &msg) == -1) + goto fail; + + if (ibuf_left(msgbuf->rpmsg) == 0) { + msgbuf_read_enqueue(msgbuf, msgbuf->rpmsg); + msgbuf->rpmsg = NULL; + } + } while (ibuf_size(&rbuf) > 0); + + if (ibuf_size(&rbuf) > 0) + memmove(msgbuf->rbuf, ibuf_data(&rbuf), ibuf_size(&rbuf)); + msgbuf->roff = ibuf_size(&rbuf); + + return (1); + + fail: + /* XXX cleanup */ + return (-1); +} + +int +ibuf_read(int fd, struct msgbuf *msgbuf) +{ + struct iovec iov; + ssize_t n; + + if (msgbuf->rbuf == NULL) { + errno = EINVAL; + return (-1); + } + + iov.iov_base = msgbuf->rbuf + msgbuf->roff; + iov.iov_len = IBUF_READ_SIZE - msgbuf->roff; + + again: + if ((n = readv(fd, &iov, 1)) == -1) { + if (errno == EINTR) + goto again; + if (errno == EAGAIN) + /* lets retry later again */ + return (1); + return (-1); + } + if (n == 0) /* connection closed */ + return (0); + + msgbuf->roff += n; + /* new data arrived, try to process it */ + return (ibuf_read_process(msgbuf, -1)); +} + +int +msgbuf_read(int fd, struct msgbuf *msgbuf) +{ + struct msghdr msg; + struct cmsghdr *cmsg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int) * 1)]; + } cmsgbuf; + struct iovec iov; + ssize_t n; + int fdpass = -1; + + if (msgbuf->rbuf == NULL) { + errno = EINVAL; + return (-1); + } + + memset(&msg, 0, sizeof(msg)); + memset(&cmsgbuf, 0, sizeof(cmsgbuf)); + + iov.iov_base = msgbuf->rbuf + msgbuf->roff; + iov.iov_len = IBUF_READ_SIZE - msgbuf->roff; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = &cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + +again: + if ((n = recvmsg(fd, &msg, 0)) == -1) { + if (errno == EINTR) + goto again; + if (errno == EMSGSIZE) + /* + * Not enough fd slots: fd passing failed, retry + * to receive the message without fd. + * imsg_get_fd() will return -1 in that case. + */ + goto again; + if (errno == EAGAIN) + /* lets retry later again */ + return (1); + return (-1); + } + if (n == 0) /* connection closed */ + return (0); + + msgbuf->roff += n; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + int i, j, f; + + /* + * We only accept one file descriptor. Due to C + * padding rules, our control buffer might contain + * more than one fd, and we must close them. + */ + j = ((char *)cmsg + cmsg->cmsg_len - + (char *)CMSG_DATA(cmsg)) / sizeof(int); + for (i = 0; i < j; i++) { + f = ((int *)CMSG_DATA(cmsg))[i]; + if (i == 0) + fdpass = f; + else + close(f); + } + } + /* we do not handle other ctl data level */ + } + + /* new data arrived, try to process it */ + return (ibuf_read_process(msgbuf, fdpass)); +} + +static void +msgbuf_read_enqueue(struct msgbuf *msgbuf, struct ibuf *buf) +{ + /* if buf lives on the stack abort before causing more harm */ + if (buf->fd == IBUF_FD_MARK_ON_STACK) + abort(); + TAILQ_INSERT_TAIL(&msgbuf->rbufs, buf, entry); +} + static void msgbuf_enqueue(struct msgbuf *msgbuf, struct ibuf *buf) { diff --git a/lib/libutil/imsg.c b/lib/libutil/imsg.c index 4e5e9041d83..73ac5d4bd5c 100644 --- a/lib/libutil/imsg.c +++ b/lib/libutil/imsg.c @@ -1,4 +1,4 @@ -/* $OpenBSD: imsg.c,v 1.35 2024/11/21 13:01:07 claudio Exp $ */ +/* $OpenBSD: imsg.c,v 1.36 2024/11/21 13:03:21 claudio Exp $ */ /* * Copyright (c) 2023 Claudio Jeker <claudio@openbsd.org> @@ -29,115 +29,54 @@ #include "imsg.h" -struct imsg_fd { - TAILQ_ENTRY(imsg_fd) entry; - int fd; -}; +#define IMSG_ALLOW_FDPASS 0x01 -static int imsg_dequeue_fd(struct imsgbuf *); +static ssize_t imsg_parse_hdr(struct ibuf *, void *); -void +int imsgbuf_init(struct imsgbuf *imsgbuf, int fd) { - imsgbuf->w = msgbuf_new(); - memset(&imsgbuf->r, 0, sizeof(imsgbuf->r)); - imsgbuf->fd = fd; + imsgbuf->w = msgbuf_new_reader(IMSG_HEADER_SIZE, imsg_parse_hdr, + imsgbuf); + if (imsgbuf->w == NULL) + return (-1); imsgbuf->pid = getpid(); - TAILQ_INIT(&imsgbuf->fds); + imsgbuf->maxsize = MAX_IMSGSIZE; + imsgbuf->fd = fd; + imsgbuf->flags = 0; + return (0); } -int -imsgbuf_read(struct imsgbuf *imsgbuf) +void +imsgbuf_allow_fdpass(struct imsgbuf *imsgbuf) { - struct msghdr msg; - struct cmsghdr *cmsg; - union { - struct cmsghdr hdr; - char buf[CMSG_SPACE(sizeof(int) * 1)]; - } cmsgbuf; - struct iovec iov; - ssize_t n; - int fd; - struct imsg_fd *ifd; - - memset(&msg, 0, sizeof(msg)); - memset(&cmsgbuf, 0, sizeof(cmsgbuf)); - - iov.iov_base = imsgbuf->r.buf + imsgbuf->r.wpos; - iov.iov_len = sizeof(imsgbuf->r.buf) - imsgbuf->r.wpos; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - msg.msg_control = &cmsgbuf.buf; - msg.msg_controllen = sizeof(cmsgbuf.buf); - - if ((ifd = calloc(1, sizeof(struct imsg_fd))) == NULL) - return (-1); - -again: - if ((n = recvmsg(imsgbuf->fd, &msg, 0)) == -1) { - if (errno == EINTR) - goto again; - if (errno == EMSGSIZE) - /* - * Not enough fd slots: fd passing failed, retry - * to receive the message without fd. - * imsg_get_fd() will return -1 in that case. - */ - goto again; - if (errno == EAGAIN) { - free(ifd); - return (1); - } - goto fail; - } - - if (n == 0) { /* connection closed */ - free(ifd); - return (0); - } - - imsgbuf->r.wpos += n; - - for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; - cmsg = CMSG_NXTHDR(&msg, cmsg)) { - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS) { - int i; - int j; - - /* - * We only accept one file descriptor. Due to C - * padding rules, our control buffer might contain - * more than one fd, and we must close them. - */ - j = ((char *)cmsg + cmsg->cmsg_len - - (char *)CMSG_DATA(cmsg)) / sizeof(int); - for (i = 0; i < j; i++) { - fd = ((int *)CMSG_DATA(cmsg))[i]; - if (ifd != NULL) { - ifd->fd = fd; - TAILQ_INSERT_TAIL(&imsgbuf->fds, ifd, - entry); - ifd = NULL; - } else - close(fd); - } - } - /* we do not handle other ctl data level */ - } + imsgbuf->flags |= IMSG_ALLOW_FDPASS; +} - free(ifd); - return (1); +void +imsgbuf_set_maxsize(struct imsgbuf *imsgbuf, uint32_t maxsize) +{ + if (maxsize < IMSG_HEADER_SIZE) + return; + imsgbuf->maxsize = maxsize; +} -fail: - free(ifd); - return (-1); +int +imsgbuf_read(struct imsgbuf *imsgbuf) +{ + if (imsgbuf->flags & IMSG_ALLOW_FDPASS) + return msgbuf_read(imsgbuf->fd, imsgbuf->w); + else + return ibuf_read(imsgbuf->fd, imsgbuf->w); } int imsgbuf_write(struct imsgbuf *imsgbuf) { - return msgbuf_write(imsgbuf->fd, imsgbuf->w); + if (imsgbuf->flags & IMSG_ALLOW_FDPASS) + return msgbuf_write(imsgbuf->fd, imsgbuf->w); + else + return ibuf_write(imsgbuf->fd, imsgbuf->w); } int @@ -153,13 +92,8 @@ imsgbuf_flush(struct imsgbuf *imsgbuf) void imsgbuf_clear(struct imsgbuf *imsgbuf) { - int fd; - - msgbuf_clear(imsgbuf->w); msgbuf_free(imsgbuf->w); imsgbuf->w = NULL; - while ((fd = imsg_dequeue_fd(imsgbuf)) != -1) - close(fd); } uint32_t @@ -172,56 +106,28 @@ ssize_t imsg_get(struct imsgbuf *imsgbuf, struct imsg *imsg) { struct imsg m; - size_t av, left, datalen; - - av = imsgbuf->r.wpos; + struct ibuf *buf; - if (IMSG_HEADER_SIZE > av) + if ((buf = msgbuf_get(imsgbuf->w)) == NULL) return (0); - memcpy(&m.hdr, imsgbuf->r.buf, sizeof(m.hdr)); - if (m.hdr.len < IMSG_HEADER_SIZE || - m.hdr.len > MAX_IMSGSIZE) { - errno = ERANGE; + if (ibuf_get(buf, &m.hdr, sizeof(m.hdr)) == -1) return (-1); - } - if (m.hdr.len > av) - return (0); - - m.data = NULL; - datalen = m.hdr.len - IMSG_HEADER_SIZE; - imsgbuf->r.rptr = imsgbuf->r.buf + IMSG_HEADER_SIZE; - - if ((m.buf = ibuf_open(datalen)) == NULL) - return (-1); - if (datalen != 0) { - if (ibuf_add(m.buf, imsgbuf->r.rptr, datalen) == -1) { - /* this should never fail */ - ibuf_free(m.buf); - return (-1); - } - m.data = ibuf_data(m.buf); - } - - if (m.hdr.flags & IMSGF_HASFD) - ibuf_fd_set(m.buf, imsg_dequeue_fd(imsgbuf)); - - if (m.hdr.len < av) { - left = av - m.hdr.len; - memmove(&imsgbuf->r.buf, imsgbuf->r.buf + m.hdr.len, left); - imsgbuf->r.wpos = left; - } else - imsgbuf->r.wpos = 0; + if (ibuf_size(buf)) + m.data = ibuf_data(buf); + else + m.data = NULL; + m.buf = buf; *imsg = m; - return (datalen + IMSG_HEADER_SIZE); + return (ibuf_size(buf) + IMSG_HEADER_SIZE); } int imsg_get_ibuf(struct imsg *imsg, struct ibuf *ibuf) { - if (imsg->buf == NULL) { + if (ibuf_size(imsg->buf) == 0) { errno = EBADMSG; return (-1); } @@ -235,7 +141,7 @@ imsg_get_data(struct imsg *imsg, void *data, size_t len) errno = EINVAL; return (-1); } - if (imsg->buf == NULL || ibuf_size(imsg->buf) != len) { + if (ibuf_size(imsg->buf) != len) { errno = EBADMSG; return (-1); } @@ -257,8 +163,6 @@ imsg_get_id(struct imsg *imsg) size_t imsg_get_len(struct imsg *imsg) { - if (imsg->buf == NULL) - return 0; return ibuf_size(imsg->buf); } @@ -328,14 +232,13 @@ imsg_compose_ibuf(struct imsgbuf *imsgbuf, uint32_t type, uint32_t id, struct imsg_hdr hdr; int save_errno; - if (ibuf_size(buf) + IMSG_HEADER_SIZE > MAX_IMSGSIZE) { + if (ibuf_size(buf) + IMSG_HEADER_SIZE > imsgbuf->maxsize) { errno = ERANGE; goto fail; } hdr.type = type; hdr.len = ibuf_size(buf) + IMSG_HEADER_SIZE; - hdr.flags = 0; hdr.peerid = id; if ((hdr.pid = pid) == 0) hdr.pid = imsgbuf->pid; @@ -366,10 +269,9 @@ imsg_forward(struct imsgbuf *imsgbuf, struct imsg *msg) struct ibuf *wbuf; size_t len = 0; - if (msg->buf != NULL) { - ibuf_rewind(msg->buf); - len = ibuf_size(msg->buf); - } + ibuf_rewind(msg->buf); + ibuf_skip(msg->buf, sizeof(msg->hdr)); + len = ibuf_size(msg->buf); if ((wbuf = imsg_create(imsgbuf, msg->hdr.type, msg->hdr.peerid, msg->hdr.pid, len)) == NULL) @@ -394,17 +296,16 @@ imsg_create(struct imsgbuf *imsgbuf, uint32_t type, uint32_t id, pid_t pid, struct imsg_hdr hdr; datalen += IMSG_HEADER_SIZE; - if (datalen > MAX_IMSGSIZE) { + if (datalen > imsgbuf->maxsize) { errno = ERANGE; return (NULL); } hdr.type = type; - hdr.flags = 0; hdr.peerid = id; if ((hdr.pid = pid) == 0) hdr.pid = imsgbuf->pid; - if ((wbuf = ibuf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) { + if ((wbuf = ibuf_dynamic(datalen, imsgbuf->maxsize)) == NULL) { return (NULL); } if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1) @@ -430,12 +331,7 @@ imsg_close(struct imsgbuf *imsgbuf, struct ibuf *msg) struct imsg_hdr *hdr; hdr = (struct imsg_hdr *)msg->buf; - - hdr->flags &= ~IMSGF_HASFD; - if (ibuf_fd_avail(msg)) - hdr->flags |= IMSGF_HASFD; hdr->len = ibuf_size(msg); - ibuf_close(imsgbuf->w, msg); } @@ -445,18 +341,18 @@ imsg_free(struct imsg *imsg) ibuf_free(imsg->buf); } -static int -imsg_dequeue_fd(struct imsgbuf *imsgbuf) +static ssize_t +imsg_parse_hdr(struct ibuf *buf, void *arg) { - int fd; - struct imsg_fd *ifd; + struct imsgbuf *imsgbuf = arg; + struct imsg_hdr hdr; - if ((ifd = TAILQ_FIRST(&imsgbuf->fds)) == NULL) + if (ibuf_get(buf, &hdr, sizeof(hdr)) == -1) + return -1; + if (hdr.len < IMSG_HEADER_SIZE || + hdr.len > imsgbuf->maxsize) { + errno = ERANGE; return (-1); - - fd = ifd->fd; - TAILQ_REMOVE(&imsgbuf->fds, ifd, entry); - free(ifd); - - return (fd); + } + return hdr.len; } diff --git a/lib/libutil/imsg.h b/lib/libutil/imsg.h index 995c3666206..8d78beb34eb 100644 --- a/lib/libutil/imsg.h +++ b/lib/libutil/imsg.h @@ -1,4 +1,4 @@ -/* $OpenBSD: imsg.h,v 1.17 2024/11/21 13:01:07 claudio Exp $ */ +/* $OpenBSD: imsg.h,v 1.18 2024/11/21 13:03:21 claudio Exp $ */ /* * Copyright (c) 2023 Claudio Jeker <claudio@openbsd.org> @@ -38,29 +38,19 @@ struct ibuf { int fd; }; -struct ibuf_read { - unsigned char buf[IBUF_READ_SIZE]; - unsigned char *rptr; - size_t wpos; -}; - struct msgbuf; -struct imsg_fd; struct imsgbuf { - TAILQ_HEAD(, imsg_fd) fds; - struct ibuf_read r; struct msgbuf *w; - int fd; pid_t pid; + uint32_t maxsize; + int fd; + int flags; }; -#define IMSGF_HASFD 1 - struct imsg_hdr { uint32_t type; - uint16_t len; - uint16_t flags; + uint32_t len; uint32_t peerid; uint32_t pid; }; @@ -120,14 +110,21 @@ int ibuf_fd_avail(struct ibuf *); int ibuf_fd_get(struct ibuf *); void ibuf_fd_set(struct ibuf *, int); struct msgbuf *msgbuf_new(void); +struct msgbuf *msgbuf_new_reader(size_t, ssize_t (*)(struct ibuf *, void *), + void *); void msgbuf_free(struct msgbuf *); void msgbuf_clear(struct msgbuf *); uint32_t msgbuf_queuelen(struct msgbuf *); int ibuf_write(int, struct msgbuf *); int msgbuf_write(int, struct msgbuf *); +int ibuf_read(int, struct msgbuf *); +int msgbuf_read(int, struct msgbuf *); +struct ibuf *msgbuf_get(struct msgbuf *); /* imsg.c */ -void imsgbuf_init(struct imsgbuf *, int); +int imsgbuf_init(struct imsgbuf *, int); +void imsgbuf_allow_fdpass(struct imsgbuf *imsgbuf); +void imsgbuf_set_maxsize(struct imsgbuf *, uint32_t); int imsgbuf_read(struct imsgbuf *); int imsgbuf_write(struct imsgbuf *); int imsgbuf_flush(struct imsgbuf *); |