mirror of
https://github.com/telemt/telemt.git
synced 2026-05-02 01: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:
105
src/proxy/tests/masking_relay_guardrails_security_tests.rs
Normal file
105
src/proxy/tests/masking_relay_guardrails_security_tests.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use super::*;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt, duplex, sink};
|
||||
use tokio::time::{Duration, timeout};
|
||||
|
||||
#[tokio::test]
|
||||
async fn relay_to_mask_enforces_masking_session_byte_cap() {
|
||||
let initial = vec![0x16, 0x03, 0x01, 0x00, 0x01];
|
||||
let extra = vec![0xAB; 96 * 1024];
|
||||
|
||||
let (client_reader, mut client_writer) = duplex(128 * 1024);
|
||||
let (mask_read, _mask_read_peer) = duplex(1024);
|
||||
let (mut mask_observer, mask_write) = duplex(256 * 1024);
|
||||
let initial_for_task = initial.clone();
|
||||
|
||||
let relay = tokio::spawn(async move {
|
||||
relay_to_mask(
|
||||
client_reader,
|
||||
sink(),
|
||||
mask_read,
|
||||
mask_write,
|
||||
&initial_for_task,
|
||||
false,
|
||||
512,
|
||||
4096,
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
32 * 1024,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
|
||||
client_writer.write_all(&extra).await.unwrap();
|
||||
client_writer.shutdown().await.unwrap();
|
||||
|
||||
timeout(Duration::from_secs(2), relay)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let mut observed = Vec::new();
|
||||
timeout(
|
||||
Duration::from_secs(2),
|
||||
mask_observer.read_to_end(&mut observed),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// In this deterministic test, relay must stop exactly at the configured cap.
|
||||
assert_eq!(
|
||||
observed.len(),
|
||||
initial.len() + (32 * 1024),
|
||||
"masked relay must forward exactly up to the cap (observed={} initial={} cap={})",
|
||||
observed.len(),
|
||||
initial.len(),
|
||||
32 * 1024
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn relay_to_mask_propagates_client_half_close_without_waiting_for_other_direction_timeout() {
|
||||
let initial = b"GET /half-close HTTP/1.1\r\n".to_vec();
|
||||
|
||||
let (client_reader, mut client_writer) = duplex(8 * 1024);
|
||||
let (mask_read, _mask_read_peer) = duplex(8 * 1024);
|
||||
let (mut mask_observer, mask_write) = duplex(8 * 1024);
|
||||
let initial_for_task = initial.clone();
|
||||
|
||||
let relay = tokio::spawn(async move {
|
||||
relay_to_mask(
|
||||
client_reader,
|
||||
sink(),
|
||||
mask_read,
|
||||
mask_write,
|
||||
&initial_for_task,
|
||||
false,
|
||||
512,
|
||||
4096,
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
32 * 1024,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
|
||||
client_writer.shutdown().await.unwrap();
|
||||
|
||||
let mut observed = Vec::new();
|
||||
timeout(
|
||||
Duration::from_millis(80),
|
||||
mask_observer.read_to_end(&mut observed),
|
||||
)
|
||||
.await
|
||||
.expect("mask backend write side should be half-closed promptly")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(&observed[..initial.len()], initial.as_slice());
|
||||
|
||||
timeout(Duration::from_secs(2), relay)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
}
|
||||
Reference in New Issue
Block a user