Page MenuHomeFreeBSD

D58695.diff
No OneTemporary

D58695.diff

diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c
--- a/sys/kern/uipc_usrreq.c
+++ b/sys/kern/uipc_usrreq.c
@@ -1451,14 +1451,26 @@
ctl = 0;
first = STAILQ_FIRST(&sb->uxst_mbq);
if (first->m_type == MT_CONTROL) {
+ struct mbuf *prev;
+
control = first;
+ prev = NULL;
+
+ /*
+ * Unlink control messages from the socket buffer. The head of
+ * the socket buffer queue is updated below.
+ */
STAILQ_FOREACH_FROM(first, &sb->uxst_mbq, m_stailq) {
- if (first->m_type != MT_CONTROL)
+ if (first->m_type != MT_CONTROL) {
+ if (!peek && prev != NULL)
+ STAILQ_NEXT(prev, m_stailq) = NULL;
break;
+ }
ctl += first->m_len;
mbcnt += MSIZE;
if (first->m_flags & M_EXT)
mbcnt += first->m_ext.ext_size;
+ prev = first;
}
} else
control = NULL;
@@ -1553,10 +1565,25 @@
*/
error = unp_externalize(so, control, controlp, flags);
control = m_free(control);
- if (__predict_false(error && control != NULL)) {
+ if (__predict_false(error != 0)) {
struct mchain cmc;
- mc_init_m(&cmc, control);
+ /*
+ * Build an mbuf chain containing the remainder
+ * of the control messages and the subsequent
+ * data, to be prepended back to the socket
+ * buffer.
+ */
+ if (control != NULL)
+ mc_init_m(&cmc, control);
+ else
+ mc_init(&cmc);
+ for (m = first; datalen > 0 && m != part;
+ m = next) {
+ datalen -= m->m_len;
+ next = STAILQ_NEXT(m, m_stailq);
+ mc_append(&cmc, m);
+ }
SOCK_RECVBUF_LOCK(so);
if (__predict_false(
@@ -1566,7 +1593,7 @@
/*
* While the lock was dropped and we
* were failing in unp_externalize(),
- * the peer could has a) disconnected,
+ * the peer could have a) disconnected,
* b) filled the buffer so that we
* can't prepend data back.
* These are two edge conditions that
@@ -1590,6 +1617,10 @@
sb->sb_mbcnt = 0;
STAILQ_FOREACH(m, &sb->uxst_mbq, m_stailq) {
if (m->m_type == MT_DATA) {
+ if (m == part) {
+ m->m_len += partlen;
+ m->m_data -= partlen;
+ }
sb->sb_acc += m->m_len;
sb->sb_ccc += m->m_len;
} else {
diff --git a/sys/sys/mbuf.h b/sys/sys/mbuf.h
--- a/sys/sys/mbuf.h
+++ b/sys/sys/mbuf.h
@@ -1758,6 +1758,13 @@
}
}
+static inline void
+mc_init(struct mchain *mc)
+{
+ STAILQ_INIT(&mc->mc_q);
+ mc->mc_len = mc->mc_mlen = 0;
+}
+
/*
* Get mchain from a classic mbuf chain linked by m_next. Two hacks here:
* we use the fact that m_next is alias to m_stailq, we use internal queue(3)
diff --git a/tests/sys/kern/unix_passfd_test.c b/tests/sys/kern/unix_passfd_test.c
--- a/tests/sys/kern/unix_passfd_test.c
+++ b/tests/sys/kern/unix_passfd_test.c
@@ -1094,6 +1094,125 @@
closesocketpair(fd);
}
+/*
+ * Exercise handling of errors from unp_externalize().
+ */
+ATF_TC_WITHOUT_HEAD(externalize_error_partial_read);
+ATF_TC_BODY(externalize_error_partial_read, tc)
+{
+ struct iovec iovec;
+ struct msghdr msghdr;
+ struct rlimit rl, orl;
+ struct stat sb;
+ char cmsgbuf[CMSG_SPACE(sizeof(int))];
+ char msg1[16];
+ char *fill, *rbuf;
+ size_t fillsz;
+#if TEST_PROTO == SOCK_STREAM
+ size_t got;
+#endif
+ ssize_t len;
+ int fd[2], nfds, putfd;
+
+ memset(msg1, 'A', sizeof(msg1));
+
+ domainsocketpair(fd);
+ devnull(&putfd);
+ dofstat(putfd, &sb);
+ nfds = getnfds();
+
+#if TEST_PROTO == SOCK_STREAM
+ fillsz = (size_t)getrecvspace() * 3 / 5;
+#elif TEST_PROTO == SOCK_DGRAM
+ fillsz = 128;
+#endif
+
+ fill = malloc(fillsz);
+ ATF_REQUIRE(fill != NULL);
+ memset(fill, 'B', fillsz);
+ rbuf = malloc(sizeof(msg1) + fillsz);
+ ATF_REQUIRE(rbuf != NULL);
+
+ /*
+ * The first message carries the rights and a small payload; the second
+ * queues more data behind it, so that the read below leaves the receive
+ * buffer non-empty.
+ */
+ len = sendfd_payload(fd[0], putfd, msg1, sizeof(msg1));
+ ATF_REQUIRE_MSG(len == (ssize_t)sizeof(msg1),
+ "sendmsg: %zd bytes sent; expected %zu: %s", len, sizeof(msg1),
+ strerror(errno));
+ len = send(fd[0], fill, fillsz, 0);
+ ATF_REQUIRE_MSG(len == (ssize_t)fillsz,
+ "send: %zd bytes sent; expected %zu: %s", len, fillsz,
+ strerror(errno));
+
+ /*
+ * Use fd limits to force receive to fail.
+ */
+ ATF_REQUIRE_MSG(getrlimit(RLIMIT_NOFILE, &orl) == 0,
+ "getrlimit failed: %s", strerror(errno));
+ rl = orl;
+ rl.rlim_cur = 1;
+ ATF_REQUIRE_MSG(setrlimit(RLIMIT_NOFILE, &rl) == 0,
+ "setrlimit failed: %s", strerror(errno));
+
+ bzero(&msghdr, sizeof(msghdr));
+ iovec.iov_base = rbuf;
+ iovec.iov_len = sizeof(msg1);
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+ msghdr.msg_control = cmsgbuf;
+ msghdr.msg_controllen = sizeof(cmsgbuf);
+
+ ATF_REQUIRE_ERRNO(EMFILE, recvmsg(fd[1], &msghdr, 0) == -1);
+
+ ATF_REQUIRE_MSG(setrlimit(RLIMIT_NOFILE, &orl) == 0,
+ "setrlimit failed: %s", strerror(errno));
+
+ /* The rights must have been disposed of rather than installed. */
+ ATF_REQUIRE_MSG(getnfds() == nfds, "descriptor leaked");
+
+ /*
+ * The failed read must leave the socket usable with both payloads still
+ * queued.
+ */
+#if TEST_PROTO == SOCK_STREAM
+ for (got = 0; got < sizeof(msg1) + fillsz; got += (size_t)len) {
+ len = recv(fd[1], rbuf + got, sizeof(msg1) + fillsz - got, 0);
+ if (len <= 0)
+ break;
+ }
+ ATF_REQUIRE_MSG(got == sizeof(msg1) + fillsz,
+ "recovered %zu of %zu bytes after the failed read: %s", got,
+ sizeof(msg1) + fillsz, strerror(errno));
+ ATF_REQUIRE_MSG(memcmp(rbuf, msg1, sizeof(msg1)) == 0,
+ "first payload corrupted");
+ ATF_REQUIRE_MSG(memcmp(rbuf + sizeof(msg1), fill, fillsz) == 0,
+ "second payload corrupted");
+#elif TEST_PROTO == SOCK_DGRAM
+ /*
+ * For datagrams, soreceive_dgram() dequeues the record before
+ * processing control messages, so the first datagram's payload is
+ * consumed even when externalize fails. Only the second datagram
+ * should remain queued.
+ */
+ len = recv(fd[1], rbuf, fillsz, 0);
+ ATF_REQUIRE_MSG(len == (ssize_t)fillsz,
+ "second datagram: got %zd bytes, expected %zu: %s", len, fillsz,
+ strerror(errno));
+ ATF_REQUIRE_MSG(memcmp(rbuf, fill, fillsz) == 0,
+ "second payload corrupted");
+#endif
+
+ dofstat(putfd, &sb);
+
+ free(rbuf);
+ free(fill);
+ close(putfd);
+ closesocketpair(fd);
+}
+
/*
* Verify that we can handle empty rights messages.
*/
@@ -1424,6 +1543,7 @@
ATF_TP_ADD_TC(tp, rights_creds_payload);
ATF_TP_ADD_TC(tp, truncated_rights);
ATF_TP_ADD_TC(tp, copyout_rights_error);
+ ATF_TP_ADD_TC(tp, externalize_error_partial_read);
ATF_TP_ADD_TC(tp, empty_rights_message);
ATF_TP_ADD_TC(tp, control_creates_records);
ATF_TP_ADD_TC(tp, cross_jail_dirfd);

File Metadata

Mime Type
text/plain
Expires
Tue, Sep 22, 1:51 PM (19 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39417964
Default Alt Text
D58695.diff (6 KB)

Event Timeline