aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Johnston <markj@FreeBSD.org>2022-02-08 17:36:41 +0000
committerMark Johnston <markj@FreeBSD.org>2022-02-08 17:40:41 +0000
commit5de79eeddb9de079d108d1312148bcbefce45c27 (patch)
tree264c5c70cd36b9fb1059dbeb5f3812c010dcc7e7
parent300cfb96fc2253c3aff8d501d5599fcf811daa34 (diff)
downloadsrc-5de79eeddb9de079d108d1312148bcbefce45c27.tar.gz
src-5de79eeddb9de079d108d1312148bcbefce45c27.zip
ktls: Disallow transmitting empty frames outside of TLS 1.0/CBC mode
There was nothing preventing one from sending an empty fragment on an arbitrary KTLS TX-enabled socket, but ktls_frame() asserts that this could not happen. Though the transmit path handles this case for TLS 1.0 with AES-CBC, we should be strict and allow empty fragments only in modes where it is explicitly allowed. Modify sosend_generic() to reject writes to a KTLS-enabled socket if the number of data bytes is zero, so that userspace cannot trigger the aforementioned assertion. Add regression tests to exercise this case. Reported by: syzkaller Reviewed by: gallatin, jhb MFC after: 1 week Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D34195
-rw-r--r--sys/kern/uipc_ktls.c18
-rw-r--r--sys/kern/uipc_socket.c5
-rw-r--r--sys/sys/ktls.h1
-rw-r--r--tests/sys/kern/ktls_test.c31
4 files changed, 41 insertions, 14 deletions
diff --git a/sys/kern/uipc_ktls.c b/sys/kern/uipc_ktls.c
index b3235e8a1e0c..2495e940a6a0 100644
--- a/sys/kern/uipc_ktls.c
+++ b/sys/kern/uipc_ktls.c
@@ -1691,19 +1691,18 @@ ktls_frame(struct mbuf *top, struct ktls_session *tls, int *enq_cnt,
* All mbufs in the chain should be TLS records whose
* payload does not exceed the maximum frame length.
*
- * Empty TLS records are permitted when using CBC.
+ * Empty TLS 1.0 records are permitted when using CBC.
*/
- KASSERT(m->m_len <= maxlen &&
- (tls->params.cipher_algorithm == CRYPTO_AES_CBC ?
- m->m_len >= 0 : m->m_len > 0),
- ("ktls_frame: m %p len %d\n", m, m->m_len));
+ KASSERT(m->m_len <= maxlen && m->m_len >= 0 &&
+ (m->m_len > 0 || ktls_permit_empty_frames(tls)),
+ ("ktls_frame: m %p len %d", m, m->m_len));
/*
* TLS frames require unmapped mbufs to store session
* info.
*/
KASSERT((m->m_flags & M_EXTPG) != 0,
- ("ktls_frame: mapped mbuf %p (top = %p)\n", m, top));
+ ("ktls_frame: mapped mbuf %p (top = %p)", m, top));
tls_len = m->m_len;
@@ -1797,6 +1796,13 @@ ktls_frame(struct mbuf *top, struct ktls_session *tls, int *enq_cnt,
}
}
+bool
+ktls_permit_empty_frames(struct ktls_session *tls)
+{
+ return (tls->params.cipher_algorithm == CRYPTO_AES_CBC &&
+ tls->params.tls_vminor == TLS_MINOR_VER_ZERO);
+}
+
void
ktls_check_rx(struct sockbuf *sb)
{
diff --git a/sys/kern/uipc_socket.c b/sys/kern/uipc_socket.c
index 33dfe6cb2176..ab8e5d6e1b69 100644
--- a/sys/kern/uipc_socket.c
+++ b/sys/kern/uipc_socket.c
@@ -1667,6 +1667,11 @@ sosend_generic(struct socket *so, struct sockaddr *addr, struct uio *uio,
atomic = 1;
}
}
+
+ if (resid == 0 && !ktls_permit_empty_frames(tls)) {
+ error = EINVAL;
+ goto release;
+ }
}
#endif
diff --git a/sys/sys/ktls.h b/sys/sys/ktls.h
index 5cfca2d860a0..4fa52f13e127 100644
--- a/sys/sys/ktls.h
+++ b/sys/sys/ktls.h
@@ -213,6 +213,7 @@ int ktls_enable_tx(struct socket *so, struct tls_enable *en);
void ktls_destroy(struct ktls_session *tls);
void ktls_frame(struct mbuf *m, struct ktls_session *tls, int *enqueue_cnt,
uint8_t record_type);
+bool ktls_permit_empty_frames(struct ktls_session *tls);
void ktls_seq(struct sockbuf *sb, struct mbuf *m);
void ktls_enqueue(struct mbuf *m, struct socket *so, int page_count);
void ktls_enqueue_to_free(struct mbuf *m);
diff --git a/tests/sys/kern/ktls_test.c b/tests/sys/kern/ktls_test.c
index 9525258a64bc..759a23455f25 100644
--- a/tests/sys/kern/ktls_test.c
+++ b/tests/sys/kern/ktls_test.c
@@ -1105,9 +1105,19 @@ test_ktls_transmit_empty_fragment(struct tls_enable *en, uint64_t seqno)
fd_set_blocking(sockets[0]);
fd_set_blocking(sockets[1]);
- /* A write of zero bytes should send an empty fragment. */
+ /*
+ * A write of zero bytes should send an empty fragment only for
+ * TLS 1.0, otherwise an error should be raised.
+ */
rv = write(sockets[1], NULL, 0);
- ATF_REQUIRE(rv == 0);
+ if (rv == 0) {
+ ATF_REQUIRE(en->cipher_algorithm == CRYPTO_AES_CBC);
+ ATF_REQUIRE(en->tls_vminor == TLS_MINOR_VER_ZERO);
+ } else {
+ ATF_REQUIRE(rv == -1);
+ ATF_REQUIRE(errno == EINVAL);
+ goto out;
+ }
/*
* First read the header to determine how much additional data
@@ -1129,6 +1139,7 @@ test_ktls_transmit_empty_fragment(struct tls_enable *en, uint64_t seqno)
"read %zd decrypted bytes for an empty fragment", rv);
ATF_REQUIRE(record_type == TLS_RLTYPE_APP);
+out:
free(outbuf);
ATF_REQUIRE(close(sockets[1]) == 0);
@@ -1369,7 +1380,7 @@ ATF_TC_BODY(ktls_transmit_##cipher_name##_##name, tc) \
ATF_TP_ADD_TC(tp, ktls_transmit_##cipher_name##_##name);
#define GEN_TRANSMIT_EMPTY_FRAGMENT_TEST(cipher_name, cipher_alg, \
- key_size, auth_alg) \
+ key_size, auth_alg, minor) \
ATF_TC_WITHOUT_HEAD(ktls_transmit_##cipher_name##_empty_fragment); \
ATF_TC_BODY(ktls_transmit_##cipher_name##_empty_fragment, tc) \
{ \
@@ -1378,14 +1389,14 @@ ATF_TC_BODY(ktls_transmit_##cipher_name##_empty_fragment, tc) \
\
ATF_REQUIRE_KTLS(); \
seqno = random(); \
- build_tls_enable(cipher_alg, key_size, auth_alg, \
- TLS_MINOR_VER_ZERO, seqno, &en); \
+ build_tls_enable(cipher_alg, key_size, auth_alg, minor, seqno, \
+ &en); \
test_ktls_transmit_empty_fragment(&en, seqno); \
free_tls_enable(&en); \
}
#define ADD_TRANSMIT_EMPTY_FRAGMENT_TEST(cipher_name, cipher_alg, \
- key_size, auth_alg) \
+ key_size, auth_alg, minor) \
ATF_TP_ADD_TC(tp, ktls_transmit_##cipher_name##_empty_fragment);
#define GEN_TRANSMIT_TESTS(cipher_name, cipher_alg, key_size, auth_alg, \
@@ -1506,7 +1517,9 @@ AES_CBC_TESTS(GEN_TRANSMIT_PADDING_TESTS);
* Test "empty fragments" which are TLS records with no payload that
* OpenSSL can send for TLS 1.0 connections.
*/
-TLS_10_TESTS(GEN_TRANSMIT_EMPTY_FRAGMENT_TEST);
+AES_CBC_TESTS(GEN_TRANSMIT_EMPTY_FRAGMENT_TEST);
+AES_GCM_TESTS(GEN_TRANSMIT_EMPTY_FRAGMENT_TEST);
+CHACHA20_TESTS(GEN_TRANSMIT_EMPTY_FRAGMENT_TEST);
static void
test_ktls_invalid_transmit_cipher_suite(struct tls_enable *en)
@@ -1768,7 +1781,9 @@ ATF_TP_ADD_TCS(tp)
AES_GCM_TESTS(ADD_TRANSMIT_TESTS);
CHACHA20_TESTS(ADD_TRANSMIT_TESTS);
AES_CBC_TESTS(ADD_TRANSMIT_PADDING_TESTS);
- TLS_10_TESTS(ADD_TRANSMIT_EMPTY_FRAGMENT_TEST);
+ AES_CBC_TESTS(ADD_TRANSMIT_EMPTY_FRAGMENT_TEST);
+ AES_GCM_TESTS(ADD_TRANSMIT_EMPTY_FRAGMENT_TEST);
+ CHACHA20_TESTS(ADD_TRANSMIT_EMPTY_FRAGMENT_TEST);
INVALID_CIPHER_SUITES(ADD_INVALID_TRANSMIT_TEST);
/* Receive tests */