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 – это язык культа страданий во имя страданий.

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

Я хочу услышать, почему нельзя распечатать указатель после вызова free() на него. Стандарт C говорит, что это UB. Чем это обусловлено, кроме того что когда-то существовали платформы, где такое могло закончиться плохо?

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

Секьюрность ломает

Reading a pointer to deallocated memory is undefined behavior because the pointer value is indeterminate and might be a trap representation. When reading from or writing to freed memory does not cause a trap, it may corrupt the underlying data structures that manage the heap in a manner that can be exploited to execute arbitrary code.

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

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

...
free(ptr);

uintptr_t copy = (uintptr_t) ptr;
...

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

но clang разрешает ) зайка )

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

The kernel panicked when accessing freed memory

Ну то есть там было примерно такое:

char *ptr = ...;
free(ptr);
ptr = 0xDEADBEEF;
putchar(*ptr);

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

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

Да, для сравнения с NULL там отдельный пункт в другой части стандарта.

Если для сравнения с NULL есть правило, то тогда почти что норм.

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

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

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

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

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

Можно, скастовав оба в uintptr_t.

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

Нет, я хочу чтобы ты мне объяснил это. Ты же написал, что в Rust просто до этого UB не дошли пока что (наверное, подразумевая, что дойдут).

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

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

Так в них тоже есть UB.

Я про другое: есть и прочие факторы, по которым Java не выбирается. Ни скорость, ни UB к этому не имеют отношение.

Так что C++ таки нужно сравнивать с языками с этой же поляны.

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

а теперь представь себе абстрактную машину, которая как родная ложится на 8086. и как она будет ложиться на, скажем, arch64? )

Например, с наличием сегментной адресации. Для arch64 будет просто один сегмент.

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

m68k

There is the possibility of pointers in segmented architectures in which reading a pointer value does some dynamic check. Derek Jones reported that original 68000 did this. It would be useful to know if there are current implementations that do.

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

Всем насрать. Оно сдохло, новых процессоров никто не делает, старых днём с огнём не сыщешь.

Если язык сейчас умеет в x86, arm и riscv, этого более чем достаточно.

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

подумалось тут внезапно - наверное было бы правильно, если бы канпелятор под конкретную архитектуру вообще обходился бы без UB, определял бы поведение. про конкретную железяку все ж известно. один хрен при портировании без #ifdef-ов не обойдешься. так пожалуй было бы правильно, ну нахрена мне эти пляски на amd64 или aarch вокруг типа нечитаемых указателей. все читается, унесите это в закат.

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

Ну дык да. В Rust и Haskell в этом плане классно сделано с Safe и Unsafe подмножествами языка. Можно всё странное и опасное огородить и отлично протестировать, выставив наружу безопасный API.

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

если бы канпелятор под конкретную архитектуру вообще обходился бы без UB

И что он должен делать при разыменовании указателя на освобождённую память? На каждый *p делать проверку по доступным областям памяти?

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

Safe может быть (только) при автоматическом управлении памятью. То есть либо сборщик мусора как в Haskell/Lisp либо отслеживание времени жизни как в Rust. Но вариант Rust не универсален, поэтому на Haskell или Lisp можно строить произвольные структуры и алгоритмы без unsafe, а на Rust приходится пользоваться unsafe или библиотеками с unsafe просто для описания структур, в которых данные имеют нетривиальное время жизни.

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

Safe может быть (только) при автоматическом управлении памятью.

Во-первых, нет. Во-вторых, Safe Haskell – это не только и не столько про память.

поэтому на Haskell или Lisp можно строить произвольные структуры и алгоритмы без unsafe

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

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

И что он должен делать при разыменовании указателя на освобождённую память? На каждый *p делать проверку по доступным областям памяти?

Опять двадцать пять. При разыменовании указателя на освобождённую память можно гарантированно падать. UB – это совершенно другая штука.

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

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

вот спасибо.

вы как будете писать менеджер кучи, что как раз работает с «указателями на освобожденную память»?

или сборщики мусора, или какие-то санитайзеры и проч.

вы смотрите на проблему чисто с позиций прикладника.

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

даю вам еще повод пометать критические стрелы, по имени union. обращение к union с неверными ожиданиями о там находящемся значении - не менее катастрофично, а может и более, чем «разыменовывание указателя на освобожденную память».

ваши предложения по union?

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

вот тебе и чо… менеджер кучи прекрасно работает с указателями на «освобожденную память» поскольку это и есть его основная работа.

Нет, не работает. Аллокатор не использует функции malloc() и free(), а реализует их. Это вообще отдельная тема.

Но если хочешь лулзов, то да, саму libc на C без UB никак не реализовать. Одна только функция syscall() чего стоит.

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

Нет, не работает. Аллокатор не использует функции malloc() и free(), а реализует их. Это вообще отдельная тема.

а причем тут «использование»? прикладной код вызывает dealloc на указатель и ты предлагаешь ему потом падать при разыменовании..

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

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

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

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

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

Ручного освобождения памяти там нет. Там внутри обычный ForeignPtr.

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

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

Как? Вот у тебя есть кусок кода

void f(int *p)
{
  *p = 1;
}

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

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

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)