Безопасность Rust приложений: топ-10 уязвимостей

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Мифы и реальность безопасности Rust

Язык Rust часто называют "безопасным" и "надёжным", и это во многом справедливо. Благодаря системе владения (ownership) и строгому компилятору, Rust устраняет целые классы уязвимостей, характерных для C и C++ (например, переполнение буфера или висячие указатели). Однако это не означает, что Rust-приложения неуязвимы. На практике разработчики сталкиваются с ошибками логики, неправильным использованием unsafe и уязвимостями на уровне архитектуры.

В этой статье мы разберём топ-10 наиболее частых уязвимостей в Rust-приложениях. Вы узнаете, как они возникают, как их обнаружить и как защитить свой код. Материал будет полезен как новичкам, так и опытным разработчикам.

1. Ошибки в unsafe коде

Самая известная "ахиллесова пята" Rust — блоки unsafe. Они позволяют выполнять операции, которые компилятор не может проверить: разыменование сырых указателей, вызов внешних функций (FFI), изменение статических переменных. Если в unsafe допущена ошибка, вся безопасность Rust рушится.

Пример уязвимости

fn unsafe_deref(ptr: *const i32) -> i32 {
    unsafe { *ptr } // Если ptr нулевой или указывает на освобождённую память — UB
}

Как защититься

  • Минимизируйте использование unsafe. Выносите его в небольшие, тщательно протестированные модули.
  • Используйте #[deny(unsafe_code)] в крейтах, где это возможно.
  • Проверяйте указатели на валидность перед разыменованием.

2. Гонки данных (Data Races) в многопоточном коде

Rust гарантирует отсутствие гонок данных в безопасном коде благодаря правилам владения и заимствования. Однако при использовании unsafe или неправильной работе с Mutex/RwLock гонки возможны. Особенно опасны ситуации с Cell/RefCell в многопоточной среде.

Пример уязвимости

use std::cell::RefCell;
use std::thread;

fn main() {
    let data = RefCell::new(0);
    thread::spawn(move || {
        *data.borrow_mut() += 1; // Паника: RefCell не Sync
    });
}

Как защититься

  • Используйте Arc<Mutex<T>> или Arc<RwLock<T>> для разделяемых данных.
  • Избегайте unsafe при работе с потоками без крайней необходимости.
  • Применяйте статический анализатор clippy и miri для обнаружения неопределённого поведения.

3. Утечки памяти (Memory Leaks)

Хотя Rust предотвращает утечки в большинстве случаев, они всё же возможны. Например, при создании циклических ссылок с Rc/RefCell или при забывании вызвать деструктор в unsafe коде. Утечки памяти в долгоживущих сервисах могут привести к исчерпанию ресурсов.

Пример уязвимости

use std::rc::Rc;
use std::cell::RefCell;

struct Node {
    next: Option<Rc<RefCell<Node>>>,
}

fn main() {
    let a = Rc::new(RefCell::new(Node { next: None }));
    let b = Rc::new(RefCell::new(Node { next: Some(a.clone()) }));
    a.borrow_mut().next = Some(b); // Циклическая ссылка — утечка
}

Как защититься

  • Используйте Weak для разрыва циклических ссылок.
  • Для долгоживущих приложений применяйте профилировщики памяти (например, valgrind или heaptrack).
  • В unsafe коде всегда проверяйте, что память освобождается корректно.

4. Неправильная обработка ошибок

Rust поощряет явную обработку ошибок через Result и Option. Однако разработчики часто игнорируют ошибки, используя .unwrap() или .expect(). В production это может привести к панике сервиса.

Пример уязвимости

fn read_file(path: &str) -> String {
    std::fs::read_to_string(path).unwrap() // Паника, если файла нет
}

Как защититься

  • Избегайте .unwrap() в production коде. Используйте match или ? оператор.
  • Определите стратегию обработки ошибок: логирование, возврат пользователю, повторные попытки.
  • Применяйте thiserror или anyhow для удобной работы с ошибками.

5. Уязвимости в зависимостях (Supply Chain)

Rust-экосистема (crates.io) насчитывает тысячи крейтов. Даже если ваш код безопасен, уязвимости в зависимостях могут скомпрометировать приложение. Особенно опасны крейты с unsafe или устаревшие версии.

Как защититься

  • Регулярно обновляйте зависимости: cargo update.
  • Используйте cargo audit для поиска известных уязвимостей.
  • Проверяйте код критических зависимостей вручную или через cargo geiger (анализ unsafe).
  • Внедрите политику минимальных привилегий: подключайте только нужные крейты.

6. Переполнение целых чисел (Integer Overflow)

В Rust переполнение целых чисел в debug-режиме вызывает панику, а в release-режиме выполняется по модулю (wrap). Это может привести к неожиданному поведению, особенно в финансовых или кри

Блоги

Рекомендаций по книгам