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

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

От этого тебя спасет компилятор, выкинув ворнинг. SAFE_FREE спасет тебя вот от такого:

thread1:
  mutex_lock(...);
  SAFE_FREE(cfg->some_list);
  mutex_unlock(...);

thread2:
  mutex_lock();
  cfg->some_list[i];
  mutex_unlock(...);

Ну как спасет – ты упадешь, но эта срань не доедет до продакшона. А если доедет, то тоже упадет, не сотворив дичи.

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

Кстати, давным-давно на MacOS Classic было так:

int **p, **q;
p = getmem(...);
q = p;
memfree(p);
**q = 1; // падает, так как в *q стоит NULL

Но цена - лишний косвенный доступ на каждую операцию. И случайно сделав повторный free я убил систему (пришлось переустанавливать).

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

От этого тебя спасет компилятор, выкинув ворнинг.

В простых случаях выкинет, в случае

thread1:
  mutex_lock(...);
  SAFE_FREE(cfg->some_list[i]);
  mutex_unlock(...);

thread2:
  mutex_lock();
  cfg->some_list[j] = 1;
  mutex_unlock(...);

ничего не выкинет, так как при компиляции он не выведет, что в этот момент i и j были одинаковы.

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

Да нет, он верен для огромного количества конфигураций.

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

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

Так C это минное поле из ада и страданий. stack протектор работает не во всех случаях, проверка границ работает только для некоторых функций, memory ordering страшен как ночной кошмар. Разумеется зануление указателей не решит всех проблем, но решит ОГРОМНУЮ их часть.

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

Разумеется зануление указателей не решит всех проблем, но решит ОГРОМНУЮ их часть.

Такое решение может понизить общую безопасность, потому что программист начнёт считать, что попытка разыменования безопасна.

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

Ты сейчас изгаляешься и ищешь самые упоротые варианты.

Нет. Намекаю, что не нужно пытаться лечить симптомы.

Хотя большая часть проблем с указателями в моей практике становится видимой при их занулении.

Вам очень повезло.

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

А сейчас программист так не считает? И что он делает? Молится перед каждым разыменованием и вставляет комменты с «Отче наш» перед ними?

Ей богу, я не понимаю этого аргумента, он какой-то совсем тупой ну.

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

давным-давно на MacOS Classic

В MacOS Classic не было защиты памяти. Я вообще не представляю, как эта система до 2002 дотянула и почему эту говнину раньше не закопали.

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

А проще взять лисп или Java.

Ну елки же палки. Не проще. Там, где нужна предсказуемость и контроль за ресурсами/скоростью, безопасные языки, вроде Lisp или Java либо не используются, либо превращаются в жалкое подобие небезопасных языков.

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

И Rust и C++ ограничивают зону UB

C++ никак не ограничивает зону UB. Хуже того, он добавляет его ЕЩЁ БОЛЬШЕ.

А вот Rust да, ограничивает всё на отличненько. Что бы ты за ересь тут не писал.

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

А сейчас программист так не считает?

Да.

Молится перед каждым разыменованием и вставляет комменты с «Отче наш» перед ними?

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

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

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

АХАХАХАХАХАХАХАХАХАХАХАХАХАХ

УААХАХАХАХАХАХАХАХАХАХАХАХАХ

ОХОХОХОХОХХОХОХООХОХОХОХОХОХ

Ты серьёзно? Ты давно вообще сишный код видел-то?

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

Так я в начале этой ветки и ответил, что Си (и Си++) используются там, где нужна производительность.

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

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

C++ никак не ограничивает зону UB. Хуже того, он добавляет его ЕЩЁ БОЛЬШЕ.

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

А вот Rust да, ограничивает всё на отличненько.

Только до тех пор, пока не используешь unsafe.

Проблема и С++ и Rust в том, что не используя unsafe или опасные операции (включая библиотеки), часть алгоритмов написать невозможно. Но ограничение позволяет надеяться, что библиотеки достаточно протестированы.

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

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

Да, только старое говно никуда не делось. В этом проблема C++ – он пытается быть обратно совместим с C.

Только до тех пор, пока не используешь unsafe.

Нет. Даже в блоках unsafe{} в Rust нет такого безумия, как UB в C. Например, можно распечатать указатель на освобождённую память. Да и проверки на NULL никто не выкидывает.

Писал лет десять назад. С тех пор что-то сильно поменялось?

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

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

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

Вопрос только в цене. Мне доводилось видеть Java для JavaCart/SmartCart образца 2001-го года. Даже чистая ламповая сишечка была бы удобнее.

Ну и если почитать как народ парится с off-heap структурами данных в Java в попытках использовать Java для real-time проектов, то дешево это точно не выглядит.

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

Вот Rust, пожалуй, наиболее известная и успешная попытка вклинится в нишу C/C++/Ada с обеспечением больших гарантий безопасности, чем в C и C++.

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

Нет. Даже в блоках unsafe{} в Rust нет такого безумия, как UB в C. Например, можно распечатать указатель на освобождённую память.

Сейчас и в C можно распечатать. А будет новый LLVM учитывать оптимизацию при чтении освобождённого указателя, она и Rust затронет. Выбор LLVM влечёт за собой багосовместимость с C/C++.

Как говнокодили без лишних мыслей в головах, так и говнокодят.

И оно после этого работает? Мне казалось, говнокодеры давно свалили на что-нибудь, что не выдаёт на говнокод совершенно произвольный результат.

70% дыр в сишном и плюсовом коде всё ещё вызваны проблемами с управлением памятью.

Это дыр. Дыры чаще связаны не с UB, а с тем, что разработчик не учёл какой-то вариант входных данных и не предполагал намеренно злобного пользователя.

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

Вопрос только в цене.

Я про предсказуемость, а не про производительность. Если в рабочем цикле создаётся/удаляется примерно одинаковое число объектов, то сборщик мусора отрабатывает за одинаковое время.

А если очень надо, то можно и жёсткое реальное время сделать: https://www.irisa.fr/prive/talpin/papers/fidji03.pdf

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

Вот Rust, пожалуй, наиболее известная и успешная попытка вклинится в нишу C/C++/Ada с обеспечением больших гарантий безопасности, чем в C и C++.

Согласен.

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

А будет новый LLVM учитывать оптимизацию при чтении освобождённого указателя, она и Rust затронет. Выбор LLVM влечёт за собой багосовместимость с C/C++.

Што? Нет, не влечёт. То, что LLVM написан на плюсах, нифига не означает, что весь код, который он генерит, тоже на плюсах или как-то подчиняется плюсовым правилам по UB. Не пиши уж полной хероты тут, пожалуйста. Все эти преобразования кода вокруг UB происходят во фронте компилятора, то есть в Clang. LLVM – это бэкенд, он про это всё знать не знает.

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

То, что LLVM написан на плюсах, нифига не означает, что весь код, который он генерит, тоже на плюсах или как-то подчиняется плюсовым правилам по UB.

Почитай обсуждение к https://github.com/rust-lang/rust/issues/45839 или https://github.com/oberien/str-concat/issues/8

Все UB из C++ протекают в Rust unsafe. А так как он массово используется в стандартной библиотеке, то практически в весь используемый код.

Все эти преобразования кода вокруг UB происходят во фронте компилятора

Тогда почему UB в Rust unsafe идентичны C++ из актуального clang?

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

Тогда почему UB в Rust unsafe идентичны C++ из актуального clang?

Они не идентичны. То, что ты притащил по ссылкам, это именно что баги либо в Rust, либо в LLVM. Там буквально так и написано: «Safe Rust code miscompilation due to a bug in LLVM’s Global Value Numbering».

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

Это rust’овцы назвали багом. А по факту это как раз UB.

В примере в issue для LLVM стоит

p = &y+1;

где y не массив.

let y = 6666;
p = (&y as *const _).wrapping_offset(1);

такое же UB, унаследованное из Си.

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

А по факту это как раз UB.

Это именно что баг, а не документированный случай «АТАТАТ ТАК НЕ ДЕЛАТЬ». И когда-нибудь его починят. Что ж поделать, если сишное наследие залезло настолько глубоко, что его ещё десятилетиями придётся выпиливать?

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

Это именно что баг, а не документированный случай «АТАТАТ ТАК НЕ ДЕЛАТЬ».

В стандарте Си написано, что

int y = 6666;
p = &y+1;
// что-то делаем с p

является UB.

В комментарии к «багу» и написано, что

Your claim that this result is not allowed is ... not clearly right :)

IE i'd stick with language that says "we don't want this to be right".

Проблема только в том, что LLVM использует UB из C/C++ для оптимизации, но не указывает этого явно в документации.

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

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

Да блин, ничего они не позволяют гарантировать. У нас в проекте сплошные смартпойнтеры, а багов этих выловили немеряно. Потому что и библиотеки, и асинхронность, и многопоток. И ты не выкинешь сырые указатели, даже core guidelines от этого предостерегают.

Намного меньше багов стало, гарантий - не было и нет.

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

У нас в проекте сплошные смартпойнтеры, а багов этих выловили немеряно.

А можно пример, как сломать STL-ный смартпоинтер?

И ты не выкинешь сырые указатели, даже core guidelines от этого предостерегают.

Так и в Rust не выкинешь unsafe.

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

А можно пример, как сломать STL-ный смартпоинтер?

Взять от него get. Например, передать в библиотеку. И например, библиотека по событию туда полезет.

Так и в Rust не выкинешь unsafe.

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

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

Взять от него get.

Такое случайно не сделаешь.

В плюсах базовые вещи делаются указателями.

Обычно не указателями, а ссылками.

Умный указатель используется там, где объект хранится/создаётся/уничтожается. Что позволяет ликвидировать как проблему использования указателя после освобождения, так и двойного освобождения. А в функции передаётся по ссылке. И функция при этом не может случайно освободить из под него память.

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

Да и ссылку на core guidelines уже приложили.

По ним сырой указатель может прийти только аргументом функции. И не может быть в этой функции освобождён.

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

Такое случайно не сделаешь

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

Пример похитрее показывал Саттер на конфе, тут можно глянуть на 9-й странице. Фиг отследишь в реальном коде. На конфе вот никто не увидел, пока не показали.

Сырые указатели в Си++ тоже

Ну, если локацией считать весь код…

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

Такое специально приходится делать, и нередко. А дальше на сложном проекте ты просто не заметишь, как этот указатель куда-то сохранится.

Вот это и есть unsafe.

можно глянуть на 9-й странице

Да, это интересный пример. Но тоже достаточно простое правило.

Ну, если локацией считать весь код…

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

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

monk ★★★★★
()

Ты ещё предложи пристёгиваться. Нормальному сишнику это всё претит. Он ошибок не допускает. А если и допустит то ничего страшного. Поправит и всё.

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

Но тоже достаточно простое правило.

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

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

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

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

Первый случай безопасен

… если не делать ошибок. См. параграф 1.

Пример из жизни: QObject::connect. Принимает сырые указатели, сохраняет. Достаточно крови попортило.

второй как unsafe требует всегда внимательного рассмотрения

Я Rust не знаю, но все еще уверен, что unsafe там гораздо реже, и его легче найти и ревьюить.

unsigned ★★★★
()