LINUX.ORG.RU

Си. Почему бы не запретить запись в стек?

 


1

4

Решил немного разобраться как работают уязвимости. Как я понял, весомая их часть модифицирует стек.

#include <stdio.h>

register long unsigned rsp asm("rsp");

void print_arg(int arg) {
    ((int*)rsp)[3] = 0xBADC0DE;
    printf("arg = %x\n", arg);
}

int main(int argc, char **argv) {
    print_arg(0xF00D);
    return 0;
}

Этот код отрабатывает и не выводит ошибкок с

-fhardened -fcf-protection=full

На мой взгляд выглядит небезопасно.

Почему бы не вставлять проверки на ассемблере при записи в память, на включаемость в регион стека? Если нужно записать что то в аргумент на стеке (int), то проверку можно не вставлять. При записи по указателю, уже обязательно вставлять. Если адрес стека то ошибка. В memset проверять пересечение двух диапазонов.

Если на стек пишет fread(), то нужно вставить еще проверку на разрешающий диапазон буфера в который он пишет. Но тут компилятору нужно обязательно отслеживать откуда приходит поинтер, но если мы говорим про значения на стеке, то это не должно представлять сложности, значение всегда лежит в прошлых значениях стека, а значит вычислить размер буфера можно в момент компиляции, ну или записать в какую нибудь дополнительную переменную на стеке, если речь о сложном потоке управления и alloca.

void read_file(const char *name)
{
        char buff[999];
        FILE *f = fopen(name, "rb");
        read_block(f, buff);
}

void read_block(FILE *f, char *buff)
{
        // тут компилятор должен вывести что len(buff) == 999
        fread(buff, 1, 9999, f);
}

Что бы все идеально работало, нужно будет:

  • Пометить libc функции
  • Если функция работает со стеком как у меня в верхнем примере, но это правильное поведение, пометить и ее
  • Перекомпилировать основные библиотеки, что бы не ломать ABI можно ввести экспорт двух прототипов, с доп.значениями для проверки диапазонов и без, дублирование прототипов понадобится для малого числа функций
★★★★

Последнее исправление: MOPKOBKA (всего исправлений: 2)

Ответ на: комментарий от MOPKOBKA

Связь между сегментами и досом очень сильная

В досе (без экстендера от PharLap) не использовалась аппаратная защита памяти. Так что от сегментов толку там небыло. Да и были они ограничены 64К,что регулярно мешало.

что мне мешает переключить селектор сегмента или как он там назывался?

Если вы его переключили сами - значит вы знаете что делаете. Разговор же был о защите от непреднамеренных ошибок в обращении к памяти,а не о защите от каких-то зловредных действий. Впрочем - ядро системы можно написать так,что оно не будет позволять прикладной программе совсем уж произвольные действия с системными таблицами.

watchcat382
()
Ответ на: комментарий от watchcat382

Из четырёх колец использовались лишь самое верхнее и нижнее, а остальные не припомню, чтобы кто-то использовал.
Сказал об этому к тому, что аппаратная защита была, но её для защиты не использовали.

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 2)
Ответ на: комментарий от firkax

Повторю: только такая, что дос написан для процов которые без сегментов не работали вообще.

Да, мое утверждение это никак не опровергает. Хотя я не знаю верно ли твое.

Если от случайных багов - то то, что смена селектора делается специальной инструкцией и его намного сложнее переключить, чем просто прибавить к указателю неправильный индекс.

Прибавление индекса это ерунда, простейшее условие спасает. На каждый массив ты по сегменту делать не будешь, в чем преимущество то?

только в начале 2000-х

Лучше сравнивать с текущим состоянием, NX бит ввели еще до моего рождения, мне те проблемы неизвестны.

MOPKOBKA ★★★★
() автор топика
Ответ на: комментарий от watchcat382

Если вы его переключили сами - значит вы знаете что делаете

Нет, я не знаю что я делаю. Более того, я разметил сегмент так что он залез на чужую память.

MOPKOBKA ★★★★
() автор топика
Ответ на: комментарий от MOPKOBKA

Вот тут как раз боундчекинг и отвалиться, сишной функции будет все равно >есть там проверки или нет.

Отчасти вы правы. Но именно отчасти. Сишной-то функции всё равно,но вот не позволить передать в сишную функцию что-нибудь не то Ада вполне может.

Простой пример: В большинство сишных функций,работающих с буферами, мы передаем две сущности - адрес буфера и его длину.А если передаем буфер из Ады то можем передать только имя этого буфера. И уже в переходнике к сишной функции получить из этого имени адрес и размер,которые являются неотъемлимыми атрибутами этой единой именованной сущности. Причем пишется это очень удобно,ошибок наделать сложно.

Практика показывает,что ошибки переполнения буфера происходят как правило не от того,что библиотечная функция косячит,а от того что ей на вход скормили неправильные параметры.

watchcat382
()
Ответ на: комментарий от MOPKOBKA

А вы почитайте.
В архитектуре x86 много разного типа сегментных регистров.
С мануалов и публикаций по этому вопросу и нужно начинать.

https://studfile.net/preview/7716604/page:5/ 5.Регистровая модель процессора x86.

https://translated.turbopages.org/proxy_u/en-ru.ru.fecba8f7-65c4a84d-ecd81804... сегментация памяти x86

...

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 1)
Ответ на: комментарий от watchcat382

Ну проверка размера буфера по аргументу и С поддерживает.

void write(int n, char s[static n]);
Но в языке иметь это конечно полезно, с проверкой генерируемой компилятором.

MOPKOBKA ★★★★
() автор топика
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

https://habr.com/ru/articles/128991/ Организация памяти

Я знаю что это такое, но я хочу что бы мне показали преимущества конкретные, я их не вижу.

Преимущество в том, что будет использована аппаратная защита сегментов.

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 1)
Ответ на: комментарий от Forum0888

Я не понимаю как это избавит от проблем которые я описываю. Какая именно ошибка будет отловлена? Секции elf тоже «аппаратные».

MOPKOBKA ★★★★
() автор топика
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

Это для защиты от доступа к данным за пределы массива, доступ лишь для тех у кого есть права доступа, ...

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 1)
Ответ на: комментарий от firkax

смена селектора делается специальной инструкцией и его намного сложнее >переключить, чем просто прибавить к указателю неправильный индекс.

Причем если правильно помню - эта специальная инструкция привилегированная и произвольное изменение селекторов можно запретить. В смысле что будет аппаратно вызываться обработчик,который проверит что там делать собрались.

Прибавлением любых индексов к указателю ты в другой сегмент не попадёшь.

Что есть крайне полезное ограничение.

И что самое обидное - в современных процессорах как минимум с обычном 32-разрядном режиме этот сегментный механизм защиты памяти есть и работоспособен (про 64-бит не знаю что там). Но такая полезная аппаратная возможность не используется только потому что тридцать лет назад на 386 процессоре автору линукса захотелось чуть-чуть улучшить производительность - тогда загрузка сегментных регистров медленно работала. А может ему просто не захотелось возиться и разбираться. Он же не ожидал что его проект приобретет такую популярность.

watchcat382
()
Ответ на: комментарий от watchcat382

Почему не используется не знаю.
Знаю лишь о том, что - НЕ ИСПОЛЬЗУЕТСЯ.

Накладные аппаратные расходы при этом конечно имеются.

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 1)
Ответ на: комментарий от Forum0888

Так и сегмент можно нечайно расширить. Тогда уже спасет указание размера fread. А на самом деле надежность не повышается, и там и там можно ошибиться.

MOPKOBKA ★★★★
() автор топика
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от firkax

ELF приспособить можно, но тогда опять будут NEAR/FAR указатели

Вот только FAR-указатели потребуются лишь в случае необходимости адресовать массив размером больше 4Gb. А не 64К как было в те времена когда сегменты программистам «мешали». Сомневаюсь что в программах так уж часто требуются непрерывные массивы размером в четыре гигабайта.

куча кода сломается.

А какому коду не хватит сегментов по 4G,адресуемых NEAR указателем?

это будет существенно другая платформа.

Как раз аппаратная платформа будет та же самая x86-32. Различие будет только в работе ядра системы с памятью и механизмами ее защиты в процессоре.

watchcat382
()
Ответ на: комментарий от MOPKOBKA

В Аде все же слишком мало проверок

Вам мало проверок в Аде? Ну тогда даже не знаю какой язык вам подойдет.

даже беду с Null оставили как есть.

Что вы имеете в виду под «бедой с Null»? В чем там в Аде беда?

watchcat382
()
Ответ на: комментарий от MOPKOBKA

На каждый массив ты по сегменту делать не будешь

А почему,собственно,нет? Не так уж много программ где используется очень много отдельных массивов. К тому же делать отдельный сегмент имеет смысл только на те массивы которые хотябы теоретически могут переполниться.

Вот более реальная проблема - это динамически создаваемые массивы. Не те что в отдельных секциях elf-файла объявлены,а те что возникают по ходу работы программы. И вот под них надо динамически создавать сегменты (при необходимости). То есть реализация условного malloc будет более сложной и как следствие более медленной. И ядру работы прибавится. Но всё это тоже вполне решаемо.

Просто на мой взгляд тридцать лет назад основным вектором развития ОС была гонка за производительностью. Потому от «внутрипрограммной» защиты памяти разработчики ОС и отказались. А сейчас приоритет смещается в сторону надёжности работы так как для абсолютного большинства задач производительности и так хватает с большим запасом. Вон в RISC-V какие-то расширения на эту тему придумывают вроде.

watchcat382
()
Ответ на: комментарий от MOPKOBKA

И как это может помочь в проверке выхода за границу массива? А сегмент как?

Аппаратной проверкой выхода за границу сегмента.

В чем проблема передавать start, end и не писать за их пределы?

В том что это будет чисто программным решением и к каждой записи в массив добавится выполнение проверочного кода.

watchcat382
()
Последнее исправление: watchcat382 (всего исправлений: 1)
Ответ на: комментарий от watchcat382

А почему,собственно,нет?

В архитектуре x86 всё хорошо продумано и там «куча мала» нет.
Каждый процесс будет иметь свой набор системных таблиц.

Экспериментально вполне реально в Linux можно добавить такого рода защиту.

Скорее проблема в том, что в других процессорах такой возможности нет.

Реализуется это приблизительно так.

 - дополняем в линкер и загрузчик требуемую функциональность;

 - добавляем в core необходимый API

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 4)
Ответ на: комментарий от watchcat382

Нет, NEAR/FAR указатели никак на размеры массивов не влияют. И вообще ты какую-то ерунду написал.

Различие будет только в работе ядра системы с памятью

В первую очередь как раз юзерспейса, и не только в виде «перекомпилируем под новый режим» а ещё и с правками исходников много где.

firkax ★★★★★
()
Ответ на: комментарий от Forum0888

Всё есть, а делать никому не охота …

Возможно причина еще и в том,что линукс работает и на тех процессорах где нет сегментного механизма защиты памяти. Поэтому и x86 привели к некоторому «общему знаменателю» неиспользованием сегментного механизма. Иначе было не сделать переносимое ядро наверно.

watchcat382
()
Ответ на: комментарий от watchcat382

Вам мало проверок в Аде? Ну тогда даже не знаю какой язык вам подойдет.

В Rust явно ситуация лучше.

Что вы имеете в виду под «бедой с Null»? В чем там в Аде беда?

Есть указатели, и указатели могут быть null.

MOPKOBKA ★★★★
() автор топика
Ответ на: комментарий от MOPKOBKA

проще уволить тех, кто так и не научился не писать мимо буфера, потому что это первый навык в программировании вообще… и не заниматься ерундой с сегментами.

что в профессии может сделать поц, что вместо 8 подставляет 16? зачем он? кто платит ему деньги, куда он их потом несет? наверняка на поддержку терроризму.

короче уволить, и всех делов.

а защита памяти достигается строгой типизацией…о чем я уже говорил.

зы. писать мимо буфера, это как сс..ть мимо унитаза, а это уже к преображенскому с барменталем, чтоб обратно в собаку переделали.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от firkax

Нет, NEAR/FAR указатели никак на размеры массивов не влияют.

При ограничении размера сегмента в 64К как раз очень даже влияли. Почему вдруг не будут влиять при сегментах в 4G?

И вообще ты какую-то ерунду написал.

В чем именно «ерунда»?

не только в виде «перекомпилируем под новый режим» а ещё и с правками >исходников много где.

Где например? Причем чтобы было «много».

watchcat382
()
Последнее исправление: watchcat382 (всего исправлений: 1)
Ответ на: комментарий от watchcat382

Сомневаюсь что в программах так уж часто требуются непрерывные массивы размером в четыре гигабайта.

В Intel APX добавили JMPABS, для абсолютного прыжка на 64 битный адрес. Я подумал, что наверное кому то не хватало 4 гб только для кода?

MOPKOBKA ★★★★
() автор топика
Ответ на: комментарий от watchcat382

Не влияли на 64к. Селектор это не продолжение индекса а отдельное поле. С 64к сегментами максимальный нативный массив (тот, к элементам которого ты обращаешься с помощью квадратных скобок) тоже 64к.

В чем именно «ерунда»?

Выше написал.

Где например? Причем чтобы было «много».

Почти весь современный софт рассчитан либо на то, что размер указателя равен размеру машинного слова, либо на то что он равен sizeof(long), либо на то что он равен sizeof(size_t), либо все три предположения сразу. Причём, если с машинным словом и size_t это ещё 32-битные нововведения, то sizeof(ptr)==sizeof(long) было и на 16-битных процах.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Почти весь современный софт рассчитан либо на то, что размер указателя равен размеру машинного слова

Можно примеры? Им дали uintptr_t...

MOPKOBKA ★★★★
() автор топика
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от firkax

сегменты вводили как хак, поскольку регистры был короткими, а физ.адреса уже длинными.

это время прошло. сейчас длинное все и наступила другая эпоха. эпоха длинного адреса.

alysnix ★★★
()
Ответ на: комментарий от MOPKOBKA

Открой любую прогу где есть тайпкасты между указателями и целыми. До 64 битов часто было ещё int<->pointer которые на 64 битах стали багнутыми (32 vs 64), ушло лет 10 на исправление популярного софта от этих проблем.

firkax ★★★★★
()
Ответ на: комментарий от alysnix

тебе придется создавать и удалять сегменты на каждый вызов функции и на каждую аллокацию в куче

Да.

а потом сегменты кончатся

А их нету, почитай о чем текущий разговор.

MOPKOBKA ★★★★
() автор топика