LINUX.ORG.RU

glibc небезопасна

 , ,


0

7

Привет, ЛОР!

Наткнулся тут на прекрасную историю о том, как использование казалось бы обычных функций из libc может оторвать жопу. И даже Rust не поможет!

Ссылка: https://www.edgedb.com/blog/c-stdlib-isn-t-threadsafe-and-even-safe-rust-didn-t-save-us

Для Ъ:

Функции getenv() и setenv() небезопасны в многопоточных программах. В частности, вызов setenv() во время выполнения getenv() в другом потоке может привести к порче памяти и падению программы. Это особенно релевантно на ARM64, потому как архитектура предоставляет меньше гарантий по части очерёдности выполнения команд.

И если бы это можно было списать на кривые руки авторов статьи, проблемы бы не было. Но тут суть в том, что огромное количество библиотек дёргают getenv() и setenv() под капотом с разными целями, в том числе гнутое gettext и прочие openssl.

Скажи, ЛОР, сишники совсем разучились писать безопасный код? Как с этим жить-то вообще?

UPD:

Подробный пост об этой проблеме: https://www.evanjones.ca/setenv-is-not-thread-safe.html

Прекрасное оттуда:

glibc uses an array to hold pointers to the "NAME=value" strings. It holds a lock in setenv() when changing this array, but not in getenv(). If a thread calling setenv() needs to resize the array of pointers, it copies the values to a new array and frees the previous one. This can cause other threads executing getenv() to crash, since they are now iterating deallocated memory.

То есть, вызов getenv() из glibc потенциально является use-after-free в многопоточной программе и от этого никак нельзя защититься.

★★★★★

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

getenv/setenv/environ не нужны вообще.

А GetEnvironmentVariable() нужно?

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

P.S. Из буквоедства, ядро не передаёт в main() список env,

Там всё хуже - ядро при exec'е засовывает то, что передал программист / runtime, в отдельный блок стека вместе с argv.

Да, никто не хочеть дёргать prctl(PR_SET_MM_ENV_START,)

И по итогу у нас один сет на старте процесса, хер пойми какой сет во время работы и милостью сишного рантайма и костылями в ядре что-то третье при exec'е. La classique de unixé.

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

за троллинг зачёт

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

Именно. И никакой косметикой, типа блокировка в getenv() эту «классику» не поправить. ИМХО, нужно убирать глобальный указатель char **environ, а это поломает совместимость с бинарниками. Но, те, кто регулярно создают фича-реквесты с предложением сделать getenv()/setenv() многопоточными, не понимают глубины классики :) Так что драмы будут продолжаться.

И, если уж ломать, то возникает вопрос, как это должно быть правильно. Одни переменные хочется локальными для потока (thread), чтобы в каждом потоке могли быть свои настрокий libcurl. А другие, наоборот, неизменяемые на всю сессию, чтобы была одна локаль для всех потоков и их дочерних процессов.

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

И если №2 можно сделать и не меняя собственный environ (просто передавать новый при exec),

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

1) выставить окружение для дин.библиотек до их загрузки ldload

Это какие-то дефективные библиотеки, если с ними приходится так костылить.

firkax ★★★★★
()

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

А они когда-то умели?

no-such-file ★★★★★
()
Ответ на: комментарий от LamerOk

Там всё хуже - ядро при exec'е засовывает то, что передал программист / runtime, в отдельный блок стека вместе с argv.

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

И по итогу у нас один сет на старте процесса, хер пойми какой сет во время работы и милостью сишного рантайма и костылями в ядре что-то третье при exec'е

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

сишного рантайма

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

костылями в ядре

Нет в ядер никаких костылей, для него env это блоб, который одна прога передаёт другой через сисколл execve()/fexecve(). И все остальные exec-и, если что - это libc-шные обёртки над execve().

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

Отстань от environ, с ним всё в порядке. Лучше пристань и бей по рукам тех кто setenv абузит, и всё станет хорошо.

в каждом потоке могли быть свои настрокий libcurl

«Свои настройки» надо передавать аргументами функции. Если у курла такого вдруг нет (я не в курсе) - багрепорт его автору.

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

«Свои настройки» надо передавать аргументами функции.

Ты вызов malloc() себе представляешь? Вот этот самый malloc из glibc тоже переменные читает. ШОК! СЕНСАЦИЯ!

Жду от тебя багрепорта в glibc.

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

Прочитай man mallopt - если прога хочет что-то передать своим собственным malloc-ам, она должна это делать нативным способом, а не через текстовые костыли. А getenv она вызывает только 1 раз, при первом обращении, и как и положено всё это сразу парсит в нативный вид и пользуется уже им.

А ты пытался настраивать malloc setenv-ом? 😱

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

Прочитай man mallopt - если прога хочет что-то передать своим собственным malloc-ам, она должна это делать нативным способом, а не через текстовые костыли.

Прочитал.

A number of environment variables can be defined to modify some of the same parameters as are controlled by mallopt(). Using these variables has the advantage that the source code of the program need not be changed.

А getenv она вызывает только 1 раз, при первом обращении, и как и положено всё это сразу парсит в нативный вид и пользуется уже им.

Круто. И? Когда от тебя багрепорт в glibc поступит-то?

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

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

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

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

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

Не знаю о какой проблеме ты пишешь. Поскольку ты упрямо не хочешь понимать о чём я писал, поясню ещё раз: багрепорты следует слать туда, где нет нативного способа делать настройки (себе самому) а есть только костыльный через env. У malloc такой способ есть. Дальнейший предмет обсуждения мне неясен.

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

бей по рукам тех кто setenv абузит

ИМХО, лучше бы setenv() бил по рукам тех, кто его в многопотоке вызвает. Чтобы сразу было понятно, что код с ошибкой, а не ловить где-то потом в глубинах другой либы сегфолт.

передавать аргументами функции

Ну, мнение, про то, что хорошо бы каждому потоку свои переменные среды, было высказано выше по треду glibc небезопасна (комментарий) Думаю, MirandaUser2 не одинок.

Так, в вашем мире всё хорошо, багрепорт автору библиотеки, передавать новый набор переменных при exec() — единственный вменяемый способ. Только где в вашем мире место setenv(), зачем его вобще в стандарт POSIX ввели?

Не знаю, по какой третий set писал LamerOK, но, в принципе, можно про /proc/PID/environ подумать. То есть, есть набор, переданый программе ядром в момент exec(), лежащий в стеке, есть набор, используемый кодом программы (char **environ), а ещё программа через /proc/PID/environ может светить что хочет, выделить область памяти, сложить туда что угодно и prctl(PR_SET_MM_ENV_START,).

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

Одни переменные хочется локальными для потока (thread)

Переменные окружения процесса локальные для контекста потока не имеют смысла. Можно просто использовать переменные ЯП / TLS.

возникает вопрос, как это должно быть правильно.

Правильно, это доступное всем процессам или в рамках одной сессии типизированное по данным с атомарным изменением на уровне ядра key-value хранилище, широко известное в быту как «проклятый вендовый реестр!!111».

Без него мы до тепловой смерти вселенной будем наблюдать клоунаду от фридесктопа с раздербаниванием одного xml-файла на отдельные файлы-сниппеты в директории.

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

что тебя не устроило то?

Гарантии со стороны системы на весь этот кавардак. Если я создам процесс через clone(2), переменные окружения будут переданы новому процессу или нет?

мне и в голову не приходило портить environ только для того чтобы потом передать его потомку.

Дай угадаю - ты никогда не создавал новых сессий и не запускал процессы от другого пользователя, верно?

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

локальные для контекста потока не имеют смысла.

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

или в рамках одной сессии

И, что, для изменения переменной новую сессию делать? И про какую сессию идёт речь, которая через setsid() или что-то другое?

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

То есть пользователь теряет возможность запуска старого/кривого бинарника с другими либами через LD_PRELOAD?

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

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

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

Это какие-то дефективные библиотеки, если с ними приходится так костылить.

значительная часть мат.библиотек :-) Если память не изменят то и Mesa и с OpenGL тоже. Ваш любимый питон и почти все интерпретаторы туда-же..

настройка и тюнинг софта/библиотек делается с env.

Потому-что это исторически оказалось что так просто, удобно и практично

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

значительная часть мат.библиотек :-) Если память не изменят то и Mesa и с OpenGL тоже.

Что, во всём перечисленном нет нативных функций для задания настроек?

Ваш любимый питон

Во-первых что это за клевета про то что питон мой любимый, а во-вторых причём тут вообще питон?

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

Только где в вашем мире место setenv(), зачем его вобще в стандарт POSIX ввели?

В коде /bin/sh для реализации команды export. А так - можно по примеру gets выдавать линкером депрекейт-варнинг при его обнаружении.

а ещё программа через /proc/PID/environ может светить что хочет

Ну это не тянет на третий сет, это всего лишь способ абуза всяких программ подобных ps/top (хотя они вроде env не умеют показывать) чтобы вывести в них кастомную инфу. Точно так же можно аргументы настоящие затереть и вместо них какую-то информацию показывать.

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

Гарантии со стороны системы на весь этот кавардак. Если я создам процесс через clone(2), переменные окружения будут переданы новому процессу или нет?

Что значит «переданы»? Переменные окружения никуда не передаются при создании процессов. Они передаются при вызове нового бинарнике через execve() - можно и без форка. Механика данной передачи точно такая же как механика передачи аргументов командной строки, отличие лишь в том (и оно не техническое) что аргументы используют почти все и их дефолтно показывает ps/top, а про env мало кто помнит и обычно его не показывают и не смотрят.

Дай угадаю - ты никогда не создавал новых сессий и не запускал процессы от другого пользователя, верно?

Не угадал.

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

Сеанс пользователя после входа в систему.

Эта сущность вообще целиком «изображена» какими-то библиотеками, на уровне системного кода никаких сеансов входа не существует.

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

на уровне системного кода никаких сеансов входа не существует.

Какого системного кода? В systemd сеансы есть, а у него системность прямо в названии.

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

Системное это ядро и libc.

Почему? Init не занимается инициализацией системы? Как насчёт udev? Системная шина IPC типа dbus?

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

Прочитай man mallopt - если прога хочет что-то передать своим собственным malloc-ам, она должна это делать нативным способом, а не через текстовые костыли. А getenv она вызывает только 1 раз, при первом обращении, и как и положено всё это сразу парсит в нативный вид и пользуется уже им.

Ты прочитал неправильно. Большая часть опций в mallopt – для отладки бинарника. У тебя есть блоб, в нем что-то не так, ты выставляешь опции окружения и получается что-то на дебажном.

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

Системное это ядро и libc.

Системное это все, что не пользовательское. Включая NTP и DNS, которые не являются пользовательскими программи, а являются частью ОС.

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

Если я создам процесс через clone(2), переменные окружения будут переданы новому процессу или нет?

Что значит «переданы»?

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

Дай угадаю - ты никогда не создавал новых сессий и не запускал процессы от другого пользователя, верно?

Не угадал.

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

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

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

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

Потому что мы обсуждаем программирование на Си, а не какую-то скриптоту или администрирование системы.

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

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

И что?

что ты не справился с пониманием поста, на который отвечал.

Справился конечно - я понял что ты написал глупость, и пытаюсь тебе это разъяснить. Например, ты плохо понимаешь процесс запуска процесса-потомка (функции fork/clone, exec), поскольку начал обсуждать env в контексте форков, которые с ним вообще никак не связаны.

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

Нет, я, в отличие от тебя, прочитал man execve. Третий его аргумент - набор env-переменных, его, в данном случае (вызов со сменой контекста прав), надо сконструировать самостоятельно, заполнив правильно весь дефолтный набор переменных, а не пытаться сувать туда патченый char **environ.

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

В коде /bin/sh для реализации команды export.

Кто мешал там ввести отдельную переменную вроде char **shell_environ?

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

Потому что мы обсуждаем программирование на Си, а не какую-то скриптоту или администрирование системы.

Окей, libsystemd. Там точно сеансы есть. Написана на Си. Системнее просто некуда.

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

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

А она там и есть (ну не точно такая но примерно) - для тех переменных, которым export не сделали. Если ты про то, почему setenv для глобального environ вообще сделали в libc, а не сделали приватной функцией шелла, то тут наверно так: 1) сложилось исторически, 2) шеллы бывают разные, а логика с env у них общая - зачем её таскать везде когда можно в библиотеку засунуть, 3) вдруг кому ещё всё-таки понадобится. Хотя на практике требуется она, к сожалению, в основном багоделам. Но воспитание сторонних программистов в задачи авторов libc явно не входит, так что это не влияет.

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

Ну и к чему ты это написал?

К тому, что твое воинствующее невежество и попытки защищать сломанные API – очень смешно.

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

Про потуги системг-адептов интегроровать системг в систему я в курсе, но, к счастью, на данный момент 99+% софта прекрасно работает, даже не зная про эту сущность. Для работы системы она не требуется, это просто очередная библиотека наряду с каким-нить liboauth или libpulseaudio.

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

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

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

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

Ты флудишь не пойми о чем, я несу свет истины о том зачем нужны опции malloc.

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

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

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

Про потуги системг-адептов интегроровать системг в систему я в курсе, но, к счастью, на данный момент 99+% софта прекрасно работает, даже не зная про эту сущность.

Почти весь софт про это знает тем или иным образом. Например, потому что пользуется sd-bus.h или создает systemd сервисы. Или пользуется ингибитором. Короче, ты опять не шаришь.

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

что тебя не устроило то?

Гарантии со стороны системы на весь этот кавардак.

Справился конечно - я понял что ты написал глупость

Примерно такого ответа я от тебя и ожидал.

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

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

И, с точки зрения sh, ИМХО, как раз setenv() не удобен, получается, два набора переменных, одни export, другие не export, а вся разница между ними только в момент exec(). Проще перед execvp() сформировать для этой функции набор export-переменных, чем всяких раз значение переменной искать в **environ и **local_environ, и определять, в какой набор записывать изменённое значение.

Вобще, если я правильно нагулил, то исходно, в 1979 году, в UNIX Programmer's Manual для Unix V7 вобще включал только char **environ и getenv(). Как хотите, так **environ и меняйте, а для упрощения поиска getenv(). А затем, зачем-то понадобилось одним придумать страшный putenv(), а другим setenv(). И эти функции потом стали затаскивать в POSIX и другие стандарты.

То есть изначально сами как хотите изменяйте **environ, а потом единообразный подход. Но, ведь единообразие нужно, в обязательном порядке, только для стороннего кода/библиотеки. То есть зачем-то хотели иметь возможность именно из библиотеки менять переменные среды. В то, что putenv()/setenv() вводили для компактности кода, чтобы не тащить свой код, изменяющий переменные среды, верится с трудом. Сколько лет отказывались от spawn(), типа fork()+exec() достаточно.

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

зачем-то хотели иметь возможность именно из библиотеки менять переменные среды. В то, что putenv()/setenv() вводили для компактности кода, чтобы не тащить свой код, изменяющий переменные среды, верится с трудом.

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

Сколько лет отказывались от spawn(), типа fork()+exec() достаточно.

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

Проще перед execvp() сформировать для этой функции набор export-переменных,

Вот только передать его будет некуда. execvp() имеет прототип без передачи окружения:

 int execvp(const char *file, char *const argv[]);

Эту функцию

int execve(const char *path, char *const argv[], char *const envp[]);

добавили в POSIX только в 2001-ом. Видимо, по этому у GNU было своё расширение:

 int execvpe(const char *file, char *const argv[], char *const envp[]);

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

но не может их установить.

Стандарт описывал в каком формате хранятся переменные и давал **environ. Как хочешь, так и заноси туда значения. argv[] же как-то формируют без функций.

А довольно глупая ситуация возникла, когда одни ввели putenv(), а другие, вроде, в этом же году, придумали setenv(). А потом все долго спорили, что же включать в стандарты (put- или set-) или как их скрестить, чтобы они друг-друга не ломали.

C execvp() я опечатался, подразумевал я execve(), которая появилась в Unix Version 7 (1979) и осталась в Unix System V, от которой все отталкиваются. Не знаю, как её не могло быть в POSIX.

А GNU-расширение execvpe() — это просто «до полного комплекта», раз остальные функции (execl() и execv()) имеют «p» версию (запуск с поиском по PATH), то и для execve() добавить.

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

Как хочешь, так и заноси туда значения. argv[] же как-то формируют без функций.

argv не является «окружением процесса».

Не знаю, как её не могло быть в POSIX.

Предлагалось жевать альтернативы в духе:

int execl(const char *, const char *, ...);
int execle(const char *, const char *, ...);
int execlp(const char *, const char *, ...);

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

Стандарт описывал в каком формате хранятся переменные и давал **environ.

Какой стандарт?

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

Какой стандарт?

Unix Seventh Edition Manual. В котором и ввели environment (user environment). В более ранних Unix'ах такого не было.

Не знаю, где посмотреть POSIX.1-1988, но, может миру UNIX тогда на POSIX было побоку. В 1994 году выпустили Single UNIX Specification, там в System Interfaces and Headers, Issue 4, Version 2 присутствует execve() https://pubs.opengroup.org/onlinepubs/9695969499/toc.pdf .

Причём, то что написано в SUS, очень похоже, а может дословно повторяет System V Interface Definition (который выше ранее в 1986) https://bitsavers.org/pdf/att/unix/SVID/System_V_Interface_Definition_Issue_2...

Ещё ранее вышел XPG1: X/Open Portability Guide Issue 1: 1985, https://bitsavers.org/pdf/xOpen/X_Open_Portability_Guide_1985/xpg_2_xopen_sys... там тоже был execve(). И везде фразы про то, что такое char **environ.

mky ★★★★★
()

как использование казалось бы обычных функций из libc может оторвать жопу

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

Stil ★★★★★
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.