8.7 KiB
Решение: Dilithium Storm
Дата: 08.01.2026 Автор: Claude Opus 4.5 (Anthropic) Источник: Google/Gemini 3 Pro audit
1. Подтверждённые проблемы
[HIGH] Dilithium Storm — CPU Exhaustion
Файл: protocol.rs:1175-1183
Message::AuthChallenge(challenge) => {
if let Some(ref secret_key) = config.hardcoded_secret_key {
// ПРОБЛЕМА: Синхронный вызов блокирует async runtime
let sig = match crate::crypto::sign_mldsa65(secret_key, &challenge) {
Some(s) => s,
None => { ... }
};
Проблемы:
sign_mldsa65— синхронная CPU-интенсивная операция (~1-5ms)- Нет rate limit для AuthChallenge в PeerRateLimits
- AuthChallenge разрешён pre_handshake (до Version/Verack)
[MEDIUM] Несовместимость verification.rs и protocol.rs
Файлы: verification.rs:472, protocol.rs:690-715
- verification.rs использует raw TCP без шифрования
- protocol.rs требует Noise XX handshake для всех соединений
- Эти компоненты несовместимы в текущем виде
2. Сравнение с Bitcoin Core
Bitcoin Core net_processing.cpp:
// Строка 3849-3852: ВСЕ сообщения кроме VERSION/VERACK отклоняются до handshake
if (!pfrom.fSuccessfullyConnected) {
LogDebug(BCLog::NET, "Unsupported message \"%s\" prior to verack from peer=%d\n", ...);
return;
}
// Строка 379-388: Token bucket для Addr rate limiting
double m_addr_token_bucket;
if (peer->m_addr_token_bucket < 1.0) {
if (rate_limited) {
++num_rate_limit;
continue;
}
}
Ключевые отличия:
| Bitcoin | Montana | Проблема |
|---|---|---|
| Только VERSION/VERACK до handshake | AuthChallenge разрешён | CPU exhaustion |
| Token bucket для Addr | Нет rate limit для AuthChallenge | Нет защиты |
| Синхронная обработка | Синхронная обработка | Блокирует runtime |
3. Идеальное решение
Изменение 1: Rate limit для AuthChallenge
Файл: rate_limit.rs
// Добавить в PeerRateLimits:
pub struct PeerRateLimits {
pub addr: AddrRateLimiter,
pub inv: InvRateLimiter,
pub getdata: GetDataRateLimiter,
pub headers: HeadersRateLimiter,
pub getslices: GetSlicesRateLimiter,
pub slice: SliceRateLimiter,
pub auth_challenge: TokenBucket, // НОВОЕ
}
impl PeerRateLimits {
pub fn new() -> Self {
Self {
// ...existing...
auth_challenge: TokenBucket::new(
3.0, // burst: 3 challenges allowed
0.05, // recovery: 1 challenge per 20 seconds
),
}
}
}
Изменение 2: spawn_blocking для sign_mldsa65
Файл: protocol.rs:1175-1205
Message::AuthChallenge(challenge) => {
// Rate limit check ПЕРВЫМ
if !peer.rate_limits.auth_challenge.try_consume() {
debug!("Rate limited AuthChallenge from {}", peer.addr);
return Ok(false);
}
if let Some(ref secret_key) = config.hardcoded_secret_key {
// Клонируем для move в spawn_blocking
let sk = secret_key.clone();
let ch = challenge.clone();
// КРИТИЧНО: Выносим CPU-интенсивную операцию из async runtime
let sig = tokio::task::spawn_blocking(move || {
crate::crypto::sign_mldsa65(&sk, &ch)
})
.await
.ok()
.flatten();
let sig = match sig {
Some(s) => s,
None => {
warn!("Failed to sign AuthChallenge");
return Ok(false);
}
};
// ... rest of response ...
}
}
Изменение 3: Требовать Version/Verack до AuthChallenge (опционально)
Файл: message.rs:89-98
pub fn allowed_pre_handshake(&self) -> bool {
matches!(
self,
Message::Version(_)
| Message::Verack
| Message::Reject(_)
// УБРАТЬ: | Message::AuthChallenge(_)
// УБРАТЬ: | Message::AuthResponse { .. }
)
}
ВНИМАНИЕ: Это изменение требует обновления verification.rs для отправки Version до AuthChallenge.
Изменение 4: Исправить verification.rs (обязательно)
Файл: verification.rs
Добавить Noise handshake:
async fn query_single_node(
addr: SocketAddr,
listen_port: u16,
is_hardcoded: bool,
testnet: bool,
keypair: &StaticKeypair, // НОВЫЙ параметр
) -> Result<(PeerChainInfo, Vec<SocketAddr>), VerificationError> {
let stream = timeout(
Duration::from_secs(CONNECT_TIMEOUT_SECS),
TcpStream::connect(addr),
)
.await??;
// НОВОЕ: Noise handshake
let encrypted = timeout(
Duration::from_secs(HANDSHAKE_TIMEOUT_SECS),
EncryptedStream::connect(stream, keypair),
)
.await
.map_err(|_| VerificationError::Timeout("noise".into()))?
.map_err(|e| VerificationError::Handshake(e.to_string()))?;
let (mut reader, mut writer) = encrypted.split();
// Теперь используем encrypted reader/writer
// ... rest of function ...
}
4. Порядок исправлений
| # | Изменение | Риск | Приоритет |
|---|---|---|---|
| 1 | Rate limit для AuthChallenge | Низкий | ВЫСОКИЙ |
| 2 | spawn_blocking для sign_mldsa65 | Низкий | ВЫСОКИЙ |
| 3 | Noise в verification.rs | Средний | ВЫСОКИЙ |
| 4 | Убрать AuthChallenge из pre_handshake | Высокий | СРЕДНИЙ |
Рекомендация: Применить #1, #2, #3 немедленно. #4 — после тестирования.
5. Почему это работает
Defense in Depth:
-
Rate limit — ограничивает количество AuthChallenge до 3 burst + 0.05/sec
- Атакующий может вызвать max 3 подписи, затем throttling
- При 117 inbound connections: 117 × 3 = 351 подписей (одноразово)
- После burst: 117 × 0.05 = 5.85 подписей/sec (приемлемо)
-
spawn_blocking — не блокирует async runtime
- tokio scheduler распределяет CPU между signing и networking
- Сервер продолжает отвечать на другие запросы
-
Noise handshake — увеличивает стоимость атаки
- Атакующий должен завершить ML-KEM-768 + X25519 handshake
- Это ~1ms CPU с обеих сторон
- Асимметрия уменьшается
-
Post-handshake AuthChallenge (опционально) — требует Version/Verack
- Соответствует Bitcoin Core модели
- Дополнительная валидация перед CPU-интенсивной операцией
6. Тестирование
Unit test для rate limit:
#[tokio::test]
async fn test_auth_challenge_rate_limit() {
let mut peer = Peer::new(addr, true, tx);
// Первые 3 должны пройти (burst)
assert!(peer.rate_limits.auth_challenge.try_consume());
assert!(peer.rate_limits.auth_challenge.try_consume());
assert!(peer.rate_limits.auth_challenge.try_consume());
// 4-й должен быть отклонён
assert!(!peer.rate_limits.auth_challenge.try_consume());
// После 20 секунд — 1 разрешён
tokio::time::sleep(Duration::from_secs(20)).await;
assert!(peer.rate_limits.auth_challenge.try_consume());
}
Integration test для spawn_blocking:
#[tokio::test]
async fn test_concurrent_auth_challenges() {
// Запустить 100 параллельных AuthChallenge
// Проверить что сервер остаётся responsive (ping/pong работает)
}
7. Финальный вердикт
| Проблема | Статус | Решение |
|---|---|---|
| Dilithium Storm (CPU DoS) | CONFIRMED | Rate limit + spawn_blocking |
| verification.rs несовместимость | CONFIRMED | Добавить Noise |
| AuthChallenge pre_handshake | DESIGN ISSUE | Опционально убрать |
Действие: Требуется исправление до mainnet.