Skip to content

Commit d039b55

Browse files
committed
Reject a malformed version message instead of panicking
The server parsed the client's version message with version_bytes[..4], which panics on a body shorter than 4 bytes — a remotely triggerable panic for any TLS-authenticated peer, and process death under panic = "abort". Parse with a checked conversion and reject anything but exactly 4 bytes as Error::ProtocolVersion. This is stricter than the old code in one way: a version message longer than 4 bytes was previously accepted with the trailing bytes ignored. No client has ever sent one (both protocol versions send exactly CURRENT_PROTOCOL_VERSION.to_le_bytes()), so no interoperability change.
1 parent 7e715cc commit d039b55

2 files changed

Lines changed: 78 additions & 2 deletions

File tree

tls/src/lib.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,76 @@ mod tests {
606606
handle.await.unwrap();
607607
}
608608

609+
// A version message whose body is shorter than the 4-byte version, sent
610+
// by a TLS-authenticated client, is rejected as Error::ProtocolVersion —
611+
// the server task must error, not panic on a short slice.
612+
#[tokio::test]
613+
async fn short_version_message_rejected() {
614+
let log = logger();
615+
let mock_datadir = mock_datadir();
616+
let addr: SocketAddrV6 = SocketAddrV6::from_str("[::1]:46467").unwrap();
617+
618+
let server_config =
619+
local_config(1, MeasurementConnectionPolicy::Enforced);
620+
let corpus = vec![
621+
mock_datadir.join("corim-rot.cbor"),
622+
mock_datadir.join("corim-sp.cbor"),
623+
];
624+
625+
let log2 = log.clone();
626+
let handle = tokio::spawn(async move {
627+
let server = Server::new(server_config, addr, log2.clone())
628+
.await
629+
.unwrap();
630+
631+
let result = server
632+
.accept(corpus.clone())
633+
.await
634+
.unwrap()
635+
.handshake()
636+
.await;
637+
match result {
638+
Err(Error::ProtocolVersion) => {}
639+
Err(other) => {
640+
panic!("expected ProtocolVersion, got {other:?}")
641+
}
642+
Ok(_) => {
643+
panic!("a malformed version message must not complete")
644+
}
645+
}
646+
});
647+
648+
let client_config = client::Client::new_tls_local_client_config(
649+
mock_datadir.join("test-sprockets-auth-2.key.pem"),
650+
mock_datadir.join("test-sprockets-auth-2.certlist.pem"),
651+
vec![mock_datadir.join("test-root-a.cert.pem")],
652+
log,
653+
)
654+
.unwrap();
655+
656+
let dnsname =
657+
rustls::pki_types::ServerName::try_from("unknown.com").unwrap();
658+
let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(
659+
client_config,
660+
));
661+
let stream = loop {
662+
if let Ok(s) = tokio::net::TcpStream::connect(addr).await {
663+
break s;
664+
};
665+
sleep(Duration::from_millis(1)).await;
666+
};
667+
let mut stream = connector.connect(dnsname, stream).await.unwrap();
668+
669+
// A valid length prefix (2) followed by a 2-byte body: the server's
670+
// recv_msg succeeds, but the body is shorter than the 4-byte version
671+
// it must contain.
672+
stream.write_all(&2u32.to_le_bytes()).await.unwrap();
673+
stream.write_all(b"xy").await.unwrap();
674+
stream.shutdown().await.unwrap();
675+
676+
handle.await.unwrap();
677+
}
678+
609679
#[tokio::test]
610680
async fn spawn_accept() {
611681
let log = logger();

tls/src/server.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,14 @@ impl SprocketsAcceptor {
138138

139139
// get version from the client
140140
let version_bytes = recv_msg(&mut stream).await?;
141-
let version =
142-
u32::from_le_bytes(version_bytes[..4].try_into().unwrap());
141+
// Anything but exactly the 4-byte little-endian version is protocol
142+
// garbage from the peer; reject it rather than index past the end of a
143+
// short message.
144+
let version_bytes: [u8; 4] = version_bytes
145+
.as_slice()
146+
.try_into()
147+
.map_err(|_| Error::ProtocolVersion)?;
148+
let version = u32::from_le_bytes(version_bytes);
143149

144150
if version == CURRENT_PROTOCOL_VERSION {
145151
// we're good to go

0 commit comments

Comments
 (0)