refactor: update TLS record size constants and related validations

- Rename MAX_TLS_RECORD_SIZE to MAX_TLS_PLAINTEXT_SIZE for clarity.
- Rename MAX_TLS_CHUNK_SIZE to MAX_TLS_CIPHERTEXT_SIZE to reflect its purpose.
- Deprecate old constants in favor of new ones.
- Update various parts of the codebase to use the new constants, including validation checks and tests.
- Add new tests to ensure compliance with RFC 8446 regarding TLS record sizes.
This commit is contained in:
David Osipov
2026-03-20 21:00:36 +04:00
parent 801f670827
commit 3abde52de8
11 changed files with 713 additions and 54 deletions

View File

@@ -111,7 +111,7 @@ fn wrap_tls_application_record(payload: &[u8]) -> Vec<u8> {
}
fn tls_clienthello_len_in_bounds(tls_len: usize) -> bool {
(MIN_TLS_CLIENT_HELLO_SIZE..=MAX_TLS_RECORD_SIZE).contains(&tls_len)
(MIN_TLS_CLIENT_HELLO_SIZE..=MAX_TLS_PLAINTEXT_SIZE).contains(&tls_len)
}
async fn read_with_progress<R: AsyncRead + Unpin>(reader: &mut R, mut buf: &mut [u8]) -> std::io::Result<usize> {
@@ -281,7 +281,7 @@ where
// incorrectly rejecting compact but spec-compliant ClientHellos from
// third-party clients or future Telegram versions.
if !tls_clienthello_len_in_bounds(tls_len) {
debug!(peer = %real_peer, tls_len = tls_len, max_tls_len = MAX_TLS_RECORD_SIZE, "TLS handshake length out of bounds");
debug!(peer = %real_peer, tls_len = tls_len, max_tls_len = MAX_TLS_PLAINTEXT_SIZE, "TLS handshake length out of bounds");
stats.increment_connects_bad();
let (reader, writer) = tokio::io::split(stream);
handle_bad_client(
@@ -729,7 +729,7 @@ impl RunningClientHandler {
// incorrectly rejecting compact but spec-compliant ClientHellos from
// third-party clients or future Telegram versions.
if !tls_clienthello_len_in_bounds(tls_len) {
debug!(peer = %peer, tls_len = tls_len, max_tls_len = MAX_TLS_RECORD_SIZE, "TLS handshake length out of bounds");
debug!(peer = %peer, tls_len = tls_len, max_tls_len = MAX_TLS_PLAINTEXT_SIZE, "TLS handshake length out of bounds");
self.stats.increment_connects_bad();
let (reader, writer) = self.stream.into_split();
handle_bad_client(

View File

@@ -3335,8 +3335,8 @@ async fn oversized_tls_record_is_masked_in_generic_stream_pipeline() {
0x16,
0x03,
0x01,
(((MAX_TLS_RECORD_SIZE + 1) >> 8) & 0xff) as u8,
((MAX_TLS_RECORD_SIZE + 1) & 0xff) as u8,
(((MAX_TLS_PLAINTEXT_SIZE + 1) >> 8) & 0xff) as u8,
((MAX_TLS_PLAINTEXT_SIZE + 1) & 0xff) as u8,
];
let backend_reply = b"HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n".to_vec();
@@ -3438,8 +3438,8 @@ async fn oversized_tls_record_is_masked_in_client_handler_pipeline() {
0x16,
0x03,
0x01,
(((MAX_TLS_RECORD_SIZE + 1) >> 8) & 0xff) as u8,
((MAX_TLS_RECORD_SIZE + 1) & 0xff) as u8,
(((MAX_TLS_PLAINTEXT_SIZE + 1) >> 8) & 0xff) as u8,
((MAX_TLS_PLAINTEXT_SIZE + 1) & 0xff) as u8,
];
let backend_reply = b"HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n".to_vec();
@@ -3769,7 +3769,7 @@ async fn tls_record_len_16384_is_accepted_in_generic_stream_pipeline() {
let backend_addr = listener.local_addr().unwrap();
let secret = [0x55u8; 16];
let client_hello = make_valid_tls_client_hello_with_len(&secret, 0, MAX_TLS_RECORD_SIZE);
let client_hello = make_valid_tls_client_hello_with_len(&secret, 0, MAX_TLS_PLAINTEXT_SIZE);
let mut cfg = ProxyConfig::default();
cfg.general.beobachten = false;
@@ -3865,7 +3865,7 @@ async fn tls_record_len_16384_is_accepted_in_client_handler_pipeline() {
let front_addr = front_listener.local_addr().unwrap();
let secret = [0x66u8; 16];
let client_hello = make_valid_tls_client_hello_with_len(&secret, 0, MAX_TLS_RECORD_SIZE);
let client_hello = make_valid_tls_client_hello_with_len(&secret, 0, MAX_TLS_PLAINTEXT_SIZE);
let mut cfg = ProxyConfig::default();
cfg.general.beobachten = false;

View File

@@ -4,7 +4,7 @@
use super::*;
use crate::config::{UpstreamConfig, UpstreamType};
use crate::protocol::constants::{MAX_TLS_RECORD_SIZE, MIN_TLS_CLIENT_HELLO_SIZE};
use crate::protocol::constants::{MAX_TLS_PLAINTEXT_SIZE, MIN_TLS_CLIENT_HELLO_SIZE};
use std::net::SocketAddr;
use std::time::Duration;
use tokio::io::{duplex, AsyncReadExt, AsyncWriteExt};
@@ -124,7 +124,7 @@ async fn tls_client_hello_lower_bound_minus_one_is_masked_and_counted_bad() {
#[tokio::test]
async fn tls_client_hello_upper_bound_plus_one_is_masked_and_counted_bad() {
run_probe_and_assert_masking(MAX_TLS_RECORD_SIZE + 1, true).await;
run_probe_and_assert_masking(MAX_TLS_PLAINTEXT_SIZE + 1, true).await;
}
#[tokio::test]
@@ -142,9 +142,9 @@ fn tls_client_hello_len_bounds_unit_adversarial_sweep() {
(101usize, true),
(511usize, true),
(512usize, true),
(16_383usize, true),
(16_384usize, true),
(16_385usize, false),
(MAX_TLS_PLAINTEXT_SIZE - 1, true),
(MAX_TLS_PLAINTEXT_SIZE, true),
(MAX_TLS_PLAINTEXT_SIZE + 1, false),
(u16::MAX as usize, false),
(usize::MAX, false),
];
@@ -168,12 +168,12 @@ fn tls_client_hello_len_bounds_light_fuzz_deterministic_lcg() {
0 => MIN_TLS_CLIENT_HELLO_SIZE - 1,
1 => MIN_TLS_CLIENT_HELLO_SIZE,
2 => MIN_TLS_CLIENT_HELLO_SIZE + 1,
3 => MAX_TLS_RECORD_SIZE - 1,
4 => MAX_TLS_RECORD_SIZE,
5 => MAX_TLS_RECORD_SIZE + 1,
3 => MAX_TLS_PLAINTEXT_SIZE - 1,
4 => MAX_TLS_PLAINTEXT_SIZE,
5 => MAX_TLS_PLAINTEXT_SIZE + 1,
_ => base,
};
let expect_bad = !(MIN_TLS_CLIENT_HELLO_SIZE..=MAX_TLS_RECORD_SIZE).contains(&len);
let expect_bad = !(MIN_TLS_CLIENT_HELLO_SIZE..=MAX_TLS_PLAINTEXT_SIZE).contains(&len);
assert_eq!(
tls_clienthello_len_in_bounds(len),
!expect_bad,
@@ -186,9 +186,9 @@ fn tls_client_hello_len_bounds_light_fuzz_deterministic_lcg() {
fn tls_client_hello_len_bounds_stress_many_evaluations() {
for _ in 0..100_000 {
assert!(tls_clienthello_len_in_bounds(MIN_TLS_CLIENT_HELLO_SIZE));
assert!(tls_clienthello_len_in_bounds(MAX_TLS_RECORD_SIZE));
assert!(tls_clienthello_len_in_bounds(MAX_TLS_PLAINTEXT_SIZE));
assert!(!tls_clienthello_len_in_bounds(MIN_TLS_CLIENT_HELLO_SIZE - 1));
assert!(!tls_clienthello_len_in_bounds(MAX_TLS_RECORD_SIZE + 1));
assert!(!tls_clienthello_len_in_bounds(MAX_TLS_PLAINTEXT_SIZE + 1));
}
}

View File

@@ -1,7 +1,7 @@
use super::*;
use crate::config::{UpstreamConfig, UpstreamType};
use crate::crypto::sha256_hmac;
use crate::protocol::constants::{HANDSHAKE_LEN, MAX_TLS_CHUNK_SIZE, TLS_VERSION};
use crate::protocol::constants::{HANDSHAKE_LEN, MAX_TLS_CIPHERTEXT_SIZE, TLS_VERSION};
use crate::protocol::tls;
use tokio::io::{duplex, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
@@ -347,7 +347,7 @@ async fn tls_bad_mtproto_fallback_forwards_max_tls_record_verbatim() {
let client_hello = make_valid_tls_client_hello(&secret, 3, 600, 0x45);
let invalid_mtproto = vec![0u8; HANDSHAKE_LEN];
let invalid_mtproto_record = wrap_tls_application_data(&invalid_mtproto);
let trailing_payload = vec![0xAB; MAX_TLS_CHUNK_SIZE];
let trailing_payload = vec![0xAB; MAX_TLS_CIPHERTEXT_SIZE];
let trailing_record = wrap_tls_application_data(&trailing_payload);
let expected_client_hello = client_hello.clone();
@@ -1786,7 +1786,7 @@ async fn tls_bad_mtproto_fallback_coalesced_tail_max_payload_is_forwarded() {
let secret = [0xA5u8; 16];
let client_hello = make_valid_tls_client_hello(&secret, 304, 600, 0x35);
let coalesced_tail = vec![0xEF; MAX_TLS_CHUNK_SIZE - HANDSHAKE_LEN];
let coalesced_tail = vec![0xEF; MAX_TLS_CIPHERTEXT_SIZE - HANDSHAKE_LEN];
let coalesced_record = wrap_invalid_mtproto_with_coalesced_tail(&coalesced_tail);
let expected_tail_record = wrap_tls_application_data(&coalesced_tail);