LINUX.ORG.RU
ФорумTalks

В Xorg обнаружена пачка уязвимостей возрастом от 25 до 35 лет

 , , ,


0

4

Выход за границы в коде libX11, датированном 1996 годом; исчерпание стека в коде функции PutSubImage() от февраля 1988 года; целочисленное переполнение внутри XCreateImage() того же года; два выхода за границы в коде libXpm от 1998 года.

https://www.phoronix.com/news/XOrg-Vulnerabilities-Since-1988

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

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

Сам влез со статическим анализом для переполнения, а теперь что-то требуешь от меня.

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

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

Во первых все эти ошибки легко не делать.

Почему тогда их постоянно находят в крупных проектах? Тысячи глаз же.

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

Что ты собрался проверять? Сколько байт осталось до границы стека? На сишке тебе жопу оторвёт при переполнении стека. Ничего ты уже не сможешь проверить.

Или что пользовательский ввод надо проверять?

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

Или про размерности у типов?

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

Во вторых coverity всё что было перечислено прекрасно детектирует.

Твои маняврирования кормой меня забавляют. Специально выбрал недоступное обычному человеку проприетарное дерьмо в надежде, что я поверю на слово? Но тебе не повезло. Они в целях рекламы делают анализ открытых проектов и можно посмотреть отчёт и увидеть что там детектируется, а что нет. Вот ссылочка на проверку glibc от 4 мая 2017 года

Теперь берём уже известную уязвимость, которая существовала на момент анализа и не была исправлена. Например про переполнение Fix integer overflows in internal memalign and malloc functions: CVE-2018-6485. Вот diff исправления этой уязвимости.

Теперь смотрим, что на эту строчку выдаёт твоё проприетарное говно, которое по-твоему я цитирую «всё что было перечислено прекрасно детектирует».

Скрин из отчёта coverity. Как видишь ничего он не нашёл здесь. Можешь сравнить с diff исправления. Там прямо перед вызовом функции вонзили проверку на переполнение.

+  /* Check for overflow.  */
+  if (nb > SIZE_MAX - alignment - MINSIZE)
+    {
+      __set_errno (ENOMEM);
+      return 0;
+    }
+
   /* Call malloc with worst case padding to hit alignment. */
 
   m = (char *) (_int_malloc (av, nb + alignment + MINSIZE));

Для примера как выглядит найденная ошибка в этом отчёте.

Я ещё проверил эту уязвимость. Анализатор тоже ничего не нашёл.

Мне от идиотов уже давно ничего не надо.

Хам. В зеркало посмотри. Я тебе железобетонные пруфы предоставил, где твой coverity обосрался. А от тебя только пустые сотрясания воздуха в стиле «да он там всё найдёт, зуб даю».

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

А как дела у вой-ланд-хлёбов?

Так они неподвержены.

xenocara

Был бы это самостоятельный проект. Но они же просто сосут код из X.Org, а безопасники проверяют-то именно X.Org, а не Патриком забытую xenocara’у.

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

Там прямо перед вызовом функции вонзили проверку на переполнение.

Мда и всё равно сделали плохо. Надо было в комментарии указать, что, поскольку alignment это степень двойки (это видно из другого кода, который в diff-е не участвует), он не может стать больше чем SIZE_MAX-MINSIZE и соответственно не надо проверять на переполнение это вычитание. Судя по отсутствию комментария (но при этом «Check for overflow.» они вписать не забыли) - вполне можно предположить что они об этом просто не подумали и, не будь такого внешнего ограничения на alignment, получили бы всё то же переполнение.

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

+  size_t aligned_nb;
.....
+  aligned_nb = nb + alignment + MINSIZE;
+  if (aligned_nb <= nb)
+    {
+      __set_errno (ENOMEM);
+      return 0;
+    }
+
   /* Call malloc with worst case padding to hit alignment. */
 
-  m = (char *) (_int_malloc (av, nb + alignment + MINSIZE));
+  m = (char *) (_int_malloc (av, aligned_nb));
Разница с их вариантом: мы не используем какую-то магическую формулу с SIZE_MAX, которая не нужна ни для чего, кроме проверки, и над смыслом которой надо хоть чуть-чуть а подумать, а вставляем проверку непосредственно точечным сравнением двух чисел, которые у нас и так есть (ни одно из них не надо вычислять ради проверки), избегаем зеркально-дублирования кода (а вдруг мы формулу в скобках malloc() изменим? опять думать как ещё и проверку менять) и повышаем общую прозрачность логики.

И вот такие if-ы должны быть не каким-то отдельным действием в логике работы функции, которое они как «check for overflow» расписали, а автоматически (т.е. не требуя очевидных объяснений) сопровождать любую арифметику, если нет 100% уверенности что переполнения тут быть не может. Если есть опасение что может переполниться alignment+MINSIZE - их надо аналогично проверить.

Скрин из отчёта coverity. Как видишь ничего он не нашёл здесь.

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

Во первых все эти ошибки легко не делать.

Почему тогда их постоянно находят в крупных проектах? Тысячи глаз же.

А, ну и да: программист, который это (malloc с формулой из сложений, умножений и/или вычитаний в качестве аргумента (деления можно), к которой рядом нет доказательства её безопасности) написал - профнепригоден, по крайней мере в сфере системного программирования. Это действительно легко НЕ делать.

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

Верно, но иксы всё-таки не бизнесмены лично писали, а программисты (хоть, возможно, и нанятые бизнесменами). В те времена сложнее было нанять «абы кого лишь бы подешевле».

Си кто писал? Питон кто писал? С++ кто писал? Ты мне хочешь сказать, что это были «другие люди»? А я вижу тех же бездарей. Ведь кто-то придумал писать на Си ответственный софт, даже Oracle написали на Си — кто были эти люди? Что ими двигало? Может и не «подешевле», но профпригодность сильно под вопросом.

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

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

Еще легче никогда не писать софт, чуть сложнее писать его и не пытаться следить за багами после релиза — потому что в противном случае написать сложный софт на Си без уязвимостей и просто повреждения памяти ПРАКТИЧЕСКИ НЕВОЗМОЖНО. С анализаторами и без.

Во вторых coverity всё что было перечислено прекрасно детектирует.

Ни один статический анализатор не может оценить достаточно сложный и динамический алгоритм.

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

Что вы писали на си и какими статическими анализаторами пользовались? Это очень простые вопросы подразумевающие очень простые ответы.

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

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

Уязвимость в libcue, приводящая к выполнению кода при загрузке файлов в GNOME

Уязвимость вызвана целочисленным переполнением в коде разбора синтаксиса параметра INDEX и проявляется при указании в данном параметре слишком больших числовых значений, не укладывающихся в тип «int».

Ахах. И недели не прошло. Как же так? Надо написать им про coverity. А то не знают поди. Мучаются от переполнений. Бгг.

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

Да. А ещё хватило бы одного if, раз уж в арифметике косячат.

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

Какими статическими анализаторами пользовались?

cppchecksolutions.com, шланговым, и обычными детализованными ворнингами GCC.

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

в рантайме valgrind беспопощен, как и в PSO — выдает очень много ошибок, которые не ошибки, потому что программа многопоточна и имеет асинхронный ввод-вывод.

У Valgrind нет проблем с многопоточными программами, но он сериализует исполнение нитей, так что в каждый момент времени исполняется только одна. Многопоточность не вызывает ложных срабатываний. Асинхронный ввод-вывод это тоже не какое-то волшебство, которое внезапно ломает Valgrind. Так что если инструменты Valgrind выдают много ошибок, дело в чём-то другом.

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

У Valgrind нет проблем с многопоточными программами, но он сериализует исполнение нитей, так что в каждый момент времени исполняется только одна. Многопоточность не вызывает ложных срабатываний. Так что если инструменты Valgrind выдают много ошибок, дело в чём-то другом.

Если «потоки» оформлены в виде разных процессов — еще и как ломает. Есть всякие там DRD, который позволяет обнаруживать гонки в многопотоке, но это совсем не гарантия отсутствия ошибок.

Асинхронный ввод-вывод это тоже не какое-то волшебство, которое внезапно ломает Valgrind.

Совсем недавно valgrind не умел переваривать io_uring, хотя это был самый стандартный линуксовый ввод-вывод.

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

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

Если «потоки» оформлены в виде разных процессов — еще и как ломает.

Это как вообще? И в чём заключается поломка?

Совсем недавно valgrind не умел переваривать io_uring

О, конкретика. Да, тут спорить не о чём, Valgrind до сих пор толком не поддерживает io_uring, есть серьёзные проблемы. Но это не просто абстрактное асинхронное IO, это конкретный набор системных вызовов, особенности которого не поддерживаются. Если в Valgrind нет модели поведения вызова, он не может знать, какую память ядро инициализировало, поэтому она вся считается неинициализированной, и поэтому Memcheck будет ругаться. С сигналами там тоже какая-то беда, потому что точка входа в io_uring их тоже задевает. Но это всё конкретные проблемы с конкретными системными вызовами, а не с асинхронным IO и многопоточностью вообще.

Я прежде всего отвечал на то, что «просто пользуйся анализаторами — и у тебя не будет багов», что, по-хорошему, есть вопиющее 4.2

А я отвечал на «Valgrind выдаёт много ложных ошибок», что являлось распространённым мифом даже задолго до появления io_uring и связанных с ним проблем. Не от одного человека такое слышал. Вот откуда этот миф растёт, очень интересно.

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

Если «потоки» оформлены в виде разных процессов — еще и как ломает.

Это как вообще? И в чём заключается поломка?

Другой поток переставил указатели, а текущий об этом совсем не в курсе. Не говоря уже о том, что у меня вообще была кастомная трансляция указателей, которую valgrind без доработок не понимает вообще.

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

Конкретных «моделей поведения вызова» теоретически может быть бесконечно число. Да, их можно каждую прикручивать к валгринду снова и снова, а потом снова появятся новые, не поддерживаемые. Смысл в том, что Valgrind изначально был ориентирован на однопоточный однозадачный синхронный код, только на нем он хорошо работает, всё остальное — уже через костыли.

А я отвечал на «Valgrind выдаёт много ложных ошибок», что являлось распространённым мифом даже задолго до появления io_uring и связанных с ним проблем. Не от одного человека такое слышал. Вот откуда этот миф растёт, очень интересно.

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

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

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

Что непрозрачно намекает что у вас не просто сильно, а очень сильно всё запущено. Без обид.

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

Что непрозрачно намекает что у вас не просто сильно, а очень сильно всё запущено. Без обид.

Или что комментаторы в этом треде пишут однопоточный код с синхронным вводом-выводом, ну максимум какой-нибудь select/epoll.

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

однопоточный код

Скажем так - тредов форкаем действительно по минимуму. Ни к чему оно.

с синхронным вводом

Почти весь IO - async, но Вы наверное другое имели в виду.

Вы хотите сказать что valgrind в много-потоке ненадёжен?

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

Почти весь IO - async, но Вы наверное другое имели в виду.

В ядре — да, в userspace у POSIX есть каноничные синхронные функции ввода-вывода, которые можно условно представить как «sleep»+«атомарный ввод-вывод». Если начинаются Linux-only, вроде vmsplice с SPLICE_F_GIFT, то valgrind должен быть адаптирован конкретно под этот вызов. Но алгоритмы асинхронного ввод-вывод и/или IPC могут быть целиком в userspace, valgrind в принципе заранее не может знать, как оно работает и как вызовы можно условно упростить.

Вы хотите сказать что valgrind в много-потоке ненадёжен?

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

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

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

Я хочу сказать, что многопоток точно не является сильной стороной valgrind.

Я готов предположить что это специфика нашего codebase, но ложных срабатываний valgrind я пока не видел. Возможно I just got lucky…

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

Другой поток переставил указатели, а текущий об этом совсем не в курсе.

Перестановка указателей внезапно делает доступ к освобождённой памяти корректным, а memcheck ругается зазря? Или доступ к неинициализированной памяти внезапно становится корректным? Я никак не понимаю логики.

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

Если, скажем, в младших битах указателя хранить флаги, то в memcheck может поломаться перечисление не освобождённой, но достижимой памяти в момент завершения программы. Но вот как это поломает отлавливание use-after-free, я никак не могу понять. Разве что ты на ходу glibc в ОЗУ патчишь?

Смысл в том, что Valgrind изначально был ориентирован на однопоточный однозадачный синхронный код, только на нем он хорошо работает, всё остальное — уже через костыли.

Я успешно использовал Valgrind в процессе, где крутился флеш плеер и процесс-хост от огнелиса. Там постоянно много потоков, которые что-то делают, я ещё несколько потоков запускал, которые тоже что-то делали. Не похоже на однопоточный синхронный код, знаешь ли.

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

Возможно I just got lucky…

Да нормально он работает. Просто эти «в Valgrind постоянные ложные срабатывания» это какое-то развитие «баг не у меня, а в компиляторе».

Ну а крайние ситуации, когда проблемы действительно в Valgrind или компиляторе, заставляют суровых сибирских мужиков говорить «ага!» и идти дальше рубить дрова по-старинке.

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

Перестановка указателей внезапно делает доступ к освобождённой памяти корректным, а memcheck ругается зазря? Или доступ к неинициализированной памяти внезапно становится корректным?

Как memcheck определяет доступ к освобожденной памяти или неинициализованность? Ладно с локальными переменными всё более-менее ясно, а вот что делать с динамически выделенной? В memcheck есть модель для malloc/free, есть чуть более чем ничего в попытке понять кастомный аллокатор, подчиняющийся некоторому протоколу (то есть, алокатор в любом случае придется адаптировать к valgrind).

Если я переиспользую блок памяти, а memcheck не понял, что эта последовательность действий значила «переиспользование» — вуаля, у меня use-after-free или доступ к неинициализированным данным, которые memcheck не может распознать. И наоборот, если я делаю какие-то хитрые указатели, вроде деления int64 на два int32 — memcheck не находит указателя на память, в его понимании память пропала. Примерно тот же эффект будет, если другой процесс параллельно «потребит» указатель на память — указателя нет в памяти текущего процесса, формально для memcheck память утекла, фактически на нее есть ссылка в другом процессе (это примерно то, что у нас происходит).

Если, скажем, в младших битах указателя хранить флаги, то в memcheck может поломаться перечисление не освобождённой, но достижимой памяти в момент завершения программы. Но вот как это поломает отлавливание use-after-free, я никак не могу понять.

Точка free хорошо отслеживается только на более-менее стандартном аллокаторе.

Я успешно использовал Valgrind в процессе, где крутился флеш плеер и процесс-хост от огнелиса. Там постоянно много потоков, которые что-то делают, я ещё несколько потоков запускал, которые тоже что-то делали. Не похоже на однопоточный синхронный код, знаешь ли.

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

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

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

Я готов предположить что это специфика нашего codebase, но ложных срабатываний valgrind я пока не видел. Возможно I just got lucky…

И не было багов, которые valgrind пропустил? Я не поверю.

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

И не было багов, которые valgrind пропустил?

Ошибок типа «uninitialized-memory-read» и «use-after-free» на моей практике - нет, не пропускал. Остальное - не его работа.

Я не поверю.

Дело хозяйское.

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

если я делаю какие-то хитрые указатели, вроде деления int64 на два int32 — memcheck не находит

Неправда. Именно поэтому report и генерится на выходе.

ПыСы. Вы помните признаки сходимости рядов от Даламбера и Коши? Я к чему. Вопрос даже не столько из математики, сколько из плоскости философии - бывают случаи когда можно сказать «точно да», бывают случаи «точно нет». Но всегда остаётся «серая зона неопределенности», и свести её к нулю невозможно в принципе. И это неизбежно, чего бы это не касалось. Не удивлюсь если существует теорема это формализующая. И нет - ничего запрещенного я не курил.

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

Как memcheck определяет доступ к освобожденной памяти или неинициализованность? Ладно с локальными переменными всё более-менее ясно, а вот что делать с динамически выделенной?

Для выделенной памяти хранятся метаданные. В общих чертах у AddressSanitizer примерно то же.

Если я переиспользую блок памяти, а memcheck не понял, что эта последовательность действий значила «переиспользование» — вуаля, у меня use-after-free или доступ к неинициализированным данным, которые memcheck не может распознать.

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

если я делаю какие-то хитрые указатели, вроде деления int64 на два int32 — memcheck не находит указателя на память, в его понимании память пропала.

Memcheck не в курсе про указатели. Он интерпретирует машинный код. Если ты полезешь в область памяти, которую не выделяли, то он это поймёт просто по факту обращения к памяти. Трюки с хранением указателя по частям на это никак не влияют.

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

У меня есть подозрение, что ты путаешь Valgrind и какую-то сишную библиотеку для сборки мусора.

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

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

Однако, в число «простых случаев» не входит уже какой-нибудь параллельный сборщик мусора.

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


На всякий случай напоминаю, что я не продаю тебе Valgrind. Но меня коробит от заявлений типа «у Valgrind много ложных срабатываний», потому что в общем случае это не правда. Обобщать проблемы случая с кастомными аллокаторами, когда инструмент применяется не по назначению, так же странно, как и фыркать на бензопилу, только из-за того, что она не перерубила лом. Или ругать компилятор Си, за то что он не смог скомпилировать текст на натуральном языке, выдав кучу непонятных ошибок. Ты же не будешь заявлять, что у синтаксического анализатора GCC куча ложных срабатываний.

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

Ошибок типа «uninitialized-memory-read» и «use-after-free» на моей практике - нет, не пропускал. Остальное - не его работа.

Вот здесь я даже соглашусь, что помимо контроля использования malloc-free по строгому протоколу valgrind мало для чего полезен — «не его работа».

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

Неправда. Именно поэтому report и генерится на выходе.

Вы выводите still reachable в ноль, что ли? Потому что если нет, то как memcheck сможет расшифровать непрямые указатели на известные участки памяти?

byko3y ★★★★
()
Ответ на: комментарий от i-rinat

Для выделенной памяти хранятся метаданные. В общих чертах у AddressSanitizer примерно то же.

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

Memcheck не в курсе про указатели. Он интерпретирует машинный код. Если ты полезешь в область памяти, которую не выделяли, то он это поймёт просто по факту обращения к памяти.

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

У меня есть подозрение, что ты путаешь Valgrind и какую-то сишную библиотеку для сборки мусора.

Я даже не знал, что такие существуют.

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

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

Отличие lock-free алгоритмов заключается в том, что побочные эффекты могут наблюдаться везде, неожиданно, и даже в алгоритмах, близких по сути к блокировочным (всякие там busy-loop-ы, активно ждущие неких условий). То есть, мы внезапно можем попытаться разыменовать указатель, который в параллельном потоке высвобождается, и это даже долго может работать не падая за счет того, что память не успевает высвободиться.

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

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

Но меня коробит от заявлений типа «у Valgrind много ложных срабатываний», потому что в общем случае это не правда.

Общем случае? Для тебя код духа C++ девяностых/нулевых — это «общий случай»? Я вполне конкретно писал, что Valgrind и статические анализаторы бесполезны на НЕСТАНДАРТНЫХ случаях, которые вполне себе входят во множество «общих случаев». А достаточно большой проект, да еще и на C/C++, неизбежно приходит к тому, что нужно придумывать что-то из ряда вон выходящего. Даже в достаточно классическом Firefox, где очень пытались делать вид, что проблемы не существует, хотя на самом деле она и у них есть, и целый ЯП вокруг безопасности доступа к памяти и отсутствия гонок Mozilla сделала не потому, что «valgrind и так всё найдёт» — в тех же расчетах стилей классический синхронно-однопоточный подход оказывается слишком неэффективным.

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

В девяностых-нулевых на C/C++ писали банальную логику, в 2023 году ее пишут на JS, Python, Go, а C/C++ стали намного чаще применять там, где нужно что-то нестандартное.

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

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

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

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

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

Memcheck такое поймает.

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

У нас свобода вероисповедания, так что можешь верить во что хочешь. Я оспаривал утверждение о том, что Valgrind «выдает очень много ошибок, которые не ошибки, потому что программа многопоточна и имеет асинхронный ввод-вывод». Это утверждение ложно.

valgrind и так всё найдёт

Где-то кто-то утверждал, что Valgrind находит прямо всё? Это очевидно невозможно.

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

Это безосновательные спекуляции.

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

Я об этом уже писал. Если у тебя странные указатели, то финальном отчёте (который ещё нужно включить) память попадёт в список потерянной, а не ещё доступной. В любом случае, если ты хочешь проверять утечки памяти утилитами, нужно как минимум иметь режим работы программы, в котором она тщательно освобождает всю выделенную память штатными методами, а не «завершился-освободился».

Удивительно, что во времена, когда программы часто работают месяцами, тебя вообще волнует, что там, в этом финальном отчёте Memcheck.

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

Удивительно, что во времена, когда программы часто работают месяцами, тебя вообще волнует, что там, в этом финальном отчёте Memcheck.

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

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

Я выразился криво и скомкано. Сейчас выражусь развёрнуто и невнятно.

Вообще как бы да, избавляться от всех утечек в финальных отчётах это как бы хорошо. Но с подходом @byko3y, где он явно не освобождает всю выделенную память при завершении, а указатели на блоки хранит в почти что зашифрованном виде, попытки разобраться, какие блоки памяти в отчёте Memcheck утекли, а какие всё-таки ещё нет, становятся бессмысленной и переусложнённой игрой. Зачем. Просто зачем. Это лишняя боль.

С другой стороны, утечки, которые не были выявлены с помощью Memcheck запусками тестов, скорее всего не будут выявлены им при длительном запуске в рабочем режиме. Будет только страдание, потому что Valgrind замедляет всё раз в 20. Утечки в длительно работающих процессах это скорее всего какие-то кеши, которые постоянно растут, и никогда не очищаются. Но очищаются при завершении программы. Memcheck такое утечкой считать не будет, ведь к моменту финального отчёта память всё-таки освобождается.

К долгоживущим процессам лучше подцепляться чем-то вроде утилиты memleak из состава bpfcc. Замедления она почти не вносит.

Пока memleak-bpfcc не существовало, можно было пользоваться утилитой Massif из Valgrind. Там по срезам можно найти динамические утечки. Но тоже медленно.

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

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

Потому я разработал методологию параноидальных assert-ов, которую впервые применил в PSO, и только благодаря этому подходу мне удалось вообще что-то отладить. «Изначальный программист» не просто «может забыть», он обязательно забудет и нарушит правило — человек в IT нужен для того, чтобы ошибаться, именно потому обрабатывать нужно не только ошибки во входных данных, но и ошибки в алгоритмах.

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

Memcheck такое поймает.

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

Я оспаривал утверждение о том, что Valgrind «выдает очень много ошибок, которые не ошибки, потому что программа многопоточна и имеет асинхронный ввод-вывод». Это утверждение ложно.

Это утверждение не ложно и не истино, оно НЕПОЛНО, в зависимости от дополнительных условий оно может становиться истинным, и я эти условия описал.

Где-то кто-то утверждал, что Valgrind находит прямо всё? Это очевидно невозможно.

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

Удивительно, что во времена, когда программы часто работают месяцами, тебя вообще волнует, что там, в этом финальном отчёте Memcheck.

Не меня, выше по треду челу был важен конечный отчет.

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

Не меня, выше по треду челу был важен конечный отчет.

Как мы мило передергиваем…

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

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

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

Это утверждение не ложно и не истино, оно НЕПОЛНО

Там две части. Первая: «Valgrind выдает очень много ошибок, которые не ошибки». Вторая: «<причина этого в том, > что программа многопоточна и имеет асинхронный ввод-вывод». Вторая часть — ложна. Многопоточность не вызывает ложные срабатывания, потому что Valgrind по сути сериализует исполнение потоков. Асинхронный ввод-вывод сам по себе не вызывает ложных срабатываний, потому что основной инструмент Valgrind, memcheck, проверяет доступ к памяти в моменты обращения к памяти. Недостаток моделирования API асинхронного ввода-вывода может давать ложные срабатывания. Например, если в Valgrind убрать модель для простого блокирующего read(), а в программе не инициализировать буфер, то Memcheck ругнётся, что дальше программа читает мусор из буфера, хотя в реальности ОС гарантирует, что данные там есть.

Я просто еще раз напомнил, что их возможности ограничены стандартными случаями.

Жёстким безапелляционным заявлением, которое доказуемо ложно. Собственно, этот момент я и оспариваю.

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

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

Да, я весьма навязчиво неоднократно пытаюсь именно это пояснить.

Там две части. Первая: «Valgrind выдает очень много ошибок, которые не ошибки». Вторая: «<причина этого в том, > что программа многопоточна и имеет асинхронный ввод-вывод». Вторая часть — ложна.

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

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

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

Да, я весьма навязчиво неоднократно пытаюсь именно это пояснить.

Но ведь никто в треде не писал такого. Ты вообще был первый, кто упомянул Valgrind, причём на фразу про статические анализаторы. С кем ты тогда споришь?

в коде с какой-нибудь разделяемой памятью

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

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

Как оформить поток другим процессом?

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

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

Как оформить поток другим процессом?

Как это было в никсах до линя — не существовало потоков, были только процессы. Отсюда историческое наследие с однопоточным кодом, синхронным вводом-выводом, и сигналами, как костылем над отсутствием потоков и асинхронности, которые потом уже постепенно стали обрастать чем-то более современным.

byko3y ★★★★
()
Ответ на: комментарий от i-rinat

Тут свалены в одну кучу процессы, потоки, ядерные воркеры и iowq воркеры, валгринд, мьютексы, aio, shared memory, аллокаторы хипа. Это типично для обезьянок. Главное погромче прокричать и сильнее метнуть какашками. Не бери в голову, обезьянки все такие.

iliyap ★★★★★
()
Закрыто добавление комментариев для недавно зарегистрированных пользователей (со score < 50)