Курс — Хакинг на Rust. #6 Система владения (Ownership). Правила владения и заимствования
Здравствуйте, дорогие друзья.
Система владения (Ownership) — это фундамент Rust, который делает его уникальным. Она решает проблемы управления памятью на этапе компиляции, предотвращая уязвимости вроде use-after-free и data races. Для хакеров это означает:
- Инструменты, защищенные от классических эксплойтов.
- Возможность анализировать чужой код на предмет ошибок владения.
- Контроль над ресурсами, сравнимый с C, но без рисков.
2.1 Правила владения: три кита безопасности
- Каждое значение имеет владельца.
1 |
let data = vec![1, 2, 3]; // `data` — владелец вектора |
2. Одновременно может быть только один владелец.
1 2 3 |
let a = String::from("test"); let b = a; // Владение передано от `a` к `b` // println!("{}", a); // Ошибка: `a` больше не владеет данными |
3. Когда владелец покидает область видимости, значение уничтожается.
1 2 3 |
{ let s = String::from("локальная строка"); } // Память освобождается автоматически |
Зачем хакеру :
- В C/C++ аналогичный код мог бы привести к двойному освобождению памяти или утечке .
- В Rust компилятор предотвращает это, анализируя поток владения.
2.2 Заимствование: ссылки и их ограничения
Чтобы избежать передачи владения каждый раз, используются ссылки (&T
— неизменяемые, &mut T
— изменяемые).
Правила заимствования :
- Много неизменяемых ссылок или одна изменяемая.
1 2 3 4 |
let mut x = 5; let r1 = &x; // Ok let r2 = &x; // Ok let r3 = &mut x; // Ошибка: нельзя совмещать изменяемые и неизменяемые ссылки |
Ссылки не могут переживать данные.
1 2 3 4 5 |
let ref_to_nothing; { let s = String::from("short-lived"); ref_to_nothing = &s; // Ошибка: `s` уничтожится, а ссылка останется } |
Пример из хакинга:
Парсинг сетевого пакета:
1 2 3 4 5 6 7 8 |
fn parse_packet(packet: &[u8]) -> Option<u16> { // Работаем с данными, не забирая владение Some(packet[0] as u16) } let buffer = vec![0x12, 0x34]; let port = parse_packet(&buffer); // Передаем ссылку println!("Порт: {:?}", port); // `buffer` все еще доступен |
2.3 Владение и функции
Передача аргументов в функции перемещает или заимствует данные:
1 2 3 4 5 6 7 |
fn take_ownership(s: String) { /* s уничтожится здесь */ } fn main() { let s = String::from("Hello"); take_ownership(s); // println!("{}", s); // Ошибка: владение потеряно } |
Чтобы вернуть владение, используйте кортежи:
1 2 3 |
fn modify(s: String) -> (String, usize) { (s, s.len()) } |
Совет для хакеров :
Если вам нужно временно изменить данные, используйте &mut
, но помните:
1 2 3 |
let mut buffer = vec![0u8; 1024]; analyze(&buffer); // Неизменяемая ссылка encrypt(&mut buffer); // Изменяемая ссылка |
2.4 Срезы: безопасный доступ к данным
Срезы (&[T]
) позволяют ссылаться на часть коллекции без копирования:
1 2 3 4 5 6 |
fn get_subslice(data: &[u8], start: usize, end: usize) -> &[u8] { &data[start..end] } let payload = vec![0xde, 0xad, 0xbe, 0xef]; let part = get_subslice(&payload, 1, 3); // [0xad, 0xbe] |
Защита от переполнения :
Компилятор проверяет границы срезов, предотвращая выход за пределы массива.
2.5 Владение и структуры
Структуры могут владеть данными или заимствовать их:
1 2 3 4 |
struct Packet { data: Vec<u8>, // Владение metadata: &'static str, // Статическая ссылка (жизненный цикл 'static') } |
Важно для хакеров :
- Владение гарантирует, что данные структуры не будут удалены до ее разрушения.
- Используйте
Box
,Rc
илиArc
, если нужно разделять владение.
2.6 Как владение предотвращает уязвимости
- Use-after-free :
В C:
1 2 3 |
int *ptr = malloc(...); free(ptr); *ptr = 42; // Неопределенное поведение |
В Rust:
1 2 3 |
let ptr = Box::new(42); drop(ptr); // Явное удаление // *ptr = 0; // Ошибка компиляции |
Data races:
В многопоточном коде Rust запрещает:
- Две изменяемых ссылки на одни данные.
- Одновременный доступ на чтение и запись.
Пример:
1 2 3 4 5 6 7 8 |
use std::thread; let mut data = vec![1, 2, 3]; let handle = thread::spawn(move || { data.push(4); }); // data.push(5); // Ошибка: данные перемещены в поток handle.join().unwrap(); |
2.7 Владение в низкоуровневом коде: unsafe
Для работы с памятью напрямую используется unsafe
:
1 2 3 4 5 6 |
let mut buffer = [0u8; 10]; let ptr = buffer.as_mut_ptr(); unsafe { *ptr.add(5) = 0xff; // Запись в 5-й байт } |
Но даже в unsafe
:
- Нельзя нарушить правила владения (например, создать висячую ссылку).
- Компилятор проверяет базовые инварианты.
Совет : Используйте unsafe
только для коротких блоков, оберните их в безопасные абстракции.
2.8 Практическое задание: анализ уязвимости
Представьте код на C:
1 2 3 4 |
void process(char *data) { free(data); printf("%s", data); // Use-after-free } |
В Rust аналогичный код просто не скомпилируется:
1 2 3 4 |
fn process(data: String) { drop(data); println!("{}", data); // Ошибка: данные уже удалены } |
Итог
Система владения — это основа безопасности Rust. Она заставляет вас думать о времени жизни данных, ссылках и многопоточности, но взамен дает:
- Защиту от уязвимостей памяти.
- Контроль над ресурсами, сравнимый с C.
- Инструменты для анализа чужого кода.
В следующих главах мы увидим, как владение работает в связке с unsafe
, сетевыми операциями и эксплойтами. А пока — попробуйте написать функцию, которая принимает срез байтов и возвращает его подмножество, не нарушая правила владения.

На этом все. Всем хорошего дня!