
Это тематическое исследование является результатом задания по курсу ECE 9069: Introduction to Hacking : https://whisperlab.org/introduction-to-hacking/
CVE-2022-0185 — это ошибка переполнения буфера в куче (heap-based buffer overflow), обнаруженная в том, как функция legacy_parse_param в функциональности Filesystem Context ядра Linux проверяет длину переданных параметров. Непривилегированный локальный пользователь (в случае, если включены непривилегированные пользовательские пространства имён; в противном случае требуется привилегия CAP_SYS_ADMIN в пределах пространства имён), способный открыть файловую систему, которая не поддерживает API Filesystem Context (и, таким образом, переключается на устаревшую обработку), мог использовать эту ошибку для повышения своих привилегий в системе. [1]
После сообщения об этой уязвимости был выпущен патч для исправления ошибки:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=722d94847de2
https://ubuntu.com/security/CVE-2022-0185#impact-score
Существует подробный разбор от исследователя: https://www.hackthebox.com/blog/CVE-2022-0185:_A_case_study
В этом репозитории я объясню основные шаги и сопутствующую справочную информацию для воспроизведения этой уязвимости. Кроме того, если что-то останется для вас неясным, вы можете написать мне на почту: [email protected]. Я с удовольствием отвечу на ваши вопросы.
Уязвимость CVE-2022-0185 была опубликована 11.02.2022, с базовой оценкой CVSS 3.x 8.4 (High).[1] Эта уязвимость представляет собой переполнение буфера в куче, вызванное целочисленным переполнением беззнакового целого (unsigned integer underflow).
Уязвимость была внесена в ядро Linux v5.1, затрагивая все дистрибутивы Linux с версиями ядра выше 5.1. Например, Ubuntu 20.04 LTS (focal) была уязвима к этой ошибке. Однако патч был выпущен и доступен начиная с версии 5.4.0-96.109.[3]
Эксплуатация этой уязвимости позволяет непривилегированному локальному пользователю повысить свои привилегии в системе, потенциально ставя под угрозу всю систему.[1] [2] Вот подробный анализ оценки CVSS: Базовая оценка: 8.4, что указывает на значительный риск безопасности, требующий немедленного внимания. Оценка воздействия: 5.9, что предполагает существенный потенциальный ущерб в случае эксплуатации. Высокие значения конфиденциальности, целостности и доступности способствуют этой оценке. Оценка эксплуатируемости: 2.5, что предполагает относительно высокую эксплуатируемость. Локальный вектор атаки, высокие значения целостности и доступности способствуют этой оценке.
Таблица 1.1 и Таблица 1.2 содержат дополнительную информацию об этих оценках и их компонентах.
| Серьёзность CVSS v3.1 | Значение |
|---|---|
| Базовая оценка | 8.4 HIGH |
| Оценка воздействия | 5.9 |
| Оценка эксплуатируемости | 2.5 |
Таблица 1.1 Оценки серьёзности CVSS[1]
Таблица 1.2 Вектор CVSS[1]
В современных компьютерах существуют два типа целых чисел: знаковые и беззнаковые. Представление знаковых чисел обычно включает операцию, называемую дополнительным кодом.[4] «В дополнительном коде старший двоичный разряд используется как знак, указывающий, является ли двоичное число положительным или отрицательным»[4]
Введение дополнительного кода преобразует операцию вычитания в сложение, тем самым упрощая проектирование и реализацию ЦП. Преобразование целого числа в дополнительный код включает три шага:[4]
Рис. 2.1.1.1 показывает процесс преобразования на схеме с реальным примером преобразования «-6» в формат дополнительного кода.

Рис. 2.1.1.2 Сложение с использованием дополнительного кода
Рисунок 2.1.1.2 показывает процесс сложения дополнительного кода «-6» с «+6». Это демонстрирует, как использование дополнительного кода позволяет использовать сложение в качестве замены вычитания.

Рис. 2.1.1.2 Сложение с использованием дополнительного кода
Из раздела 2.1.1 мы уже знаем, что такое дополнительный код. Теперь рассмотрим сценарий переполнения беззнакового целого в компьютерах. В современных компьютерах при использовании беззнаковых чисел самый старший бит не рассматривается как знаковый; вместо этого он является частью самого беззнакового числа. Это означает, что при выполнении вычитания с беззнаковым числом нужно быть осторожным, так как это может привести к состоянию, известному как переполнение беззнакового целого.[5]
Рис. 2.1.2.1 иллюстрирует ситуацию вычитания 6 из 5 для 8-битного беззнакового числа. Конечным результатом является 255 из-за зацикливания беззнакового числа. Когда такое переполнение происходит в условном операторе, оно может нарушить функциональность этого оператора.

Рис. 2.1.2.1 Переполнение беззнакового целого
В ядре Linux Allocator Slab (Slab Allocator) — это механизм управления памятью, используемый для эффективного выделения и освобождения небольших блоков памяти. Он обеспечивает производительность, поддерживая несколько кэшей слэбов, каждый из которых содержит блоки памяти фиксированного размера. Как правило, kmalloc-32 выделяет 32 байта памяти, это слэб kmalloc-32, тогда как kmalloc-4k выделяет 4096 байт памяти, это слэб kmalloc-4k.[6]
Кроме того, выделение слэбов в ядре Linux обычно включает выделение памяти из непрерывного адресного пространства в области кучи ядра. Это непрерывное адресное пространство управляется ядром и используется для выделения памяти для различных объектов ядра и структур данных. Рис. 2.2.1.1 показывает расположение слэбов в памяти ядра Linux.

Рис. 2.2.1.1 Аллокатор Slab в Linux [7] (Автор этого рисунка — https://leviathan.vip/)
Если вы хотите воспроизвести процесс с самостоятельно скомпилированным ядром Linux, пожалуйста, прочитайте следующие markdown-файлы, чтобы получить справочную информацию:
Примечание:
Все markdown-файлы, а также код и скрипты находятся в разных папках этого репозитория. В каждой папке есть собственный markdown-файл. Прочитайте его, прежде чем пытаться что-либо сделать!
В разделе 2.1 мы объяснили, как работает переполнение беззнакового целого. Теперь мы рассмотрим функцию ядра, которая содержит эту уязвимость.
Пользователь «clubby789» обнаружил уязвимость в функции ядра legacy_parse_param. Эта функция в первую очередь отвечает за разбор параметров, передаваемых ядру. В случае CVE-2022-0185 она вызывалась после использования fsopen для открытия файлового дескриптора, а затем использования функции fsconfig для передачи пар ключ-значение конфигурации ядру. Упрощённая версия legacy_parse_param показана в следующем коде. [2]```c static int legacy_parse_param(struct fs_context *fc, struct fs_parameter *param) { struct legacy_fs_context *ctx = fc->fs_private; // [1] unsigned int size = ctx->data_size; // [2] size_t len = 0; int ret; [ ... ] switch (param->type) { case fs_value_is_string: len = 1 + param->size; // [3] case fs_value_is_flag: len += strlen(param->key); break; default: return invalf(fc, "VFS: Legacy: Parameter type for '%s' not supported", param->key); } if (len > PAGE_SIZE-2-size) return invalf(fc, "VFS: Legacy: Cumulative options too large"); // [4] [ ... ] if (!ctx->legacy_data) { ctx->legacy_data = kmalloc(PAGE_SIZE, GFP_KERNEL); // [5] if (!ctx->legacy_data) return -ENOMEM; } ctx->legacy_data[size++] = ','; // [6] len = strlen(param->key); memcpy(ctx->legacy_data + size, param->key, len); size += len; if (param->type == fs_value_is_string) { ctx->legacy_data[size++] = '='; memcpy(ctx->legacy_data + size, param->string, param->size); size += param->size; } ctx->legacy_data[size] = '\0'; ctx->data_size = size; ctx->param_type = LEGACY_FS_INDIVIDUAL_PARAMS; return 0; }
Из приведённого выше фрагмента кода видно, что строки [1] и [2] задают контекст кода, а строка [4] содержит оператор, в котором происходит беззнаковое переполнение снизу (underflow). Строка [5] обрабатывает выделение slab из кучи, а строки [6] и [7] отвечают за заполнение выделенного slab'а данными. Обратите внимание, что в строке [6] добавляется запятая (',') в качестве отдельного разделителя, а также знак равенства ('='), что приводит к двум дополнительным байтам сверх фактического размера данных.
В строке [4] переменные внутри оператора if содержат PAGE_SIZE (макрос, установленный в 4096) и size (беззнаковое 64-битное число). Когда беззнаковое число накапливается до 4095, происходит underflow, из-за чего оператор if всегда принимает значение false. Это позволяет выполнить запись за пределами границ (out-of-bounds) в соседний slab. Underflow вызван вычитанием беззнакового числа: 4096 - 4095 даёт беззнаковое число 18446744073709551615.[2]
Результат `18446744073709551615` объясняется в следующем тексте:```c
if (len > PAGE_SIZE-2-size) return invalf(fc, "VFS: Legacy: Cumulative options too large");
Обратите внимание, что здесь PAGE_SIZE равен 4096 байт, а 2 — это символ , и =, добавленные для разделения каждой пары key-value. Проблема в том, что size — это беззнаковое значение, поэтому когда size достигает 4095, выражение PAGE_SIZE-2-size будет равно singned value: -1, однако для беззнакового значения: 18446744073709551615 из-за дополнения до двух, как показано на следующей диаграмме! [3]

Следовательно, приведённый выше оператор if всегда будет ложным, а это значит, что остальные данные будут скопированы в кучу за пределы slab, который мы выделили!
Поняв, как может произойти это переполнение беззнакового числа, мы можем перейти к созданию proof of concept (POC) кода для демонстрации уязвимости.
Пользователь “clubby789” предоставил нам подробный POC-код, показанный в следующем фрагменте. Код лаконичен: сначала он открывает файловый дескриптор под названием ext4, затем использует fsconfig несколько раз для заполнения ядра данными.
Здесь важно отметить две вещи:
В разделе 3.1.1 мы упоминали, что перед наблюдением записи за пределами границ необходимо заполнить 4095 байт данных. Поскольку каждый цикл заполняет только 35 байт, нам необходимо выполнить операцию 117 раз (4095 / 35), прежде чем наблюдать память кучи, чтобы завершить доказательство концепции.
### 3.1.3 POC с QEMU
В этом репозитории: GitHub - dcheng69/CVE-2022-0185-Case-Study, мы предоставили shell-скрипт с именем poc.sh для упрощения процесса отладки. Пожалуйста, прочитайте markdown-файл в папке Poc перед началом настройки.
Поскольку legacy_parse_param является функцией ядра, вам потребуется отлаживать именно функцию ядра. Для этого необходимо скомпилировать исходный код ядра, чтобы получить необходимые символы и исходный код. Мы также предоставили подробный markdown-файл для сопровождения процесса. Пожалуйста, обратитесь к папке Compile_linux за дополнительными сведениями.
На рис. 3.1.3.1 мы продемонстрировали, что после заполнения кучи ядра 4095 байтами данных нам успешно удалось вызвать запись за пределами границ за счёт использования андерфлоу беззнакового целого. Кроме того, мы заполнили в общей сложности 4130 байт данных в slab kmalloc-4k, успешно повредив соседний slab. Хотя в этом примере соседний slab не содержит информации (все нули), мы можем тщательно сконструировать наш код, чтобы использовать эту особенность для записи вредоносных данных. Мы продемонстрируем, как этого добиться, в разделе 3.2 «Эксплуатация».

**Рис. 3.1.3.1 POC с QEMU**
Более подробную информацию можно найти по адресу https://github.com/dcheng69/CVE-2022-0185-Case-Study/blob/main/Poc/poc.md в папке `Poc` этого репозитория!
## 3.2 Эксплуатация
После знакомства с этой уязвимостью мы можем перейти к её эксплуатации. Подробности описаны в папке `exploit-ubuntu`, а также в этом markdown-файле: https://github.com/dcheng69/CVE-2022-0185-Case-Study/blob/main/explot-ubuntu/exploit.md
Короче говоря, мы компилируем исходный код Ubuntu, получаем deb-файл, затем тестируем его на виртуальной машине для получения конкретной версии ядра. Затем мы корректируем смещения в коде эксплоита под целевую версию ядра, используя информацию из файла `System.map`. Наконец, мы обновляем grub и перезагружаемся для эксплуатации!
### 3.2.1 Обзор эксплуатации
Пользователь 'clubby789' предоставил нам подробный код эксплоита. Мы начнём с обзора, затем поясним несколько ключевых концепций с помощью диаграмм. В конце мы представим результаты эксплуатации на Ubuntu, запущенной в виртуальной машине (VirtualBox).
После демонстрации доказательства концепции для этой уязвимости мы можем перейти к её эксплуатации. На рис. 3.2.1.1 показан обзор того, как эксплуатировать эту уязвимость:
- Левая часть посвящена получению базового адреса ядра Linux. Это достигается с помощью андерфлоу беззнакового целого для переопределения поля m_ts структуры msg_msg, что позволяет читать за пределами границ и получать доступ к ранее распылённым структурам ядра.
- Правая часть направлена на получение привилегий root. Это достигается за счёт использования андерфлоу беззнакового целого для переопределения указателя next структуры msg_msg, направляя его на modprobe_path. Затем мы инициируем страничную ошибку (page fault), которая вызывает наш сконструированный FUSE-код, обеспечивая произвольную запись в пространстве ядра.

**Рис. 3.2.1.1 Обзор эксплуатации**
### 3.2.2 Получение базового адреса ядра Linux
Как мы анализировали ранее, в этой части мы будем использовать андерфлоу беззнакового целого для переопределения поля m_ts структуры msg_msg, что позволит выполнять чтение за пределами границ. Распыляя кучу структурами, содержащими указатели ядра, мы, как мы надеемся, получим утечку памяти.
Структура msg_msg — это структура данных в ядре Linux, используемая для реализации очередей сообщений System V. В этом разделе мы сосредоточимся на внутренней структуре struct msg_msg и логике функций, связанных с отправкой, получением и выделением сообщений. Как показано на рис. 3.2.2.1, это те функции, которые нам нужно понять.
Структура msg_msg является структурой данных в ядре Linux, используемой для реализации очередей сообщений System V.[8] В этом обсуждении мы сосредоточимся на внутренней структуре struct msg_msg, а также на логике функций, связанных с отправкой, получением и выделением сообщений. Как показано на рис. 3.2.2.1, это функции, которые нам нужно понять."
Реализация отправки сообщений находится в файле msg.c, где максимальная длина сообщения определена как 8192 байта. В функции alloc_msg сообщения разделяются на сегменты на основе их длины. Если длина сообщения вместе с заголовком сообщения превышает одну страницу (4096 байт), сообщение будет храниться в нескольких сегментах, связанных между собой указателями.

**Рис. 3.2.2.1 struct msg_msg: отправка и получение**
На рис. 3.2.2.2 видно, что struct msg_msg выступает в роли заголовка сообщения, занимая 0x30 байт памяти. Если в сообщении остаются данные, они сохраняются в сегментах сообщения и связываются с struct msg_msgseg. Следовательно, если ядро допускает сообщения длиной до 8192 байт, данные будут храниться максимум в трёх сегментах сообщения.

**Рис. 3.2.2.2 struct msg_msg**
На рис. 3.2.2.3 показана структура struct msg_msg. Из кода мы знаем, что поле m_ts — это то поле, которое необходимо переопределить для чтения за пределами границ. (Если нужно, исходный файл draw.io можно найти в папке `res`!)

**Рис. 3.2.2.2 структура struct msg_msg**
Теперь, когда мы понимаем структуру struct msg_msg, нам нужно разобраться, как получить утечку данных ядра. В ядре Linux есть функция рандомизации адресного пространства ядра (KASLR), что означает, что код ядра загружается по случайному адресу, определяемому на этапе загрузки. Однако смещение от начальной точки ядра до адреса любой функции остаётся постоянным, что позволяет выполнять определённые операции для заполнения пространства кучи структурами, содержащими конкретные функции ядра. Вычитая смещение, мы можем найти начальный адрес ядра.
К счастью, мы можем легко распылить кучу структурами seq_operations, открывая /proc/self/stat; они находятся в slab-объектах kmalloc-32. Определение seq_operations показано на рис. 3.2.2.3.

**Рис. 3.2.2.3 Структуры для утечки данных ядра**
Наконец, общий процесс показан на рис. 3.2.2.4. Мы начинаем с заполнения legacy_data 4095 байтами данных для подготовки к переопределению. Затем мы создаём сообщения с помощью struct msg_msg. Поскольку память кучи выделяется непрерывно, созданные сообщения, скорее всего, окажутся соседними с соседним slab'ом kmalloc-4k. Мы переопределяем поле m_ts, управляя данными, которые записываем в legacy_data.
Затем мы распыляем кучу множеством структур seq_operations размера kmalloc-32. После этого мы получаем данные из очереди сообщений, что инициирует чтение за пределами границ. Корректируя смещение от функции ядра, мы можем получить базовый адрес ядра.

**Рис. 3.2.2.3 Обзор чтения за пределами границ**
### 3.2.3 Получение привилегий root
Как и в предыдущем анализе, в этой части нам нужно настроить файловую систему FUSE, которая позволяет нашему коду в пространстве пользователя обрабатывать страничные ошибки из пространства ядра. При этом мы переопределим указатель msg_msgseg *next так, чтобы он указывал на modprobe_path, что обеспечит произвольную запись в пространстве ядра.
Сначала рассмотрим стек вызовов FUSE, показанный на рис. 3.2.3.1. В целом FUSE позволяет реализовать файловую систему в пространстве пользователя. При выполнении новой операции система вызывает код, который мы определили для FUSE!

**Рис. 3.2.3.1 Обзор FUSE**
Давайте проанализируем, как использовать FUSE для обеспечения произвольной записи в пространстве ядра. Сначала рассмотрим операцию отправки сообщения для struct msg_msg. Эта операция включает запись данных в пространство ядра. Если мы сконструируем сообщение из двух сегментов, мы сможем переопределить указатель и записать по любому адресу ядра. Эта концепция проиллюстрирована на рис. 3.2.3.2.
Кроме того, после изучения логики отправки сообщений в очередь мы знаем, что этот процесс включает копирование буфера из пространства пользователя в пространство ядра. Если сообщения достаточно длинные, они копируются по сегментам. Если нам удастся вызвать страничную ошибку во время этого процесса, мы сможем добиться сценария, показанного на рис. 3.2.3.2.
К счастью, FUSE предоставляет необходимую функциональность. Мы можем отобразить страницу на FUSE, и при возникновении страничной ошибки система вызовет нашу функцию чтения FUSE для её обработки. Приостановив процесс чтения до тех пор, пока андерфлоу беззнакового целого не переопределит указатель msg_msg_seg *next, а затем возобновив процесс отправки сообщения, мы сможем добиться произвольной записи. Весь процесс продемонстрирован на рис. 3.2.3.3.

**Рис. 3.2.3.2 Использование отправки сообщения**

**Рис. 3.2.3.3 FUSE и отправка сообщения**
### 3.2.4 Эксплуатация в VirtualBox
Следуйте указаниям markdown-файла: https://github.com/dcheng69/CVE-2022-0185-Case-Study/blob/main/explot-ubuntu/exploit.md
После завершения компиляции вы можете увидеть результаты эксплуатации на рис. 3.2.4.1.

**Рис. 3.2.4.1 Эксплуатация в VirtualBox**
# 4. Устранение уязвимости
## 4.1 Официальные исправления
После сообщения об этой уязвимости Linux и многие его дистрибутивы включили исправления для устранения этой ошибки.[9] [10] [11]
На следующем скриншоте показан патч, принятый Линусом Торвальдсом. Устранение проблемы андерфлоу сводится к замене операции вычитания на сложение.

# 5. Влияние на реальный мир
В этом отчёте мы демонстрируем, как эту уязвимость можно использовать для компрометации системы Ubuntu. Кроме того, она потенциально может затрагивать старые системы, в которых отсутствуют регулярные обновления безопасности.
Что касается Kubernetes, эта уязвимость может приводить к повышению привилегий, побегу из контейнера или атакам типа «отказ в обслуживании».
Хотя в новостях не сообщалось об убытках, вызванных этой уязвимостью, она подчёркивает важность регулярного применения критических обновлений безопасности. Исследователь, сообщивший об этой уязвимости, является примером этичного хакинга, которого нам всем следует придерживаться.[12]
# Ссылки
[1] https://nvd.nist.gov/vuln/detail/CVE-2022-0185
[2] https://www.hackthebox.com/blog/CVE-2022-0185:_A_case_study
[3] https://ubuntu.com/security/CVE-2022-0185#impact-score
[4] [https://en.wikipedia.org/wiki/Two%27s_complement](https://en.wikipedia.org/wiki/Two's_complement)
[5]https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Unsigned-Overflow.html
[6] https://www.kernel.org/doc/gorman/html/understand/understand011.html
[7] https://leviathan.vip/
[8] https://www.willsroot.io/2021/08/corctf-2021-fire-of-salvation-writeup.html
[9] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=722d94847de2
[10] https://ubuntu.com/security/CVE-2022-0185
[11] https://access.redhat.com/security/cve/CVE-2022-0185
[12]https://jfrog.com/blog/the-impact-of-cve-2022-0185-linux-kernel-vulnerability-on-popular-kubernetes-engines/
[14] https://github.com/chenaotian/CVE-2022-0185?tab=readme-ov-file
[15] https://www.tutorialspoint.com/two-s-complement
| Метрики CVSS v3.1 | Значение |
|---|
| Вектор атаки (AV) | Локальный |
| Требуемые привилегии (PR) | Отсутствуют |
| Взаимодействие с пользователем (UI) | Отсутствует |
| Конфиденциальность (C) | Высокая |
| Целостность (I) | Высокая |
| Доступность (A) | Высокая |