VYPR
High severity7.5NVD Advisory· Published Apr 19, 2024· Updated Apr 15, 2026

CVE-2024-32650

CVE-2024-32650

Description

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.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
rustlscrates.io
>= 0.23.0, < 0.23.50.23.5
rustlscrates.io
>= 0.22.0, < 0.22.40.22.4
rustlscrates.io
>= 0.21.0, < 0.21.110.21.11

Patches

5
6e938bcfe82a

complete_io: bail out if progress is impossible

https://github.com/rustls/rustlsJoseph Birr-PixtonApr 18, 2024via ghsa
2 files changed · +43 0
  • rustls/src/conn.rs+5 0 modified
    @@ -553,6 +553,11 @@ impl<Data> ConnectionCommon<Data> {
             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)?;
                 }
    
  • rustls/tests/api.rs+38 0 modified
    @@ -6335,6 +6335,44 @@ fn test_complete_io_errors_if_close_notify_received_too_early() {
         );
     }
     
    +#[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> {
    
f45664fbded0

Don't specially handle unauthenticated close_notify alerts

https://github.com/rustls/rustlsJoseph Birr-PixtonApr 18, 2024via ghsa
1 file changed · +2 2
  • rustls/src/common_state.rs+2 2 modified
    @@ -431,8 +431,8 @@ impl CommonState {
             }
     
             // If we get a CloseNotify, make a note to declare EOF to our
    -        // caller.
    -        if alert.description == AlertDescription::CloseNotify {
    +        // caller.  But do not treat unauthenticated alerts like this.
    +        if self.may_receive_application_data && alert.description == AlertDescription::CloseNotify {
                 self.has_received_close_notify = true;
                 return Ok(());
             }
    
2123576840aa

Regression test for `complete_io` infinite loop bug

https://github.com/rustls/rustlsJoseph Birr-PixtonApr 18, 2024via ghsa
1 file changed · +51 0
  • rustls/tests/api.rs+51 0 modified
    @@ -6306,6 +6306,57 @@ fn test_server_fips_service_indicator_includes_require_ems() {
         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)]
    
ebcb4782f23b

complete_io: bail out if progress is impossible

https://github.com/rustls/rustlsJoseph Birr-PixtonApr 18, 2024via ghsa
2 files changed · +43 0
  • rustls/src/conn.rs+5 0 modified
    @@ -378,6 +378,11 @@ impl<Data> ConnectionCommon<Data> {
             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)?;
                 }
    
  • rustls/tests/api.rs+38 0 modified
    @@ -4976,6 +4976,44 @@ fn test_complete_io_errors_if_close_notify_received_too_early() {
         );
     }
     
    +#[test]
    +fn test_complete_io_with_no_io_needed() {
    +    let (mut client, mut server) = make_pair(KeyType::Rsa);
    +    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> {
    
5374108df698

complete_io: bail out if progress is impossible

https://github.com/rustls/rustlsJoseph Birr-PixtonApr 18, 2024via ghsa
2 files changed · +43 0
  • rustls/src/conn.rs+5 0 modified
    @@ -391,6 +391,11 @@ impl<Data> ConnectionCommon<Data> {
             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)?;
                 }
    
  • rustls/tests/api.rs+38 0 modified
    @@ -5855,6 +5855,44 @@ fn test_complete_io_errors_if_close_notify_received_too_early() {
         );
     }
     
    +#[test]
    +fn test_complete_io_with_no_io_needed() {
    +    let (mut client, mut server) = make_pair(KeyType::Rsa);
    +    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> {
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.