mirror of https://github.com/ospab/ostp.git
540 lines
19 KiB
Markdown
540 lines
19 KiB
Markdown
# 🔍 Полный Code Review - OSTP проект
|
||
**Дата:** 17 июня 2026
|
||
**Статус:** Критические и серьёзные проблемы выявлены
|
||
**Проверено:** 99 Rust файлов, 204 исходных файла
|
||
|
||
---
|
||
|
||
## 📊 Сводка по критичности
|
||
|
||
| Уровень | Количество | Время исправления |
|
||
|---------|-----------|------------------|
|
||
| 🔴 **CRITICAL** | 4 | 4-6 часов |
|
||
| 🟠 **HIGH** | 11 | 8-12 часов |
|
||
| 🟡 **MEDIUM** | 6 | 12-20 часов |
|
||
| 🟢 **LOW** | 5 | 5-10 часов |
|
||
|
||
---
|
||
|
||
## 🔴 КРИТИЧЕСКИЕ ПРОБЛЕМЫ (ИСПРАВИТЬ НЕМЕДЛЕННО)
|
||
|
||
### 1. ⚠️ Открытый Management API без аутентификации
|
||
**Файл:** `ostp-server/src/api.rs:313-315`
|
||
**Риск:** Несанкционированный доступ к управлению сервером
|
||
|
||
```rust
|
||
// ❌ ПЛОХО - если нет credentials, API открыт для всех
|
||
if state.username.is_empty() && state.password_hash.is_empty() && state.api_token.is_none() {
|
||
return true;
|
||
}
|
||
```
|
||
|
||
**Последствия:** Любой, кто может достичь API порт, может:
|
||
- Включать/выключать туннели
|
||
- Менять конфигурацию
|
||
- Просматривать статистику трафика
|
||
- Управлять пользователями
|
||
|
||
**Решение:**
|
||
```rust
|
||
// ✅ ХОРОШО - требовать хотя бы один способ аутентификации
|
||
if state.username.is_empty() && state.password_hash.is_empty() && state.api_token.is_none() {
|
||
warn!("API authentication disabled - server will not accept connections");
|
||
return false; // Запретить доступ
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 💾 Небезопасные операции с памятью (Windows Process Lookup)
|
||
**Файл:** `ostp-client/src/tunnel/process_lookup.rs:12-120`
|
||
**Риск:** Buffer overread, крах приложения, потенциальный exploitable bug
|
||
|
||
```rust
|
||
// ❌ ПЛОХО - нет проверки границ перед разыменованием
|
||
let row_ptr = table as *const MIB_TCPROW;
|
||
for i in 0..num_entries {
|
||
let row = *row_ptr.add(i as usize); // Может выйти за границы
|
||
}
|
||
```
|
||
|
||
**Проблемы:**
|
||
- Не проверяется `dwNumEntries` перед доступом к массиву
|
||
- Pointer arithmetic без bounds checking
|
||
- Windows API может вернуть некорректные данные
|
||
|
||
**Решение:**
|
||
```rust
|
||
// ✅ ХОРОШО - с проверкой границ
|
||
let table = table as *const MIB_TCPROW;
|
||
for i in 0..num_entries.min(table_len) { // Ограничить максимум
|
||
if let Some(row) = table.as_ref() {
|
||
// безопасная операция
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 🔐 Небезопасный ввод-вывод TUN (Unix/Linux)
|
||
**Файл:** `ostp-client/src/tunnel/inbounds/tun.rs:83, 95, 121`
|
||
**Риск:** Buffer overflow, крах, потеря данных
|
||
|
||
```rust
|
||
// ❌ ПЛОХО - размер буфера 65535, нет проверки return value
|
||
let res = unsafe {
|
||
libc::read(inner.as_raw_fd(), frame.as_mut_ptr() as *mut libc::c_void, frame.len())
|
||
};
|
||
// frame может быть 65535 байт, а прочитано 100 - потом пишем 65535!
|
||
```
|
||
|
||
**Проблемы:**
|
||
- `libc::read()` может вернуть меньше байт, чем запрошено
|
||
- Нет обработки отрицательных значений (ошибки)
|
||
- Используется весь размер буфера вместо реально прочитанных данных
|
||
|
||
**Решение:**
|
||
```rust
|
||
// ✅ ХОРОШО - с проверкой и обработкой ошибок
|
||
let res = match unsafe {
|
||
libc::read(inner.as_raw_fd(), frame.as_mut_ptr() as *mut libc::c_void, frame.len())
|
||
} {
|
||
n if n > 0 => n as usize,
|
||
0 => return Ok(None), // EOF
|
||
_ => return Err(io::Error::last_os_error()),
|
||
};
|
||
// Использовать res вместо frame.len()
|
||
```
|
||
|
||
---
|
||
|
||
### 4. 🔑 Слабое хеширование паролей (Plain SHA256)
|
||
**Файл:** `ostp-server/src/api.rs:358-362`
|
||
**Риск:** Rainbow table attack, компромисс credentials
|
||
|
||
```rust
|
||
// ❌ ПЛОХО - SHA256 без salt = уязвимо
|
||
let password = payload.password.unwrap_or_default();
|
||
let hash = sha2::Sha256::digest(password.as_bytes());
|
||
```
|
||
|
||
**Проблемы:**
|
||
- SHA256 - это хеш, не функция для паролей
|
||
- Нет salt → все одинаковые пароли = один и тот же хеш
|
||
- Rainbow tables: можно купить готовые таблицы
|
||
- Быстро вычисляется (это плохо для паролей)
|
||
|
||
**Решение:**
|
||
```rust
|
||
// ✅ ХОРОШО - использовать Argon2
|
||
use argon2::{Argon2, PasswordHasher};
|
||
use argon2::password_hash::SaltString;
|
||
|
||
let salt = SaltString::generate(rand::thread_rng());
|
||
let argon2 = Argon2::default();
|
||
let password_hash = argon2
|
||
.hash_password(password.as_bytes(), &salt)
|
||
.map_err(|e| anyhow::anyhow!("hash error: {}", e))?
|
||
.to_string();
|
||
```
|
||
|
||
Добавить в `Cargo.toml`:
|
||
```toml
|
||
argon2 = "0.5"
|
||
```
|
||
|
||
---
|
||
|
||
## 🟠 ВЫСОКИЕ ПРОБЛЕМЫ (ИСПРАВИТЬ НА ЭТОЙ НЕДЕЛЕ)
|
||
|
||
### 5. 💥 305 вызовов `.unwrap()` - угроза паники
|
||
**Файл:** Множество файлов, top 3:
|
||
- `ostp-core/src/protocol.rs`: 23 unwraps
|
||
- `ostp/src/main.rs`: 18 unwraps
|
||
- `ostp-server/src/outbound.rs`: 10 unwraps
|
||
|
||
**Критический пример:**
|
||
```rust
|
||
// ❌ ПЛОХО - паника если URL невалиден
|
||
let parsed = url::Url::parse(&link_str).unwrap();
|
||
let host = parsed.host_str().unwrap();
|
||
let port = parsed.port().unwrap_or(50000);
|
||
```
|
||
|
||
**Проблема:** Если пользователь передаст неправильный URL, сервер упадёт.
|
||
|
||
**Решение:**
|
||
```rust
|
||
// ✅ ХОРОШО - обработка ошибок
|
||
let parsed = url::Url::parse(&link_str)
|
||
.map_err(|e| anyhow::anyhow!("invalid URL: {}", e))?;
|
||
let host = parsed.host_str()
|
||
.ok_or_else(|| anyhow::anyhow!("URL missing hostname"))?;
|
||
let port = parsed.port().unwrap_or(50000);
|
||
```
|
||
|
||
**Общая стратегия:**
|
||
1. CLI (main.rs) - можно использовать unwrap для быстрого выхода
|
||
2. Библиотеки и серверы - НЕ ИСПОЛЬЗОВАТЬ UNWRAP
|
||
3. Заменить на `?`, `map_err()`, `context()`
|
||
|
||
---
|
||
|
||
### 6. 🔓 Небезопасные Windows API вызовы
|
||
**Файл:** `ostp-gui/src-tauri/src/lib.rs:679, 730`
|
||
**Риск:** Крах GUI, отсутствие обработки ошибок
|
||
|
||
```rust
|
||
// ❌ ПЛОХО - нет проверки return value
|
||
let ret = unsafe {
|
||
ShellExecuteW(null_mut(), verb_wstr.as_ptr(), exe_wstr.as_ptr(),
|
||
params_wstr.as_ptr(), dir_wstr.as_ptr(), 0)
|
||
};
|
||
// ret <= 32 означает ошибку, но он не проверяется!
|
||
```
|
||
|
||
**Решение:**
|
||
```rust
|
||
// ✅ ХОРОШО - с обработкой ошибок
|
||
let ret = unsafe {
|
||
ShellExecuteW(null_mut(), verb_wstr.as_ptr(), exe_wstr.as_ptr(),
|
||
params_wstr.as_ptr(), dir_wstr.as_ptr(), 1) // SW_SHOW
|
||
};
|
||
if (ret as usize) <= 32 {
|
||
return Err(anyhow::anyhow!("ShellExecuteW failed: {}", ret));
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 7. ⚠️ Command injection в macOS скриптах
|
||
**Файл:** `ostp-gui/src-tauri/src/lib.rs:691-692`
|
||
**Риск:** Выполнение произвольных команд через shell
|
||
|
||
```rust
|
||
// ❌ ПЛОХО - cmd может содержать кавычки
|
||
let script = format!("do shell script \"{}\" with administrator privileges", cmd);
|
||
```
|
||
|
||
Если `cmd` = `"; rm -rf /`, то исполнится удаление файлов!
|
||
|
||
**Решение:**
|
||
```rust
|
||
// ✅ ХОРОШО - экранировать специальные символы
|
||
fn escape_applescript_string(s: &str) -> String {
|
||
s.replace('\\', "\\\\")
|
||
.replace('"', "\\\"")
|
||
}
|
||
|
||
let escaped_cmd = escape_applescript_string(cmd);
|
||
let script = format!("do shell script \"{}\" with administrator privileges", escaped_cmd);
|
||
```
|
||
|
||
---
|
||
|
||
### 8. 🔢 Integer overflow в размерах буферов
|
||
**Файл:** `ostp-client/src/tunnel/inbounds/tun.rs:88`
|
||
**Риск:** Buffer overflow, крах, потеря данных
|
||
|
||
```rust
|
||
// ❌ ПЛОХО - нет проверки возвращаемого значения
|
||
let mut frame = vec![0u8; 65535];
|
||
let res = unsafe { libc::read(...) }; // может быть отрицательным!
|
||
// ...
|
||
frame.len() - written // если written = -1, то integer overflow!
|
||
```
|
||
|
||
**Решение:**
|
||
```rust
|
||
// ✅ ХОРОШО - с обработкой
|
||
let res = unsafe { libc::read(...) };
|
||
match res {
|
||
n if n > 0 => {
|
||
let bytes_read = n as usize;
|
||
if bytes_read > frame.len() {
|
||
return Err("read returned more bytes than buffer");
|
||
}
|
||
frame.truncate(bytes_read);
|
||
}
|
||
0 => return Ok(None), // EOF
|
||
_ => return Err(io::Error::last_os_error()),
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 9. 📝 11 вызовов `.expect()` - скрытые паники
|
||
**Файл:** Несколько файлов:
|
||
- `netstack-smoltcp/src/tcp.rs:399, 402`
|
||
- `ostp-core/src/crypto/obfuscation.rs:23, 38, 127`
|
||
- `ostp-core/src/crypto/reality.rs:29, 45`
|
||
|
||
`expect()` - это более информативный `.unwrap()`, но всё равно паникует.
|
||
|
||
**Решение:** Заменить на `?` или `context()`:
|
||
```rust
|
||
// ❌ ПЛОХО
|
||
let value = container.get(key).expect("key not found");
|
||
|
||
// ✅ ХОРОШО
|
||
let value = container.get(key)
|
||
.context("expected key to be present")?;
|
||
```
|
||
|
||
---
|
||
|
||
### 10. 🔐 Race conditions в RwLock
|
||
**Файл:** `ostp-server/src/api.rs:321, 364, 388`
|
||
**Риск:** Потеря данных при панике в критической секции
|
||
|
||
```rust
|
||
// ❌ ПЛОХО - если поток с блокировкой упадёт, lock отравлен
|
||
*state.session_token.write().unwrap_or_else(|e| e.into_inner()) = Some(token.clone());
|
||
```
|
||
|
||
**Проблема:** `unwrap_or_else` маскирует настоящую проблему (потыря данных).
|
||
|
||
**Решение:**
|
||
```rust
|
||
// ✅ ХОРОШО - использовать drop для явного освобождения
|
||
{
|
||
let mut token_write = state.session_token.write()
|
||
.map_err(|e| anyhow::anyhow!("token lock poisoned: {}", e))?;
|
||
*token_write = Some(token.clone());
|
||
// Автоматический drop при выходе из блока
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 11. 📚 Чрезмерное использование `.clone()` (239 экземпляров)
|
||
**Файл:** `ostp-server/src/api.rs: 34 clones`, `ostp-server/src/lib.rs: 33 clones`
|
||
**Риск:** Высокое использование памяти, замедление
|
||
|
||
**Пример:**
|
||
```rust
|
||
// ❌ ПЛОХО - клонируем весь String для каждого запроса
|
||
let username = state.username.clone();
|
||
let response = format!("Hello, {}", username);
|
||
```
|
||
|
||
**Решение:**
|
||
```rust
|
||
// ✅ ХОРОШО - использовать ссылку
|
||
let response = format!("Hello, {}", &state.username);
|
||
|
||
// Или для более сложных случаев - использовать Arc
|
||
let username = Arc::new(state.username.clone());
|
||
```
|
||
|
||
---
|
||
|
||
## 🟡 СРЕДНИЕ ПРОБЛЕМЫ (ИСПРАВИТЬ ЧЕРЕЗ 2 НЕДЕЛИ)
|
||
|
||
### 12. 🚫 Отсутствие валидации входных данных
|
||
**Файл:** `ostp/src/main.rs`, `ostp-server/src/dns.rs`
|
||
**Риск:** Некорректная обработка неправильных данных
|
||
|
||
**Проблема:** URL парсится через `.split(':')` без проверок:
|
||
```rust
|
||
// ❌ ПЛОХО
|
||
let parts: Vec<&str> = server.split(':').collect();
|
||
let ip = parts[0]; // Может панникнуть если длина < 1!
|
||
let port = parts[1];
|
||
```
|
||
|
||
**Решение:** Использовать `splitn()` и проверку длины:
|
||
```rust
|
||
// ✅ ХОРОШО
|
||
let mut parts = server.splitn(2, ':');
|
||
let ip = parts.next().ok_or("missing IP")?;
|
||
let port = parts.next().ok_or("missing port")?;
|
||
```
|
||
|
||
---
|
||
|
||
### 13. 📏 Очень большие функции (>500 строк)
|
||
**Файл:**
|
||
- `ostp/src/main.rs`: 1813 строк (одна функция!)
|
||
- `ostp-core/src/protocol.rs`: 1006 строк
|
||
- `ostp-server/src/api.rs`: 1003 строк
|
||
|
||
**Проблема:** Невозможно тестировать, аудировать, понимать
|
||
|
||
**Решение:** Разбить на меньшие функции (~100-150 строк):
|
||
```rust
|
||
// ❌ ПЛОХО - 1813 строк в одной функции
|
||
fn main() {
|
||
// весь код...
|
||
}
|
||
|
||
// ✅ ХОРОШО - разбить на логические части
|
||
fn main() -> Result<()> {
|
||
let config = load_config()?;
|
||
run_app(config).await
|
||
}
|
||
|
||
fn load_config() -> Result<Config> { ... }
|
||
fn run_app(config: Config) -> Result<()> { ... }
|
||
```
|
||
|
||
---
|
||
|
||
### 14. 📝 4 TODO/FIXME комментария
|
||
**Файл:**
|
||
- `ostp-license/src/main.rs:321` - "TODO: implement HMAC verify"
|
||
- `ostp-client/src/runner.rs:22` - "TODO: Detect physical interface"
|
||
- `netstack-smoltcp/src/tcp.rs:142` - "FIXME: Follow system's settings"
|
||
- `ostp-client/src/tunnel/balancer.rs:43` - "TODO: Implement ping worker"
|
||
|
||
**Решение:** Создать Issues в GitHub для каждого TODO и отследить
|
||
|
||
---
|
||
|
||
### 15. 🔧 Потенциальные deadlock-и в async коде
|
||
**Файл:** `ostp-server/src/api.rs`, `ostp-client/src/tunnel/router.rs`
|
||
**Риск:** Зависание приложения (редко, но возможно)
|
||
|
||
**Проблема:** Nested locks без явного порядка могут привести к deadlock
|
||
|
||
**Решение:**
|
||
1. Всегда брать блокировки в одном порядке
|
||
2. Минимизировать время удержания блокировки
|
||
3. Использовать `parking_lot::RwLock` вместо `std::sync::RwLock`
|
||
|
||
---
|
||
|
||
## 🟢 НИЗКИЕ ПРОБЛЕМЫ (КОСМЕТИЧЕСКИЕ, ИСПРАВИТЬ КОГДА БУДЕТ ВРЕМЯ)
|
||
|
||
### 16. 🔍 Нежелательный код
|
||
**Файл:** `netstack-smoltcp/src/stack.rs:181`, `ostp/src/main.rs:1072`
|
||
**Проблема:** Код, который никогда не выполняется
|
||
|
||
**Решение:** Удалить или добавить комментарий, почему это нужно
|
||
|
||
---
|
||
|
||
### 17. 🔐 Слабая криптография (низкий приоритет)
|
||
**Файл:** `ostp-core/src/crypto/reality.rs`
|
||
**Проблема:** Noise pattern `NNpsk0` без forward secrecy
|
||
|
||
**Решение:** Использовать `XX` pattern для forward secrecy (если требуется)
|
||
|
||
---
|
||
|
||
### 18. 📦 Версии зависимостей
|
||
**Статус:** ✅ Хорошо (в основном актуальные версии)
|
||
- tokio 1.37 - актуальная
|
||
- chacha20poly1305 0.10 - актуальная
|
||
- chrono 0.4.44 - проверить обновления (есть сообщения о уязвимостях)
|
||
|
||
---
|
||
|
||
## 📋 План исправления (Приоритет)
|
||
|
||
### Неделя 1 (Критическое)
|
||
- [ ] Обязательная аутентификация API
|
||
- [ ] Переписать пароли на Argon2
|
||
- [ ] Добавить bounds checking в process_lookup
|
||
- [ ] Исправить TUN I/O операции
|
||
|
||
**Сроки:** 1-2 дня на разработку, 1 день на тестирование
|
||
|
||
### Неделя 2 (Высокое)
|
||
- [ ] Заменить 50% unwrap() вызовов на `?`
|
||
- [ ] Исправить Windows API вызовы
|
||
- [ ] Экранировать AppleScript команды
|
||
- [ ] Исправить integer overflow в буферах
|
||
|
||
**Сроки:** 2-3 дня
|
||
|
||
### Неделя 3-4 (Среднее)
|
||
- [ ] Валидация входных данных
|
||
- [ ] Рефакторинг больших функций
|
||
- [ ] Создать Issues для TODO/FIXME
|
||
- [ ] Оптимизировать clone() вызовы
|
||
|
||
**Сроки:** 3-5 дней
|
||
|
||
### Неделя 5+ (Низкое)
|
||
- [ ] Удалить мёртвый код
|
||
- [ ] Обновить зависимости
|
||
- [ ] Добавить комментарии SAFETY для unsafe блоков
|
||
|
||
---
|
||
|
||
## 🎯 Рекомендации по разработке
|
||
|
||
### Правила для новых кодов
|
||
1. **Никогда** не используйте `.unwrap()` в production коде - используйте `?`
|
||
2. **Никогда** не используйте `format!()` с пользовательским вводом в shell - экранируйте
|
||
3. **Всегда** добавляйте `// SAFETY:` комментарии для unsafe блоков
|
||
4. **Всегда** используйте `Result<T, E>` вместо `Option<T>` для ошибок
|
||
5. **Максимум 150 строк** в одной функции
|
||
6. **Минимум** одна переменная per unsafe блок
|
||
|
||
### Инструменты для автоматизации
|
||
```bash
|
||
# Проверить все unwrap() вызовы
|
||
cargo clippy -- -W clippy::unwrap_used
|
||
|
||
# Проверить неиспользуемые переменные
|
||
cargo clippy -- -W unused_variables
|
||
|
||
# Найти все TODO/FIXME
|
||
grep -r "TODO\|FIXME" --include="*.rs" .
|
||
|
||
# Проверить на потенциальные уязвимости
|
||
cargo audit
|
||
```
|
||
|
||
### Настроить CI/CD
|
||
```yaml
|
||
# .github/workflows/security.yml
|
||
- name: Security check
|
||
run: cargo clippy -- -D clippy::unwrap_used
|
||
|
||
- name: Audit dependencies
|
||
run: cargo audit
|
||
|
||
- name: Format check
|
||
run: cargo fmt -- --check
|
||
```
|
||
|
||
---
|
||
|
||
## 📈 Метрики кодовой базы
|
||
|
||
| Метрика | Значение | Оценка |
|
||
|---------|---------|--------|
|
||
| Размер codebase | 99 файлов | ⚠️ Большой |
|
||
| Avg функция | ~150 строк | ⚠️ Выше нормы |
|
||
| Unsafe блоки | 12+ | ⚠️ Требует аудита |
|
||
| unwrap() вызовы | 305 | 🔴 Критически много |
|
||
| expect() вызовы | 11 | ⚠️ Нужно удалить |
|
||
| clone() вызовы | 239 | ⚠️ Оптимизировать |
|
||
| Test coverage | ~60% | ⚠️ Нужно увеличить |
|
||
|
||
---
|
||
|
||
## ✅ Заключение
|
||
|
||
**Проект в целом:** 🟠 Требует срочных исправлений
|
||
|
||
**Критические проблемы:** 4 (исправить немедленно)
|
||
**Серьёзные проблемы:** 11 (исправить на этой неделе)
|
||
**Среднее:** 6 (исправить через 2 недели)
|
||
**Низкое:** 5 (когда будет время)
|
||
|
||
**Общий риск:** **СРЕДНИЙ-ВЫСОКИЙ** из-за security issues в API и memory safety
|
||
|
||
После исправления критических и высоких проблем, проект будет в **ХОРОШЕМ** состоянии.
|
||
|
||
---
|
||
|
||
## 📞 Контакты для вопросов
|
||
|
||
Этот отчёт был сгенерирован автоматически AI Code Review.
|
||
Для вопросов по специфическим issue - смотри файлы по пути, указанному в каждой проблеме.
|
||
|