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)

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

А где в посте было сказано «не использую»?

У меня API разрабатывается для использования метаданных в run-time, а не compile-time, поэтому «всё строго».

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

У меня API разрабатывается…

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

он и эксплуатирует перечисленные «небезопасности» в си и хочет их «забороть», но вовсе не так как следует.

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

Это легко исправить:

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

Теперь не падает. А повреждения есть.

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

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

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

надо просто написать

Напиши хотя бы для fread, а я посмотрю как надо. Пока что не представляю даже как ты это сделаешь. Не знаю я таких способов.

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

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

int read_block(FILE *f, char *buff, int buf_size) {
   return fread(buff, 1, buf_size, f);
}
alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

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

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

а то что ты лох и не отличаешь 8 от 16 - это не ее дело. может ты из аула приехал и у вас так принято

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

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

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

а то что ты лох и не отличаешь 8 от 16 - это не ее дело. может ты из аула приехал и у вас так принято

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

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

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

ты нарушаешь контракт, получаешь облом, и кричишь дурным голосом.

но проблема решается созданием структуры DataBuffer, с буфером внутри и размером. и написанем функций чтения-записи, что работают с этой структурой.

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

ты просто должен выполнять условия контракта.

Послушай, я уже десять раз тут написал, что я не собираюсь ничего писать на С, я хочу поправить чужой код.

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

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

на следующем уровне абстракции, ради защиты от невнимательности и дурака, рисуют датабуфер, который несет в себе размер, и весь обмен делают на нем, если оно так парит.

в с++ это делается особенно просто на темплейтах, в си сложней, надо рисовать макросы. но задача решаема.

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

нынешние технологии разработки - НАСКАЛЬНЫЕ.

если для тебя «нынешние технологии» означают пятидесятилетний переассемблер ака C, то у меня есть новости. Сейчас никого не удивишь рантайм рефлексией/интроспекцией, в той же жабе, например, функция/метод может несколькими способами размотать стек и посмотреть, кто там каво чево ее дернул.

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

А ещё можно себе специально хер дверью прищемить, и потом всем рассказывать про неправильные двери.

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

. Слишком многие воспринимают С как простой транслятор в ассемблер x86.

Вот он! Лови его, пинай!

если для тебя «нынешние технологии» означают пятидесятилетний переассемблер ака C, то у меня есть новости. Сейчас никого не удивишь рантайм рефлексией/интроспекцией, в той же жабе, например, функция/метод может несколькими способами размотать стек и посмотреть, кто там каво чево ее дернул.

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

Я тебе про то что программер сам решает когда ему юзать unsafe а когда нет. Так и в Си (тут выше правильно заметили) ты сам решаешь что и как тебе юзать а не запрещать что то …

mx__ ★★★★★
()

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

Перед вызовом функции можно ведь проверку сделать?

Лишний раз почитать не помешает.

https://learn.microsoft.com/ru-ru/cpp/cpp/calling-conventions?view=msvc-170 Соглашения о вызовах

https://translated.turbopages.org/proxy_u/en-ru.ru.a56a29a8-65c3ac13-e630d601... Соглашения о вызовах в C / C ++

https://book-pc.ru/programmirovanie/1484-obratnye-vyzovy-v-c.html книга Обратные вызовы в C++

https://learn.microsoft.com/ru-ru/cpp/build/x64-software-conventions?view=msv... Общие сведения о соглашениях ABI x64

https://ru.wikipedia.org/wiki/Соглашение_о_вызове

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

или уж Ada, если на то пошло.

Вот тоже никогда не мог понять почему если от программы требуется именно надежность - не взять специально предназначенный для этого очень хороший инструмент? Компилятор gnat доступен с середины 90х. Разве что на 80286 его небыло,но там был компилятор Meridian ADA и я им успешно пользовался. А уж последние четверть века gnat доступен бесплатно прямо в дистрибутивах линукса. Виндовый кстати тоже есть для желающих. В тех же 90х общим местом в дискуссиях о выборе языка была «сложность» Ады. Ну если в сравнении с каким-нибудь Turbo C 2.0 то да,могу согласиться. Но в сравнении с почти любым языком сейчас - Ада как бы даже и не попроще в освоении будет за счет своей логичности. Сравнить хотябы с тем что нагромоздили в стандарте С++ за последние два десятка лет. Причем именно надежности как раз особо и не прибавилось. В результате программисты продолжают крутиться в бесконечном цикле редактор-компилятор-отладчик и тратить время на отлов ошибок,за которые адский компилятор просто сразу бьет по рукам и даже не собирает исполняемый файл пока не напишешь как положено.

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

Не выдумывай, этот функционал ещё в 80286 16-битном проце был в 1982 году.

Я могу поинтересоваться - как проверки на уровне сегментов помогут с corruptions в пределах одного stack frame?

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

Стек и так в отдельном сегменте

Сейчас и в линуксе - нифига не в отдельном. У линукса слишком примитивная модель распределения памяти. Запускаемой программе просто выделяется кусок памяти и внутри него она может творить любую дичь. Вот в 90х была операционная система OS/2 где таки действительно была возможна «внутрипрограммная» защита памяти. Можно было хоть функцию хоть массив,хоть стек положить именно что в отдельный сегмент и сам процессор проверял корректность обращений. За всякие «хакерские трюки» и прочие «творческие изыски» по рукам било только так. Причем эта сегментная модель работала не только на 386+, но даже и на 80286,хотя и с ограничениями по размеру доступной памяти. Для 286 максимальный размер сегмента был 64К,для 386 максимальный размер 4Gb. Компилятор можно было использовать самый обычный,а вот от линкера требовалась специальная поддержка - способность делать исполняемые файлы формата NE.

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

Если писать код не зная как работает «виртуальная машина С», он по любому >развалится с выходом новой версии компилятора, поэтому никаких фич нету, >есть незнание.

А например компилятор ADA просто не даст написать код в котором есть «незнание». И поэтому с выходом новой версии компилятора однажды написанный код не разваливается.

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

Почитайте https://ru.wikipedia.org/wiki/Кольца_защиты.

Так всё равно в линуксе невозможно использовать этот механизм внутри прикладной программы. А вот в OS/2 можно было использовать сегменты. Почитать можно тут: https://habr.com/ru/articles/689326/ Но в тех древних процессорах операция загрузки сегментных регистров была медленной. Поэтому от надёжности исполнения программы как обычно отказались в пользу прироста скорости,причем довольно незначительного. Те технические ограничения давно стали не актуальными,а хорошая идея внутрипрограммной защиты памяти так и осталась заброшенной.

Как я вижу, компьютерная индустрия последние пол-века регулярно и многократно наступает на одни и те же грабли,которые можно обобщить одной классической фразой «640К хватит всем». Из-за временных и локальных технических ограничений то оказываются заброшенными вполне годные идеи,то плодятся странные конструкции из костылей.

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

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

Самое главное - что существующее железо,как минимум x86-32, весьма много проверок делать может. Но эти возможности в линуксе не используются хотябы даже так как использовались в OS/2. Другое дело,что например процессоры ARM подобного механизма «железных» проверок не имеют. Там пришлось бы эмулировать их программно,что конечно скажется на производительности,хотя и не сильно. Судя по коду который производят компиляторы ADA в котором проверки таки делаются,но тормозов особо не видно.

Или оно обходится на раз-два.

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

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

На каждый указатель нужно еще две переменные, которые будут хранить >диапазон буфера

В точности так сделана проверка границ массивов в ADA. Так что можете просто взять компилятор gnat и пользоваться. Абсолютное большинство ошибок на возможность которых вы указываете - он вам сделать не даст. Некорректный исходник или не соберется вообще(чаще всего) или выскочит исключение при работе программы вместо «тихого» повреждения содержимого памяти.

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

си не для каких-то там безопасных моделей и защищенных вычислительных >парадигм. си для того, чтобы делать с компом все, что позволяет hardware.

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

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

Так всё равно в линуксе невозможно использовать этот механизм внутри прикладной программы.

Безусловно.
Для решения этой задачи требуется доработка: компиляторов, линкера и ядра как минимум.

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

но есть много уже написанного ПО, которое переписывать никто не будет.

По мере развития искусственного интеллекта - может быть и будет. Он. Всё же программный код ему понимать легче чем произвольный текст на весьма сложных языках типа русского. Однако он уже и русский относительно прилично понимать научился,причем всего а несколько лет. Вполне допускаю что и под переписывание программного кода ИИ скоро кто-нибудь заточит. Проблема-то актуальная - слишком много legacy накопилось в мире.

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

А ещё можно себе специально хер дверью прищемить, и потом всем рассказывать про неправильные двери.

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

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

ну вот напиши драйвер на gc-шном D. и вставь его в микроконтроллер.

На D не получится если конечно это не какой-нибудь весьма монстрообразный МК, а вот на ADA вполне можно. Даже русскоязычная статья есть: https://habr.com/ru/articles/161257/ И там даже МК восьмибитный,а не STM32 какой-нибудь.

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

Смотрел я на эти проекты, к сожалению этого нету, хотя шумиха такая будто >уже пол ядра переписали.

А вот ядра на ADA вполне себе уже есть. В том числе знаю два открытых: https://ironclad.nongnu.org https://muen.codelabs.ch Закрытые тоже есть но они закрытые поэтому нам малоинтересны.

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

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

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

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

watchcat382
()