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/tests/api.rs
Commit date: 2024-04-19 15:01:19 +0000
Commit message: Regression test for `complete_io` infinite loop bug

Diff: diff --git a/rustls/tests/api.rs b/rustls/tests/api.rs
index 0774e61e..61a9fce7 100644
--- a/rustls/tests/api.rs
+++ b/rustls/tests/api.rs
@@ -6304,10 +6304,61 @@ fn test_server_fips_service_indicator_includes_require_ems() {
     assert!(server_config.fips());
     server_config.require_ems = false;
     assert!(!server_config.fips());
 }
 
+#[test]
+fn test_complete_io_errors_if_close_notify_received_too_early() {
+    let mut server = ServerConnection::new(Arc::new(make_server_config(KeyType::Rsa2048))).unwrap();
+    let client_hello_followed_by_close_notify_alert = b"\
+        \x16\x03\x01\x00\xc8\x01\x00\x00\xc4\x03\x03\xec\x12\xdd\x17\x64\
+        \xa4\x39\xfd\x7e\x8c\x85\x46\xb8\x4d\x1e\xa0\x6e\xb3\xd7\xa0\x51\
+        \xf0\x3c\xb8\x17\x47\x0d\x4c\x54\xc5\xdf\x72\x00\x00\x1c\xea\xea\
+        \xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9\xcc\xa8\xc0\x13\xc0\x14\
+        \x00\x9c\x00\x9d\x00\x2f\x00\x35\x00\x0a\x01\x00\x00\x7f\xda\xda\
+        \x00\x00\xff\x01\x00\x01\x00\x00\x00\x00\x16\x00\x14\x00\x00\x11\
+        \x77\x77\x77\x2e\x77\x69\x6b\x69\x70\x65\x64\x69\x61\x2e\x6f\x72\
+        \x67\x00\x17\x00\x00\x00\x23\x00\x00\x00\x0d\x00\x14\x00\x12\x04\
+        \x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01\x02\
+        \x01\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x00\x10\
+        \x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31\
+        \x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x0a\x00\x08\
+        \x1a\x1a\x00\x1d\x00\x17\x00\x18\x1a\x1a\x00\x01\x00\
+        \x15\x03\x03\x00\x02\x01\x00";
+
+    let mut stream = FakeStream(client_hello_followed_by_close_notify_alert);
+    assert_eq!(
+        server
+            .complete_io(&mut stream)
+            .unwrap_err()
+            .kind(),
+        io::ErrorKind::UnexpectedEof
+    );
+}
+
+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());
+        let (taken, remain) = self.0.split_at(take);
+        b[..take].copy_from_slice(taken);
+        self.0 = remain;
+        Ok(take)
+    }
+}
+
+impl<'a> io::Write for FakeStream<'a> {
+    fn write(&mut self, b: &[u8]) -> io::Result<usize> {
+        Ok(b.len())
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
 } // test_for_each_provider!
 
 #[derive(Default, Debug)]
 struct LogCounts {
     trace: usize,

Fixed code: #[test]
fn test_complete_io_errors_if_close_notify_received_too_early() {
    let mut server = ServerConnection::new(Arc::new(make_server_config(KeyType::Rsa2048))).unwrap();
    let client_hello_followed_by_close_notify_alert = b"\
        \x16\x03\x01\x00\xc8\x01\x00\x00\xc4\x03\x03\xec\x12\xdd\x17\x64\
        \xa4\x39\xfd\x7e\x8c\x85\x46\xb8\x4d\x1e\xa0\x6e\xb3\xd7\xa0\x51\
        \xf0\x3c\xb8\x17\x47\x0d\x4c\x54\xc5\xdf\x72\x00\x00\x1c\xea\xea\
        \xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9\xcc\xa8\xc0\x13\xc0\x14\
        \x00\x9c\x00\x9d\x00\x2f\x00\x35\x00\x0a\x01\x00\x00\x7f\xda\xda\
        \x00\x00\xff\x01\x00\x01\x00\x00\x00\x00\x16\x00\x14\x00\x00\x11\
        \x77\x77\x77\x2e\x77\x69\x6b\x69\x70\x65\x64\x69\x61\x2e\x6f\x72\
        \x67\x00\x17\x00\x00\x00\x23\x00\x00\x00\x0d\x00\x14\x00\x12\x04\
        \x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01\x02\
        \x01\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x00\x10\
        \x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31\x2e\x31\
        \x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x0a\x00\x08\
        \x1a\x1a\x00\x1d\x00\x17\x00\x18\x1a\x1a\x00\x01\x00\
        \x15\x03\x03\x00\x02\x01\x00";

    let mut stream = FakeStream(client_hello_followed_by_close_notify_alert);
    assert_eq!(
        server
            .complete_io(&mut stream)
            .unwrap_err()
            .kind(),
        io::ErrorKind::UnexpectedEof
    );
}

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());
        let (taken, remain) = self.0.split_at(take);
        b[..take].copy_from_slice(taken);
        self.0 = remain;
        Ok(take)
    }
}

impl<'a> io::Write for FakeStream<'a> {
    fn write(&mut self, b: &[u8]) -> io::Result<usize> {
        Ok(b.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}