LINUX.ORG.RU

Навеяно свежей дырой в Xorg

 , ,


9

7

Привет, ЛОР!

Ты, наверное, уже видел свежую дыру в Xorg, патч для которой выглядит буквально вот так:

-        else
+        else {
             free(to->button->xkb_acts);
+            to->button->xkb_acts = NULL;
+        }

В связи с этим у меня возник вопрос: а почему в стандартной библиотеке C нет макроса SAFE_FREE()?

#define SAFE_FREE(ptr) do{free(ptr);(ptr)=NULL;}while(0)

Напомню, что значение указателя после вызова free() является неопределённым согласно стандарту. Не только значение памяти, на которое он указывает, но и значение самого указателя, и работа с ним представляет собой жуткое undefined behaviour, а значит единственное что можно сделать – занулить его.

Так вот, почему даже таких банальных вещей нет? Я уже не говорю про строковый тип, а то даже Эдичка тут строки не осилил.

Моя гипотеза тут: C – это язык культа страданий во имя страданий.

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

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

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

Отладки чего?

Кода, конечно. Падающего кода, например.

И почему нельзя печатать указатель до вызова free?

Например, потому что free происходит где-то внутри вызываемых функций, и я могу об этом заранее не знать (или это самое удобное место, куда я могу воткнуть отладочную печать)

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

Так это C, тут любое UB чревато «неизвестно чем». К тому же, если у указателя есть копии, в них значение адреса не изменится, что только прибавит гемора разработчику, отлаживающему код.

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

Например, потому что free происходит где-то внутри вызываемых функций, и я могу об этом заранее не знать (или это самое удобное место, куда я могу воткнуть отладочную печать)

Отлично. Что плохого будет, если при вызове free() указатель сразу же превратится в NULL и как это помешает твоей теоретической отладке? Ну кроме того, что возможно твой код перестанет разыменовывать невалидные указатели и падать.

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

А потом ты покупаешь автомобиль, контроллер в котором программировал вот такой вот firkax, и он убивает тебя об стену.

https://www.edn.com/toyotas-killer-firmware-bad-design-and-its-consequences/

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

Не изменится, потому что каждая единица трансляции компилируется отдельно, и компилятор не может знать, что там в другом файле free освободил и когда. А если это зависит от каких-то рантаймовых условий, то это даже с LTO нельзя будет осуществить.

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

Что плохого будет, если при вызове free() указатель сразу же превратится в NULL и как это помешает твоей теоретической отладке?

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

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

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

А это гарантировано стандартом? Или в стандарте это обозначено как UB, следовательно где-то падения программы может и не быть?

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

А так есть asan и ubsan, которые в меру возможностей, пытаются в рантайме такие штуки отслеживать, замедляя работу программы в N раз

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

Провел эксперимент. Пока был ненулевой указатель, было так:

 int *j = (int*)malloc(1024);
    printf("%p\n", j);
    free(j);
    printf("%p\n", j);
    printf("%d\n", *j);
    return 0;

Вывод программы: 
0x562f5804c2a0
0x562f5804c2a0
1660256332
После обнуления j стало так:
int *j = (int *)malloc(1024);
    printf("%p\n", j);
    free(j);
    printf("%p\n", j);
    j = NULL;
    printf("%d\n", *j);
    return 0;

Вывод: 
0x5585516be2a0
0x5585516be2a0
Ошибка сегментирования (образ памяти сброшен на диск)

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

Идея в том, что падения под таким instrumentation не будут противоречить стандарту, ни у кого не будет права вижжать что «ну я же читаю значение не из того указателя, который я совал во free, а копию, с ним ничего не должно было случиться!»

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

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

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

У тебя один указатель занулился, а остальные нет. Это делает поведение неконсистентым, при этом что-то улучшается только для самого простейшего случая use-after-free, который и так найдет любой статический анализатор (и опытный глаз)

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

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

Это широко распространенный паттерн, например для древовидных структур. При правильном порядке удаления объектов все корректно.

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

Это в стандарте

Что, так прямо и написано? Что-то вроде «an attempt to dereference a null pointer causes the termination of the program»?

Даже в bare metal переход к NULL - остановка или перезапуск железа.

А как же стандарт в таком случае?

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

Идея в том, что падения под таким instrumentation не будут противоречить стандарту, ни у кого не будет права вижжать что «ну я же читаю значение не из того указателя, который я совал во free, а копию, с ним ничего не должно было случиться!»

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

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

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

И в капиталистической системе, делающей такую организацию допустимой и даже выгодной

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

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

© Бойцовский клуб

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

Дерево – это один объект, считай.

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

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

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

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

Что, так прямо и написано? Что-то вроде «an attempt to dereference a null pointer causes the termination of the program»?

Почти. Там написано, что это UB и корректная программа такого не делает.

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

Причина не в быдлокодере, а в организации, которая наняла этого быдлокодера. Быдлокодеры на C никогда не виноваты. Они всегда пишут безупречный код, который не падает и не просирает стэк.

Знаем, проходили. А потом смотришь в код таких сишников, а так у каждого второго дыра на дыре.

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

Если написано UB, то прерывание программы при обращению по нулевому указателю – это всего лишь приятные плюшки от конкретной реализации, а никакие не гарантии. И вот это вот утверждение от LongLiveUbuntu:

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

всего лишь благие пожелания.

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

А это гарантировано стандартом? Или в стандарте это обозначено как UB, следовательно где-то падения программы может и не быть?

Прикольно. Погуглил, оказывается в С++ можно разыменовывать нулевой указатель. Я думал нельзя. Главное не записывать и не читать ничего из этой памяти.

Вот что я нашёл: cwg232

We agreed that the approach in the standard seems okay: p = 0; *p; is not inherently an error. An lvalue-to-rvalue conversion would give it undefined behavior.

не падает программа: https://gcc.godbolt.org/z/zvxrW9qcM

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

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

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

Если написано UB, то прерывание программы при обращению по нулевому указателю – это всего лишь приятные плюшки от конкретной реализации, а никакие не гарантии.

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

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

Именно. И ещё приняла этот говнокод в работу.

Дык этот говнокод принял такой же говнокодер тимлид. Думаешь, это всё из воздуха берётся? Сишная культура говнокода – это рак, который надо выжигать калёным железом.

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

не падает программа

А с чего ей падать - компилятор просто выкинул стейтмент *p;, не имеющий никаких эффектов. Сегфолт может появиться только при загрузке памяти по адресу, только тогда ноль попадет на MMU и вызовет исключение

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

malloc округляет запрошенный размер до размера структуры свободного блока. Так что у free не будет проблем положить туда свои данные при освобождении. Но пока он их туда не положил, мы можем дать юзеру использовать эту память в своих целях вместо резервирования её.

Пусть адрес 4 байта, структура занятого блока 4 байта (только размер), структура свободного блока 8 байт (размер и указатель на следующий свободный блок). Юзер запросил 1 байт, округляем до (8 - 4) = 4 (разница между свободным и занятым заголовком блока), прибавляем 4 (размер занятого заголовка), снова округляем уже до 8 (размер свободного заголовка). В итоге реально выделяем 8 байт, инициализируем заголовок занятого блока, возвращаем юзеру базовый адрес + 4. При освобождении читаем заголовок занятого блока и инициализируем заголовок свободного, мы знаем, что блоков меньше реальных 8 байт не существует, поэтому можем свободно инициализировать структуру большего размера, чем заголовок занятого блока. При этом мы портим тот самый 1 байт, которые там хранил пользователь, но мы имеем на это право - после free пользователь не должен обращаться к блоку по контракту этой функции. Если этот блок снова выделяется malloc, то он удаляется из списка свободных и вновь инициализируется маленький заголовок занятого блока. Теперь эти вторые 4 байта блока вновь не используются менеджером и юзер снова может в них писать.

В реальном менеджере есть несколько граничных случаев. Malloc может не только вернуть исходно свободный блок на 8 байт, но и отрезать 8 байт от свободного блока большего размера, однако из-за округление всех размеров до 8, остаток после отрезания гарантированно сохранит кратность 8. Free может не только создать свободный блок, но и приклеить к смежному, если он есть, но склеивание двух блоков с размером кратным 8 гарантированно породит блок размера кратного 8. Здесь тоже никаких проблем.

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

Нет конечно. Это общее место индустрии софта. Все лепят MVP и у всех отказ от ответственности в лицензиях.

И к используемому ЯП это никакого отношения не имеет.

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

Все лепят MVP и у всех отказ от ответственности в лицензиях.

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

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