Часть VI. Безопасность системного программирования
6.1 Управление памятью и безопасность
6.1.1 Уязвимости, связанные с памятью
Одной из самых распространенных категорий уязвимостей в системном программировании являются проблемы, связанные с управлением памятью. Если программы неправильно управляют памятью, это может привести к утечкам памяти, перезаписи критических данных и даже к выполнению вредоносного кода. Основные виды уязвимостей, связанных с памятью:
- 
Переполнение буфера:
- Происходит, когда программа записывает больше данных, чем выделено в буфере. Это может привести к перезаписи памяти, что может быть использовано для захвата управления программой злоумышленником.
 
Пример переполнения буфера:
1 2 3 4 5 6 7 8 9 10void vulnerable_function(char *input) { char buffer[10]; strcpy(buffer, input); // Нет проверки длины строки } int main() { char large_input[100] = "Очень длинная строка..."; vulnerable_function(large_input); return 0; }Риск: Если злоумышленник передаст строку, превышающую 10 символов, это может перезаписать критическую память и вызвать сбой программы или выполнение вредоносного кода.
 - 
Использование неинициализированной памяти:
- Программа может использовать область памяти, которая не была инициализирована, что может привести к неопределенному поведению или утечке данных.
 
Пример:
1 2int *ptr; printf("%d\n", *ptr); // Использование неинициализированного указателя - 
Двойное освобождение памяти (double free):
- Когда программа пытается освободить одну и ту же область памяти более одного раза, это может привести к повреждению данных и сбоям.
 
Пример:
1 2 3int *ptr = malloc(10 * sizeof(int)); free(ptr); free(ptr); // Повторное освобождение памяти - 
Утечки памяти:
- Происходят, когда программа не освобождает память после ее использования, что может привести к исчерпанию доступной памяти и сбоям.
 
 
6.1.2 Защита от уязвимостей в управлении памятью
Существуют различные механизмы и методы, которые помогают предотвратить уязвимости, связанные с управлением памятью:
- 
Механизмы защиты памяти на уровне ОС:
- Address Space Layout Randomization (ASLR): Этот механизм случайно распределяет ключевые области памяти (например, стеки, кучи, сегменты кода), что затрудняет предсказание расположения данных в памяти и предотвращает атаки на основе переполнения буфера.
 - Data Execution Prevention (DEP): Запрещает выполнение кода в сегментах памяти, предназначенных только для данных (например, в куче или стеке).
 
 - 
Инструменты для выявления проблем с памятью:
- Valgrind: Анализатор памяти, который помогает выявлять утечки памяти, использование неинициализированной памяти и другие проблемы с памятью.
 - AddressSanitizer: Инструмент для обнаружения ошибок в управлении памятью, включая переполнение буфера и использование освобожденной памяти.
 
 - 
Использование безопасных функций:
- Вместо небезопасных функций, таких как 
strcpy()иsprintf(), лучше использовать их безопасные аналоги, такие какstrncpy()иsnprintf(), которые принимают дополнительные параметры для ограничения объема копируемых данных. 
 - Вместо небезопасных функций, таких как 
 
Пример программы с исправлением переполнения буфера:
 | 
 | 
6.2 Управление доступом и безопасность процессов
6.2.1 Управление правами доступа
Управление правами доступа является важным аспектом безопасности системного программирования. Операционные системы предоставляют различные механизмы для управления доступом процессов и пользователей к ресурсам системы.
- 
Discretionary Access Control (DAC):
- Это традиционный механизм управления доступом, который используется в UNIX и Linux. В DAC каждый файл и ресурс имеют права доступа для владельца, группы и остальных пользователей. Права доступа включают чтение, запись и выполнение.
 
Пример:
1 2ls -l my_file.txt -rw-r--r-- 1 user group 1024 Oct 18 12:00 my_file.txtВ этом примере файл
my_file.txtимеет следующие права доступа:- Владелец файла (user) может читать и записывать файл.
 - Пользователи группы могут только читать файл.
 - Остальные пользователи могут только читать файл.
 
 - 
Mandatory Access Control (MAC):
- В MAC доступ к ресурсам контролируется системой на основе заданных политик. Примером MAC является SELinux или AppArmor, которые используются для более строгого контроля доступа к ресурсам и процессам.
 
 - 
Control Groups (cgroups):
- Cgroups — это механизм в Linux, который позволяет ограничивать и изолировать ресурсы (такие как процессор, память, диск) для групп процессов. Это особенно полезно при работе с контейнерами, где необходимо ограничить использование ресурсов для каждого контейнера.
 
 
Пример использования cgroups для ограничения ресурсов:
 | 
 | 
6.2.2 Работа с правами суперпользователя (root)
Использование прав суперпользователя (root) должно быть ограничено для предотвращения несанкционированного доступа к критическим системным ресурсам. В системном программировании существует несколько подходов для минимизации использования прав суперпользователя:
- 
Принцип наименьших привилегий:
- Программы и пользователи должны получать минимальные права, необходимые для выполнения их задач. Это уменьшает потенциальные риски безопасности.
 
 - 
Setuid-программы:
- В UNIX-системах программы могут быть выполнены с привилегиями владельца файла, используя бит setuid. Это полезно для программ, которые должны временно получать права суперпользователя.
 
 
Пример установки setuid на программу:
 | 
 | 
6.3 Основные уязвимости и их предотвращение
6.3.1 Атаки типа “переполнение буфера”
Атаки типа “переполнение буфера” — это одна из наиболее распространенных уязвимостей в системном программировании. Злоумышленники могут использовать эту уязвимость для перезаписи памяти программы, что позволяет выполнить произвольный код с привилегиями программы.
Предотвращение переполнения буфера:
- Используйте безопасные функции работы с буферами, такие как 
strncpy(),snprintf(). - Применяйте защитные механизмы, такие как ASLR и DEP, которые затрудняют предсказание расположения буферов в памяти и выполнение вредоносного кода.
 
6.3.2 Гонки данных
Гонки данных происходят, когда несколько потоков или процессов одновременно обращаются к общим данным без надлежащей синхронизации. Это может привести к некорректному поведению программы и уязвимостям безопасности.
Пример гонки данных:
 | 
 | 
В этой программе два потока одновременно увеличивают переменную counter, что приводит к некорректному результату из-за гонки данных.
Предотвращение гонок данных:
- Используйте механизмы синхронизации, такие как мьютексы или семафоры, для контроля доступа
 
к общим данным.
Пример с мьютексом:
 | 
 | 
Практическое задание
Задание 1:
- Напишите программу на C с использованием функций динамического выделения памяти (
malloc()иfree()). Специально создайте утечку памяти и используйте Valgrind для ее обнаружения и исправления. 
Задание 2:
- Создайте программу, которая имитирует гонку данных между двумя потоками. Исправьте программу с использованием мьютексов для предотвращения гонки данных.
 
Задание 3:
- Настройте SELinux или AppArmor на Linux для защиты системного приложения, ограничив его права доступа к файловой системе и сетевым ресурсам.
 
Заключение к главе 6
В этой главе мы изучили ключевые аспекты безопасности в системном программировании, включая управление памятью, контроль доступа и предотвращение распространенных уязвимостей, таких как переполнение буфера и гонки данных. В следующих разделах мы рассмотрим дополнительные современные темы, такие как автоматизация и DevOps-инструменты для системного программирования.