CVE-2024-32650: Insufficient Information (NVD-CWE-noinfo)
value: Rustls is a modern TLS library written in Rust. `rustls::ConnectionCommon::complete_io` could fall into an infinite loop based on network input. When using a blocking rustls server, if a client send a `close_notify` message immediately after `client_hello`, the server's `complete_io` will get in an infinite loop. This vulnerability is fixed in 0.23.5, 0.22.4, and 0.21.11.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-19T16:15Z
Repository: https://github.com/rustls/rustls
Language: Rust
Files changed: rustls/src/conn.rs, rustls/tests/api.rs
Commit date: 2024-04-19 15:01:19 +0000
Commit message: complete_io: bail out if progress is impossible

Have a test that demonstrates another route to reaching this
state: a fully & cleanly closed connection.

Diff: diff --git a/rustls/src/conn.rs b/rustls/src/conn.rs
index d6d06cdc..2a046895 100644
--- a/rustls/src/conn.rs
+++ b/rustls/src/conn.rs
@@ -551,10 +551,15 @@ impl<Data> ConnectionCommon<Data> {
         let mut rdlen = 0;
 
         loop {
             let until_handshaked = self.is_handshaking();
 
+            if !self.wants_write() && !self.wants_read() {
+                // We will make no further progress.
+                return Ok((rdlen, wrlen));
+            }
+
             while self.wants_write() {
                 wrlen += self.write_tls(io)?;
             }
             io.flush()?;
 
diff --git a/rustls/tests/api.rs b/rustls/tests/api.rs
index 61a9fce7..f7c0b80d 100644
--- a/rustls/tests/api.rs
+++ b/rustls/tests/api.rs
@@ -6333,10 +6333,48 @@ fn test_complete_io_errors_if_close_notify_received_too_early() {
             .kind(),
         io::ErrorKind::UnexpectedEof
     );
 }
 
+#[test]
+fn test_complete_io_with_no_io_needed() {
+    let (mut client, mut server) = make_pair(KeyType::Rsa2048);
+    do_handshake(&mut client, &mut server);
+    client
+        .writer()
+        .write_all(b"hello")
+        .unwrap();
+    client.send_close_notify();
+    transfer(&mut client, &mut server);
+    server.process_new_packets().unwrap();
+    server
+        .writer()
+        .write_all(b"hello")
+        .unwrap();
+    server.send_close_notify();
+    transfer(&mut server, &mut client);
+    client.process_new_packets().unwrap();
+
+    // neither want any IO: both directions are closed.
+    assert!(!client.wants_write());
+    assert!(!client.wants_read());
+    assert!(!server.wants_write());
+    assert!(!server.wants_read());
+    assert_eq!(
+        client
+            .complete_io(&mut FakeStream(&[]))
+            .unwrap(),
+        (0, 0)
+    );
+    assert_eq!(
+        server
+            .complete_io(&mut FakeStream(&[]))
+            .unwrap(),
+        (0, 0)
+    );
+}
+
 struct FakeStream<'a>(&'a [u8]);
 
 impl<'a> io::Read for FakeStream<'a> {
     fn read(&mut self, b: &mut [u8]) -> io::Result<usize> {
         let take = core::cmp::min(b.len(), self.0.len());

Fixed code:             if !self.wants_write() && !self.wants_read() {
                // We will make no further progress.
                return Ok((rdlen, wrlen));
            }

#[test]
fn test_complete_io_with_no_io_needed() {
    let (mut client, mut server) = make_pair(KeyType::Rsa2048);
    do_handshake(&mut client, &mut server);
    client
        .writer()
        .write_all(b"hello")
        .unwrap();
    client.send_close_notify();
    transfer(&mut client, &mut server);
    server.process_new_packets().unwrap();
    server
        .writer()
        .write_all(b"hello")
        .unwrap();
    server.send_close_notify();
    transfer(&mut server, &mut client);
    client.process_new_packets().unwrap();

    // neither want any IO: both directions are closed.
    assert!(!client.wants_write());
    assert!(!client.wants_read());
    assert!(!server.wants_write());
    assert!(!server.wants_read());
    assert_eq!(
        client
            .complete_io(&mut FakeStream(&[]))
            .unwrap(),
        (0, 0)
    );
    assert_eq!(
        server
            .complete_io(&mut FakeStream(&[]))
            .unwrap(),
        (0, 0)
    );
}