montana/Русский/Совет/Cursor/аудит_сетевого_уровня_15.01.2025_20:00.md

452 lines
27 KiB
Markdown
Raw Normal View History

# Security Audit: Сетевой слой Montana
**Модель:** Composer 1
**Компания:** Cursor
**Дата:** 15.01.2025 20:00 UTC
---
## 1. Понимание архитектуры
Montana использует принципиально иную модель консенсуса — Atemporal Coordinate Presence (ACP), основанную на физических ограничениях времени и вычислений, а не на традиционных механизмах блокчейна.
### Ключевые отличия от традиционных систем:
1. **Время как основа консенсуса**: Montana использует физические ограничения Слоя -1 (упорядоченность событий) для создания временных координат (τ₂), которые физически невозможно подделать или ускорить. Это означает, что атаки типа "long-range" или "nothing-at-stake" не применимы, так как нельзя создать валидные координаты для прошлого времени.
2. **Отсутствие майнеров/валидаторов**: Вместо этого используется детерминистическая лотерея на основе хешей для выбора "победителей" слайсов. Без майнинга или стейкинга нет экономических стимулов для атак типа 51% или selfish mining.
3. **Fork choice через время**: Выбор форка основан на временной цепочке (timechain), где каждый слайс ссылается на предыдущий через хеш. Это делает реорганизации предсказуемыми и ограниченными временем.
4. **Криптографическая безопасность**: Все подписи используют ML-DSA-65 (пост-квантовая криптография), что защищает от будущих квантовых атак. Присутствие (presence) требует реального времени для создания подписей.
5. **Sybil модель**: В Montana Sybil-атаки влияют в основном на распределение наград, а не на безопасность сети, так как безопасность обеспечивается криптографически, а накопление присутствия требует реального времени.
### Следствия для безопасности сетевого слоя:
- Защита от Eclipse критична, так как изоляция узла может привести к получению фейкового chain state
- DoS-атаки могут нарушить синхронизацию и доступность сети
- Memory exhaustion может привести к отказу в обслуживании
- Неправильная обработка сообщений может привести к некорректному состоянию узла
---
## 2. Изученные файлы
| Файл | LOC | Ключевые компоненты |
|------|-----|---------------------|
| `types.rs` | ~400 | Константы сети, ограничения памяти, типы сообщений, state machines |
| `protocol.rs` | ~1500 | Основной протокол P2P, обработка сообщений, handshake, connection loops |
| `addrman.rs` | ~600 | Менеджер адресов с криптографическими бакетами, защита от отравления |
| `connection.rs` | ~500 | Управление подключениями, ban list, retry logic, netgroup limits |
| `inventory.rs` | ~600 | Управление инвентарем (slices, tx, presence), LRU eviction, relay cache |
| `peer.rs` | ~400 | Состояние пира, rate limits, flow control, known inventory tracking |
| `message.rs` | ~300 | Определение всех типов P2P сообщений, размеры, сериализация |
| `rate_limit.rs` | ~400 | Token bucket rate limiting, flow control, adaptive subnet limiting |
| `bootstrap.rs` | ~400 | Верификация bootstrap, Trusted Core модель, консенсус проверок |
| `eviction.rs` | ~300 | Логика вытеснения пиров, многоуровневая защита |
| `sync.rs` | ~700 | Headers-first sync, параллельная загрузка слайсов, orphan pool |
| `startup.rs` | ~200 | Оркестрация полной верификации при старте |
| `subnet.rs` | ~300 | Отслеживание репутации подсетей, защита от Eclipse |
| `verification.rs` | ~700 | Клиент для верификации, аутентификация hardcoded нод |
| `hardcoded_identity.rs` | ~200 | Определение hardcoded нод, ML-DSA-65 аутентификация |
| `discouraged.rs` | ~300 | Rolling bloom filter для discouraged пиров |
| `feeler.rs` | ~250 | Короткоживущие подключения для проверки адресов |
| `dns.rs` | ~270 | DNS seeds и fallback IPs для bootstrap |
| `noise.rs` | ~900 | Noise XX + ML-KEM-768 гибридное шифрование |
| `encrypted.rs` | ~435 | Обертка для зашифрованных потоков, управление ключами |
| `mod.rs` | ~75 | Объявления модулей и re-exports |
**Всего:** ~20 файлов, ~9000+ строк кода
---
## 3. Attack Surface
### Точки входа для атакующего:
1. **P2P подключения**: Атакующий может инициировать входящие и исходящие TCP подключения, выполнять Noise handshake и отправлять P2P сообщения.
2. **Bootstrap процесс**: При старте узла атакующий может контролировать DNS seeds, fallback IPs (через BGP hijacking), или предоставлять фейковый chain state через P2P пиры.
3. **Сообщения протокола**: Атакующий может отправлять любые типы сообщений (Version, Inv, Addr, GetData, Slice, Tx, Presence и т.д.) в пределах rate limits.
4. **Address management**: Атакующий может отправлять адреса через `Addr` сообщения для отравления адресной таблицы.
5. **Inventory flooding**: Атакующий может отправлять множество уникальных inventory items для исчерпания памяти.
6. **Handshake exhaustion**: Атакующий может инициировать множество handshake без их завершения для исчерпания ресурсов.
---
## 4. Найденные уязвимости
### [HIGH] Memory Exhaustion через неограниченный `have_slice` HashSet
**Файл:** `inventory.rs:118`
**Уязвимый код:**
```rust
pub struct Inventory {
// Items we have locally
// Slices use HashSet (bounded by chain length, not attacker-controlled)
have_slice: HashSet<Hash>,
// Tx and Presence use LruHashSet (attacker can flood with unique items)
have_tx: LruHashSet,
have_presence: LruHashSet,
// ...
}
```
**Вектор атаки:**
1. Атакующий подключается к узлу и отправляет множество `Inv` сообщений с фейковыми slice hashes
2. Узел обрабатывает эти `Inv` и добавляет каждый hash в `have_slice` через `add_have()` или `add_slice()`
3. Так как `have_slice` использует обычный `HashSet` без ограничений размера, память растет неограниченно
4. Комментарий предполагает, что slice hashes ограничены длиной цепи, но атакующий может отправлять фейковые hashes, которые не соответствуют реальным слайсам
**Импакт:**
- Memory exhaustion узла
- Возможный отказ в обслуживании (DoS)
- Узел может быть вынужден перезапуститься или упасть
**Сложность:**
- Низкая: требуется только одно подключение и отправка множества `Inv` сообщений
- Ресурсы: минимальные (один бот или скрипт)
**PoC сценарий:**
```python
# Псевдокод атаки
1. Подключиться к целевому узлу Montana
2. Выполнить Noise handshake
3. Отправить Version/Verack
4. Для i в range(1, 1000000):
5. Сгенерировать случайный hash h_i
6. Отправить Inv([InvItem(InvType::Slice, h_i)])
7. Узел добавит h_i в have_slice HashSet
8. Память узла исчерпается
```
**Рекомендация:** Использовать `LruHashSet` для `have_slice` с ограничением размера (например, `MAX_SLICE_HAVE_ENTRIES = 1_000_000` для учета реальной длины цепи с запасом).
---
### [MEDIUM] Resource Exhaustion через неограниченные handshake подключения
**Файл:** `protocol.rs:589-613`, `connection.rs:186`
**Уязвимый код:**
```rust
// protocol.rs:589
tokio::spawn(async move {
if let Err(e) = Self::handle_connection(
stream,
socket_addr,
false,
// ...
)
.await
{
// ...
}
});
// connection.rs:186
connecting: Mutex<HashSet<SocketAddr>>,
```
**Вектор атаки:**
1. Атакующий инициирует множество TCP подключений к узлу одновременно (используя разные IP адреса)
2. Каждое подключение добавляется в `connecting` HashSet и создает новую `tokio::spawn` задачу
3. Атакующий начинает Noise handshake, но не завершает его (например, отправляет только часть сообщений или медленно отвечает)
4. Каждое незавершенное handshake потребляет память (задача, буферы, криптографические операции) и CPU
5. `connecting` HashSet не имеет явного ограничения размера, только `MAX_CONNECTIONS_PER_IP` ограничивает одно IP
**Импакт:**
- Исчерпание памяти через множество незавершенных handshake
- Исчерпание CPU через криптографические операции (ML-KEM-768, X25519)
- Возможный отказ в обслуживании
**Сложность:**
- Средняя: требуется множество IP адресов (например, ботнет или распределенная атака)
- Ресурсы: ботнет с ~1000+ узлов или распределенная атака
**PoC сценарий:**
```python
# Псевдокод атаки
1. Получить доступ к ботнету с 1000+ IP адресов
2. Для каждого IP:
3. Инициировать TCP подключение к целевому узлу
4. Начать Noise handshake (отправить Message 0)
5. НЕ отвечать на Message 1 от узла (или отвечать очень медленно)
6. Поддержать подключение открытым до таймаута (30 секунд)
7. Повторить для всех IP
8. Узел будет иметь 1000+ незавершенных handshake, потребляющих ресурсы
```
**Рекомендация:**
1. Добавить глобальное ограничение на количество одновременных подключений в процессе handshake (например, `MAX_CONCURRENT_HANDSHAKES = 100`)
2. Добавить ограничение размера `connecting` HashSet
3. Более агрессивные таймауты для handshake
---
### [MEDIUM] CPU Exhaustion через дубликаты в Inv сообщениях
**Файл:** `protocol.rs:1001-1022`
**Уязвимый код:**
```rust
Message::Inv(items) => {
if items.len() > MAX_INV_SIZE {
return Err(NetError::Protocol("Too many inv items".into()));
}
if !peer.rate_limits.inv.try_consume(items.len()) {
debug!("Rate limited inv from {} ({} items)", peer.addr, items.len());
return Ok(false);
}
for item in &items {
peer.add_known_inv(item.hash);
}
let needed = inventory.read().await.filter_needed(&items);
if !needed.is_empty() {
for item in &needed {
inventory.write().await.request(item, peer.addr);
}
peer_tx.send(Message::GetData(needed)).await.ok();
}
}
```
**Вектор атаки:**
1. Атакующий отправляет `Inv` сообщение с `MAX_INV_SIZE` (50,000) дубликатов одного и того же hash
2. Код не дедуплицирует элементы внутри одного сообщения перед обработкой
3. Каждый элемент обрабатывается отдельно:
- `peer.add_known_inv(item.hash)` вызывается 50,000 раз для одного hash
- `inventory.read().await.filter_needed(&items)` обрабатывает все 50,000 элементов
- Хотя `BoundedInvSet` в `peer.add_known_inv` использует HashSet и не добавит дубликаты, проверка выполняется 50,000 раз
4. Это приводит к избыточному использованию CPU
**Импакт:**
- CPU exhaustion через избыточную обработку дубликатов
- Возможное замедление обработки легитимных сообщений
- DoS через ресурсное исчерпание
**Сложность:**
- Низкая: требуется только одно подключение
- Ресурсы: минимальные (один скрипт)
**PoC сценарий:**
```python
# Псевдокод атаки
1. Подключиться к целевому узлу
2. Выполнить handshake
3. Сгенерировать один валидный hash h
4. Создать Inv сообщение с 50,000 копий InvItem(InvType::Slice, h)
5. Отправить это сообщение
6. Повторить шаги 3-5 с разными hashes
7. Узел будет обрабатывать миллионы дубликатов, потребляя CPU
```
**Рекомендация:** Дедуплицировать элементы внутри `Inv` сообщения перед обработкой:
```rust
let unique_items: Vec<InvItem> = items.iter()
.unique_by(|item| item.hash)
.cloned()
.collect();
```
---
### [LOW] Потенциальная утечка памяти в `ip_votes` при частых переподключениях
**Файл:** `protocol.rs:897`, `protocol.rs:834-839`
**Уязвимый код:**
```rust
// При получении Version от outbound пира
ip_votes.write().await.insert(peer.addr, their_view_of_us);
// При отключении пира
peers.write().await.remove(&addr);
let _ = event_tx.send(NetEvent::PeerDisconnected(addr)).await;
if let Some(nonce) = our_sent_nonce {
sent_nonces.write().await.remove(&nonce);
}
// НО: ip_votes НЕ очищается при отключении пира
```
**Вектор атаки:**
1. Атакующий создает множество outbound подключений к узлу
2. Каждое подключение отправляет Version с "голосом" за внешний IP, добавляя запись в `ip_votes`
3. Атакующий быстро отключается и переподключается с новыми адресами
4. Старые записи в `ip_votes` не удаляются при отключении
5. При частых переподключениях `ip_votes` может расти неограниченно
**Импакт:**
- Потенциальная утечка памяти (хотя комментарий говорит, что ограничено `MAX_OUTBOUND`)
- Если пиры часто переподключаются, старые записи накапливаются
**Сложность:**
- Средняя: требуется множество подключений и частые переподключения
- Ресурсы: ботнет или распределенная атака
**PoC сценарий:**
```python
# Псевдокод атаки
1. Получить доступ к множеству IP адресов
2. Для каждого IP:
3. Подключиться к целевому узлу (outbound)
4. Отправить Version с "голосом" за фейковый IP
5. Немедленно отключиться
6. Повторить с новым IP
7. После 10,000 переподключений ip_votes будет содержать 10,000 записей
```
**Рекомендация:** Очищать `ip_votes` при отключении outbound пира:
```rust
if !peer.inbound {
ip_votes.write().await.remove(&addr);
}
```
---
### [LOW] Потенциальная атака через false positives в DiscouragedFilter
**Файл:** `discouraged.rs:13-105`
**Уязвимый код:**
```rust
pub struct DiscouragedFilter {
data: Vec<u64>,
n_hash: u32,
n_elements: u32,
max_elements: u32,
generation: u32,
tweak: u64,
}
pub fn contains(&self, addr: &SocketAddr) -> bool {
// Bloom filter может иметь false positives
// ...
}
```
**Вектор атаки:**
1. Атакующий изучает структуру bloom filter (хотя `tweak` случайный)
2. Атакующий создает множество адресов, которые могут вызвать false positives с легитимными адресами
3. Легитимные пиры случайно попадают в discouraged filter
4. Легитимные пиры деприоритизируются, что может помочь атакующему вытеснить их
**Импакт:**
- Деприоритизация легитимных пиров
- Помощь в Eclipse атаке (вытеснение хороших пиров)
**Сложность:**
- Высокая: требуется глубокое понимание структуры bloom filter и криптографических хешей
- Ресурсы: значительные вычислительные ресурсы для анализа
**PoC сценарий:**
```python
# Псевдокод атаки (теоретический)
1. Изучить структуру DiscouragedFilter (SipHasher24, tweak)
2. Найти адреса, которые вызывают коллизии хешей с легитимными адресами
3. Добавить эти адреса в discouraged filter (через misbehavior)
4. Легитимные адреса могут попасть в false positive
5. Легитимные пиры деприоритизируются
```
**Рекомендация:** False positives в bloom filter приемлемы для "мягкого" наказания, но стоит мониторить частоту false positives и при необходимости увеличить размер фильтра или уменьшить false positive rate.
---
## 5. Атаки, которые НЕ работают
### Классические блокчейн атаки:
1. **51% атака**: Не применима, так как нет майнинга или стейкинга. Безопасность обеспечивается криптографически и физическими ограничениями времени.
2. **Selfish mining**: Не применима, так как нет майнеров, которые могут удерживать блоки. Слайсы создаются детерминистически на основе хешей.
3. **Long-range атака**: Не применима, так как нельзя создать валидные координаты для прошлого времени (накопление присутствия требует реального времени).
4. **Nothing-at-stake**: Не применима, так как нет стейкинга и нет экономических стимулов для создания множества форков.
5. **Eclipse через адресное отравление**: Частично защищено криптографическими бакетами в AddrMan, hardcoded нодами, и netgroup diversity. Однако, если атакующий контролирует DNS/BGP, он может отравить bootstrap процесс.
6. **Sybil атака на консенсус**: Не применима в традиционном смысле, так как консенсус не зависит от количества нод. Однако Sybil может влиять на распределение наград и сетевую топологию.
### Защитные механизмы, которые работают хорошо:
1. **Rate limiting**: Token bucket для различных типов сообщений эффективно ограничивает DoS через сообщения.
2. **Flow control**: Управление очередями предотвращает переполнение буферов.
3. **Bounded collections**: Большинство коллекций имеют явные ограничения размера (LruHashSet, MAX_RELAY_CACHE_ENTRIES, и т.д.).
4. **Netgroup diversity**: Ограничение подключений из одной /16 подсети защищает от Erebus-стиль атак.
5. **Hardcoded nodes**: ML-DSA-65 аутентификация hardcoded нод обеспечивает надежный bootstrap.
6. **Adaptive subnet limiting**: Двухуровневое адаптивное лимитирование подсетей защищает от распределенных атак.
---
## 6. Рекомендации
### Критические исправления:
1. **Исправить `have_slice` HashSet** (`inventory.rs:118`):
- Заменить на `LruHashSet` с ограничением размера
- Рекомендуемый размер: `MAX_SLICE_HAVE_ENTRIES = 1_000_000` (для учета реальной длины цепи с запасом)
2. **Ограничить одновременные handshake** (`protocol.rs:589`, `connection.rs:186`):
- Добавить глобальное ограничение `MAX_CONCURRENT_HANDSHAKES = 100`
- Добавить ограничение размера `connecting` HashSet
- Более агрессивные таймауты для handshake
3. **Дедуплицировать Inv сообщения** (`protocol.rs:1001-1022`):
- Дедуплицировать элементы внутри `Inv` сообщения перед обработкой
- Использовать `HashSet` или `BTreeSet` для уникальности
### Улучшения:
4. **Очищать `ip_votes` при отключении** (`protocol.rs:834-839`):
- Добавить очистку `ip_votes` при отключении outbound пира
5. **Мониторинг false positives** (`discouraged.rs`):
- Добавить метрики для отслеживания частоты false positives в DiscouragedFilter
- При необходимости увеличить размер фильтра или уменьшить false positive rate
6. **Валидация содержимого сообщений**:
- Добавить более строгую валидацию содержимого сообщений (например, проверка типов, диапазонов значений)
- Добавить метрики для отслеживания аномальных паттернов в сообщениях
---
## 7. Вердикт
- [ ] CRITICAL — есть уязвимости, позволяющие уничтожить сеть
- [x] HIGH — есть серьёзные уязвимости
- [ ] MEDIUM — есть уязвимости среднего риска
- [ ] LOW — только minor issues
- [ ] SECURE — уязвимостей не найдено
**Обоснование:**
Найдены **4 уязвимости** различной степени серьезности:
1. **HIGH**: Memory exhaustion через неограниченный `have_slice` HashSet — может привести к DoS узла
2. **MEDIUM**: Resource exhaustion через неограниченные handshake — требует больше ресурсов, но все еще серьезная
3. **MEDIUM**: CPU exhaustion через дубликаты в Inv — может замедлить обработку сообщений
4. **LOW**: Потенциальная утечка памяти в `ip_votes` — менее критична, но стоит исправить
Сетевой слой Montana в целом хорошо защищен с множеством защитных механизмов (rate limiting, bounded collections, netgroup diversity, hardcoded nodes). Однако найденные уязвимости показывают, что есть места для улучшения, особенно в области ограничения памяти и обработки дубликатов.
**Общая оценка:** Сетевой слой имеет хорошую архитектуру безопасности, но требует исправления найденных уязвимостей перед production deployment.