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)
Ответ на: комментарий от maxcom

Rust форсит использование тех или иных примитивов синхронизации через систему типов. Например, блокировка mutex возвращает мутабельную ссылку на данные, а в Rust, как известно, мутабельная ссылка на объект может быть только одна в один момент времени. При этом эту ссылку нельзя класть никуда, откуда её сможет прочитать другой поток (глобальные переменные, другой mutex и т д).

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

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

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

Тут реально вся проблема в том, что в getenv() не используется лок. И это довольно просто починить было бы, но пришёл LamerOk и рассказывает, что портить память – это фича.

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

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

Это просто мои любимые шизофреники на ЛОРе

Ну про шизофреников это порой правда. Я иногда думаю: «как можно настолько не видеть бревно у себя в глазу» – такие треды даже сместили мое виденье растеров и сишников/плюсовиков. Раньше я думал что первые шизанутые, а вторые вроде адекватные, сейчас они уравнены. Я даже для нового пет проекта решил взять раст, но там скорее язык не столько важен, потому что по сути надо дергать pipewire api, зато как проект, чтобы узнать язык вроде норм.

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

Rust форсит использование тех или иных примитивов синхронизации через систему типов

https://doc.rust-lang.org/std/env/fn.set_var.html

система типов 80-го уровня, бгг. Причем они сделали это ансейфом судя по всему только в прошлом году, а до этого бездари фигачили гонки как в статье вполне себе безопасно))

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

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

firkax ★★★★★
()

1. Сделай мьютекс или семафор, если есть риск гонки. А лучше прочитать/записать до запуска многопоточного кода, если это возможно.

2. Далеко не каждая программа многопоточная. Какие-то прекрасно работают в один поток, какие-то достигают параллелизма через fork().

3. К libc нет никаких вопросов, она работает быстро, насколько это возможно, а если программист придумал многопоточность к себе в программу сделать, libc это не касается никак вообще. Все проблемы многопоточности ложатся на программиста.

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

Да такая же проблема со всякими strtok, которым сделали замену в виде strtok_r, где сохраняется контекст токенизации.

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

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

KivApple ★★★★★
()

а все эти libc не thread safe по определению. и чтоб их сделать таковыми, надо пользоваться внешними средствами синхронизации. навроде мьютексов.

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

потому что тредсейф это накладные расходы и в большинстве случаев не нужны вообще. зачем однопоточной программе делать мьютексы и лочить их? зачем грамотной многопоточной, где только один тред занимается этими вашими getenv/setenv, делать мьютексы и лочить их?

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

У Rust тоже до жопы проблем, так-то. Упоротая стандартная библиотека, в которую несут всё подряд. Просто лютое безумие с async. Мусорные библиотеки на crates.io. Это всё весьма бесит, да.

Ну и ещё тот факт, что половина растовиков – членодевки в отрицании и не отличаются эмоциональной стабильностью.

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

а все эти libc не thread safe по определению. и чтоб их сделать таковыми, надо пользоваться внешними средствами синхронизации. навроде мьютексов.

Там есть мютекс лол. Но он проверяется только в setenv, в getenv не проверяется.

@zx_gamer

  1. К libc нет никаких вопросов, она работает быстро, насколько это возможно, а если программист придумал многопоточность к себе в программу сделать, libc это не касается никак вообще. Все проблемы многопоточности ложатся на программиста.

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

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

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

все правильно. просто в грамотном дизайне многопотока треды имеют свою специализацию, и если только один занимается вот этими вашими set/getenv, то ему не нужна синхронизация. а в однопотоке она тем более не нужна.

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

alysnix ★★★
()

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

Сейчас с соевым подходом в виде SODD и уж тем более с ChatGPT-driven development такое непостижимо :)

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

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

Ах если бы. Писалось это, судя по всему, в стиле «херак херак и в продакшон». Как и весь юникс. Признаков наличия разума у авторов этих поделий не наблюдается.

Сейчас с соевым подходом в виде SODD и уж тем более с ChatGPT-driven development такое непостижимо :)

Вот что непостижимо, так это как можно так обосраться на ровном месте.

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

Ах если бы. Писалось это, судя по всему, в стиле «херак херак и в продакшон». Как и весь юникс.

Нет. Ты просто набрасываешь говна на вентилятор, как почти всегда.

Признаков наличия разума у авторов этих поделий не наблюдается.

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

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

базовые либы не многопоточны

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

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

Не надо писать многопоточный код. Это всё от лукавого. Если бы ты прочитал книгу А. В. Столярова «Программирование: введение в профессию», то не задавал бы таких вопросов.

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

quantum-troll ★★★★★
()

Сишные апи в целом ОЧЕНЬ плохие.

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

Они никогда и не умели его писать. Особенно многопоточный код.

quantum-troll ★★★★★
()
Ответ на: комментарий от Dimez

Нет. Ты просто набрасываешь говна на вентилятор, как почти всегда.

Одно другому не мешает. Особенно когда тут ТАКОЙ вентилятор.

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

Вся индустрия хором хает API вокруг getenv/setenv, называя его чуть ли не худшим (хотя я знаю хуже). Модераторам ЛОРа норм.

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

getenv, setenv...

а вы представляете что там «под капотом» делается ?

ещё наверное прикольно во время setenv запустить fork()

PS/ по хорошему set/getenv должны быть системными вызовами, гарантировать атомарность и усыплять процесс до завершения. На уровне библиотек это не объехать. Это заодно кстати про виндовый реестр, который конечно-же говно, но какой-то аналог нужен :-)

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

Она с нами в одном треде даже.

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

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

Не понимаю смысл драмы. В мане написано все по существу. Если ты знаешь, что у тебя set/get-env будут вызываться в разных потоках – втули мютекс. Тоже самое и с тонной других функций не thread-safe. Нафига нужен мютекс в самой функции по дефолту, если 99%-м ее «потребителей» это нафиг не нужно?

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

А я давно предлагаю починить этот баг и забанить димеза!

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

getenv дёргается в куче библиотек

Обычно в документации библиотечных вызовов указывается, thread-safe функция или нет. А если ты дергаешь их в многопотоке своего кода – то задача синхронизации их вызовов на твоих плечах. Я сам наступал на грабли с функциями типа EVP_MD_CTX_new() пока не разобрался со всеми правилами их использования в многопотоке :(

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

Обычно в документации библиотечных вызовов указывается, thread-safe функция или нет. А если ты дергаешь их в многопотоке своего кода – то задача синхронизации их вызовов на твоих плечах.

Ага, и при этом эта документация не учитывает гонку в getenv. Сейчас проверил у curl, там про это ничего нет.

В любом случае, getenv никак мютексом не защитишь. Просто потому что он может быть вообще в любой библиотеке. Или ты серьёзно предлагаешь вообще все внешние библиотечные вызовы под мютекс совать?

Я сам наступал на грабли с функциями типа EVP_MD_CTX_new() пока не разобрался со всеми правилами их использования в многопотоке :(

API OpenSSL – один из наиболее ублюдочных что я видел в своей жизни. С ним не разбираться надо, его надо выкинуть на помойку и забыть как страшный сон.

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

Писалось это, судя по всему, в стиле «херак херак и в продакшон». Как и весь юникс

Изначально всё было нормально, потому что в UNIX не было поддержки потоков, только fork. А значит никаких проблем с потокобезопасностью не было. Это уже потом внедрили pthread, не разобравшись возникшей проблемой потокобезопасности libc.

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

Или ты серьёзно предлагаешь вообще все внешние библиотечные вызовы под мютекс совать?

Я предлагаю лишь вдумчиво читать доки по их использованию. Если в доке нет нужной инфы – можно отловить большинство «аномалий» нагрузочным тестированием + walgrind или sanitizer.

API OpenSSL – один из наиболее ублюдочных что я видел в своей жизни.

Сложно не согласится. Ну что поделаешь, иногда приходится жрать кактус.

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

Но то, что glibc не засунули туда, как положено, rwlock

Не помогло бы, getenv возвращает указатель на строку в environment, а не на копию строки

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

если программа запущена с опцией –secure, которая (для примера) запрещает выход за пределы контура. Тогда, вместо того, чтобы лепить в коде, который использует SERVER какие-то проверки удобнее на этапе инициализации сделать setenv(SERVER, )

На этапе инициализации (парсинг опций) ещё нет других потоков, так что это не проблема.

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

у curl, там про это ничего нет.

Значит, разработчики curl налажали с документацией.

И исходя из имеющихся реалий, библиотеки, если они считают себя многопоточными, должны быть написаны так, чтобы вместо getenv/setenv дёргать функции из головной программы, которые разработчик напишет с мутексом.

И вобще, при чём там мутекс, в ″man 3p getenv″ прямо написано, что возвращаемый указатель «might be invalidated ... by a subsequent call to getenv(), setenv()». Ну, или в ″man 3 getenv″:

The string pointed to by the return value of getenv() may be statically allocated, and can be modified by a subsequent call to getenv(), putenv(3), setenv(3), or unsetenv(3).

То есть мутекс должен быть не в getenv(), а на весь код, принимающий решение по значению переменной (или копирующей его). А, чтобы getenv() был thread-safe, он должен, либо возвращать указатель на динамически выделенную память, а поток, потом должен делать free(), либо что здесь назвали macos-вариантом, новые значения переменных через setenv() в новую память (как бы утечка), а старые значения остаются по старым адресам (указателям).

То есть, имеющуюся POSIX реализацию getenv() не сделать thread-safe, а добавить какой-нибудь getenv_ts() можно, но тогда авторов библиотек нужно заставлять его использовать, gnulibc-only функцию.

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

В musl другая залупа.

Хм, а «залупа» ведь толковый термин для программирования. Типа «коряво написанный loop который потенциально может зациклится» или что-то типа того. Возьму на вооружение, пожалуй. «Посмотри внимательнее, у тебя вот тут в коде залупа же очевидная!»

А что до setenv/getenv оно при старте софтины используется в 99% случаев для извлечения нужной информации в переменные, которые в дальшейшем не изменяются. Всякие треды запускаются потом. Нужды в thread safe setenv/getenv просто нет. Использовать переменные окружения для IPC в процессе работы потоков софтины - идиотизм. Постоянно делать getenv в тредах вместо использования готовой переменной инициализированной при старте - тупо.

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

Сейчас с соевым подходом в виде SODD и уж тем более с ChatGPT-driven development такое непостижимо :)

чатгопота-то как раз прекрасно знает про getenv (я проверял) и не допустит такой ошибки, …наверно))

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

getenv возвращает указатель

Да, и когда функция в Си возвращает указатель, и это указатель не на переданую её память или не на динамическую память, то это повод сразу считать эту функцию thread-unsafe. И не требовать: «где в документации написано, что эту функцию нельзя в многопоток», а наоборот искать, где написано, что можно.

И для растоманов, которые не понимают, что означает MT-Unsafe, в ″info setenv″ прямо написано:

Modifications of environment variables are not allowed in multi-threaded programs.

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

Как раз во всяких потоках и вызываются библиотечные функции, которые делают getenv(). Тот же getaddrinfo() вызывает getenv() для значений LOCALDOMAIN и RES_OPTIONS.

Но getenv() не ломается, если не делать setenv(). А вот ТС утверждает, что библиотеки постоянно делают setenv(), но, пока что, ни одну не показал.

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

Зачем системные вызовы, спокойно на уровне библиотеки объёзжается. malloc()/free() ведь сделали thread-safe. Просто getenv() должна работать по-другому к каким-то общим с setenv() мутексом не отделаться. Допустим, виндовая getenv_s() копирует в предоставленный ей буфер, и возвращает ошибку, если места недостаточно. То есть код, использующий getenv_s() пишется с другой логикой, чем getenv().

fork() в многопоточном приложении — это отдельный случай, там без setenv() проблем хватает, не случайно придумали pthread_atfork().

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

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

Спорим, я знаю другие способы? :)

pihter ★★★★★
()

Мощно.

Очередная победа сишников над здравым смыслом.

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

А по ссылке, приведёной ТС, боль то не у Сишников, а у Гошечников. Гошечка под капотом сразу многопоточная, там нельзя setenv зарание. Писать замену libc, а не обёртку Гошечке не хочется, вот бегают и ноют, что как всё полохо и не контролируемо в Си, если в Гошечке вызывать os.Setenv. Давайте, починяйте ваш кривой glibc...

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

Худший сценарий — нестабильная работа софта, когда вобще не понятно, например, почему он то в одном каталоге временные файлы создаёт, то в другом, а то вобще пытается в несуществующем. А так будет, если TMPDIR --

шанс прочитать старое значение, наполовину переписанное новым.

Если библиотека исходно расчитана на однопоток и не вызвает setenv() или многопоток, но без setenv (по документации), то спокойно в её разных частях может использоваться getenv(«TMPDIR») без проблем. Работа setenv() вместе с getenv() даст в таком коде непонятные глюки. Уж лучше делать аналог микрософтовского getenv_r(), аналог getenv()+strdup() и флаг, который выставил и вызов getenv() приводит к сегфолту, чтобы старый код пересмотреть.

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

Это всё понятно, что лучше быть здоровым и счастливым. Вопрос в том, как обеспечить корректную работу getenv/setenv в многопоточных приложениях, которые уже написаны, здесь и сейчас. Которые состоят из множества компонентов, написанных разными людьми, вызывающих друг друга в произвольном порядке. И которые переписывать никто не спешит.

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

Ага, а с другой стороны люди с синдромом бога и полным отрицанием своих ошибок: «Если программист на C/C++ сделал CVE, то это неправильный программист на C/C++, нормальный бы не ошибся никогда».

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