mirror of
https://github.com/telemt/telemt.git
synced 2026-04-25 22:44:10 +03:00
Security hardening, concurrency fixes, and expanded test coverage
This commit introduces a comprehensive set of improvements to enhance the security, reliability, and configurability of the proxy server, specifically targeting adversarial resilience and high-load concurrency. Security & Cryptography: - Zeroize MTProto cryptographic key material (`dec_key`, `enc_key`) immediately after use to prevent memory leakage on early returns. - Move TLS handshake replay tracking after full policy/ALPN validation to prevent cache poisoning by unauthenticated probes. - Add `proxy_protocol_trusted_cidrs` configuration to restrict PROXY protocol headers to trusted networks, rejecting spoofed IPs. Adversarial Resilience & DoS Mitigation: - Implement "Tiny Frame Debt" tracking in the middle-relay to prevent CPU exhaustion from malicious 0-byte or 1-byte frame floods. - Add `mask_relay_max_bytes` to strictly bound unauthenticated fallback connections, preventing the proxy from being abused as an open relay. - Add a 5ms prefetch window (`mask_classifier_prefetch_timeout_ms`) to correctly assemble and classify fragmented HTTP/1.1 and HTTP/2 probes (e.g., `PRI * HTTP/2.0`) before routing them to masking heuristics. - Prevent recursive masking loops (FD exhaustion) by verifying the mask target is not the proxy's own listener via local interface enumeration. Concurrency & Reliability: - Eliminate executor waker storms during quota lock contention by replacing the spin-waker task with inline `Sleep` and exponential backoff. - Roll back user quota reservations (`rollback_me2c_quota_reservation`) if a network write fails, preventing Head-of-Line (HoL) blocking from permanently burning data quotas. - Recover gracefully from idle-registry `Mutex` poisoning instead of panicking, ensuring isolated thread failures do not break the proxy. - Fix `auth_probe_scan_start_offset` modulo logic to ensure bounds safety. Testing: - Add extensive adversarial, timing, fuzzing, and invariant test suites for both the client and handshake modules.
This commit is contained in:
@@ -121,6 +121,20 @@ fn auth_probe_eviction_offset(peer_ip: IpAddr, now: Instant) -> usize {
|
||||
hasher.finish() as usize
|
||||
}
|
||||
|
||||
fn auth_probe_scan_start_offset(
|
||||
peer_ip: IpAddr,
|
||||
now: Instant,
|
||||
state_len: usize,
|
||||
scan_limit: usize,
|
||||
) -> usize {
|
||||
if state_len == 0 || scan_limit == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let window = state_len.min(scan_limit);
|
||||
auth_probe_eviction_offset(peer_ip, now) % window
|
||||
}
|
||||
|
||||
fn auth_probe_is_throttled(peer_ip: IpAddr, now: Instant) -> bool {
|
||||
let peer_ip = normalize_auth_probe_ip(peer_ip);
|
||||
let state = auth_probe_state_map();
|
||||
@@ -269,11 +283,7 @@ fn auth_probe_record_failure_with_state(
|
||||
let mut eviction_candidate: Option<(IpAddr, u32, Instant)> = None;
|
||||
let state_len = state.len();
|
||||
let scan_limit = state_len.min(AUTH_PROBE_PRUNE_SCAN_LIMIT);
|
||||
let start_offset = if state_len == 0 {
|
||||
0
|
||||
} else {
|
||||
auth_probe_eviction_offset(peer_ip, now) % state_len
|
||||
};
|
||||
let start_offset = auth_probe_scan_start_offset(peer_ip, now, state_len, scan_limit);
|
||||
|
||||
let mut scanned = 0usize;
|
||||
for entry in state.iter().skip(start_offset) {
|
||||
@@ -769,7 +779,7 @@ where
|
||||
let mut dec_key_input = Zeroizing::new(Vec::with_capacity(PREKEY_LEN + secret.len()));
|
||||
dec_key_input.extend_from_slice(dec_prekey);
|
||||
dec_key_input.extend_from_slice(&secret);
|
||||
let dec_key = sha256(&dec_key_input);
|
||||
let dec_key = Zeroizing::new(sha256(&dec_key_input));
|
||||
|
||||
let mut dec_iv_arr = [0u8; IV_LEN];
|
||||
dec_iv_arr.copy_from_slice(dec_iv_bytes);
|
||||
@@ -805,7 +815,7 @@ where
|
||||
let mut enc_key_input = Zeroizing::new(Vec::with_capacity(PREKEY_LEN + secret.len()));
|
||||
enc_key_input.extend_from_slice(enc_prekey);
|
||||
enc_key_input.extend_from_slice(&secret);
|
||||
let enc_key = sha256(&enc_key_input);
|
||||
let enc_key = Zeroizing::new(sha256(&enc_key_input));
|
||||
|
||||
let mut enc_iv_arr = [0u8; IV_LEN];
|
||||
enc_iv_arr.copy_from_slice(enc_iv_bytes);
|
||||
@@ -830,9 +840,9 @@ where
|
||||
user: user.clone(),
|
||||
dc_idx,
|
||||
proto_tag,
|
||||
dec_key,
|
||||
dec_key: *dec_key,
|
||||
dec_iv,
|
||||
enc_key,
|
||||
enc_key: *enc_key,
|
||||
enc_iv,
|
||||
peer,
|
||||
is_tls,
|
||||
@@ -979,6 +989,14 @@ mod saturation_poison_security_tests;
|
||||
#[path = "tests/handshake_auth_probe_hardening_adversarial_tests.rs"]
|
||||
mod auth_probe_hardening_adversarial_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/handshake_auth_probe_scan_budget_security_tests.rs"]
|
||||
mod auth_probe_scan_budget_security_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/handshake_auth_probe_scan_offset_stress_tests.rs"]
|
||||
mod auth_probe_scan_offset_stress_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/handshake_advanced_clever_tests.rs"]
|
||||
mod advanced_clever_tests;
|
||||
@@ -995,6 +1013,10 @@ mod real_bug_stress_tests;
|
||||
#[path = "tests/handshake_timing_manual_bench_tests.rs"]
|
||||
mod timing_manual_bench_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/handshake_key_material_zeroization_security_tests.rs"]
|
||||
mod handshake_key_material_zeroization_security_tests;
|
||||
|
||||
/// Compile-time guard: HandshakeSuccess holds cryptographic key material and
|
||||
/// must never be Copy. A Copy impl would allow silent key duplication,
|
||||
/// undermining the zeroize-on-drop guarantee.
|
||||
|
||||
Reference in New Issue
Block a user