LINUX.ORG.RU

Переполнение кучи в glibc и другое

 ,


0

4

Рунет сегодня ожил, а тут свежачок подвезли:

https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=6bd0e4efcc78f3c0115e5ea9739a1642807450da;hp=8aeec0eb5a18f9614d18156f9d6092b3525b818c

И ещё другие баги в glibc 2.37 типа порчи памяти в qsort.

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

Перемещено hobbit из talks

★★★★★

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

Этими вещами компилятор и оптимизатор занимается, а не программист.

ORLY? Давай посмотрим в код!

$ cd ~/src/linux
$ git grep cacheline_aligned | wc -l
766
$ git grep __aligned | wc -l
1594
cumvillain
()
Последнее исправление: cumvillain (всего исправлений: 1)
Ответ на: комментарий от untitl3d

Это значит, что указатель – это не число. И способ «прибавить единицу к указателю и получить следующий адрес» не работает. В стандарте Си указатели описываются отдельно от чисел. И операции с адресами определены отдельно.

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

Это значит, что указатель – это не число. И способ «прибавить единицу к указателю и получить следующий адрес» не работает. В стандарте Си указатели описываются отдельно от чисел. И операции с адресами определены отдельно.

Ну ты можешь сделать next = ptr + 5; Только это не будет пять байт :D

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

Ну, ты подтвердил мои слова. Все эти атрибуты это хинты отдельно взятому компилятору, а не язык Си. Да и сорцы-то ядра ОС, а не прикладухи. Я в основном про прикладное программирование, пусть даже системное.

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

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

Не. Арифметика не имеет отношения к модели памяти от слова совсем.

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

Ну, ты подтвердил мои слова.

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

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

Внутри это alignas(L1_CACHE_BYTES). alignas – часть стандарта, если что.

Да и сорцы-то ядра ОС, а не прикладухи. Я в основном про прикладное программирование, пусть даже системное.

Так прикладное или системное, лол? Но это не важно, потому что в том же NGINX тоже куча выравниваний.

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

Этими вещами компилятор и оптимизатор занимается, а не программист. На каком ЯП ты вообще видел, чтоб такое можно было руками делать и зачем?

Если этим компилятор занимается, почему вот эти два куска кода, хоть и делают одно и то же, имеют совершенно разную производительность?

int array[N][M];

for(int i = 0; i < N; i++)
  for(int j = 0; j < M; j++)
    array[i][j] = rand();
int array[N][M];

for(int j = 0; j < M; j++)
  for(int i = 0; i < N; i++)
    array[i][j] = rand();
hateyoufeel ★★★★★
()
Ответ на: комментарий от snake266

Короче, всё как всегда и держится на честном слове.

Джентльмены друг друга не обманывают

Да-да. Вот тебе нового UB в свежем стандарте и побольше!

Если вам не хватало UB в C, то вам принесли ещё

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

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

Линейность - это свойство адресации самой по себе, а не того, что за ней стоит, когда адресация представлена в виде сквозной нумерации всех доступных адресов, то есть например у процесса есть адресное пространство в 4ГБ, и все эти байты просто пронумерованы 32-битным числом. То, что эти адреса замаплены не пойми куда, какие-то вообще не замаплены или даже принципиально недоступны - не важно, это не свойство адресации а уже детали конкретного изделия.

Альтернатива - сегментная адресация, она была единственным вариантом на 16-битных процах, и вроде бы не поддерживается в 64-битном режиме новых. На 32-битном её можно использовать, но большинство ОС не используют. При сегментной адресации у ячеек нет сквозной нумерации, а есть набор блоков памяти (могут быть как фиксированного, так и меняющегося размера), каждый со своей нумерацией ячеек. Максимальный размер объекта, который можно нативно (а Си умеет только так, в отличие от например фортрана) хранить в такой памяти - это максимальный размер сегмента (на 16-битных процах сегменты были не больше 64кб, несмотря на то что всего было доступно 16МБ физической памяти на комп и около 1ГБ виртуальной на процесс). Речь, разумеется, про логические адреса, а физические адреса в памяти всегда линейные.

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

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

Вот только это не так. «Линейны» только сами аллокации, и то не без приколов (привет, padding).

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

rand() - это функция с сайд-эффектами (меняет глобальное состояние), так что оптимизатор не имеет права изменить порядок заполнения. C каким-нибудь a[i][j]=i+2*j; теоретически может и поменять.

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

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

ичо?

C каким-нибудь a[i][j]=i+2*j; теоретически может и поменять.

Может, но почему-то не меняет (в последний раз когда я проверял).

Окей, замени определение массива на int **arr и выделяй его динамически. Вот так уже не может даже теоретически.

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

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

Нативно-линейные «только аллокации» были с сегментной моделью, и то там не про malloc речь а про аналог mmap.

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

Оно означает два линейных адресных пространства транслируются друг в друга

Нет никакого линейного адресного пространства.

Линейность - это свойство адресации самой по себе, а не того, что за ней стоит, когда адресация представлена в виде сквозной нумерации всех доступных адресов, то есть например у процесса есть адресное пространство в 4ГБ, и все эти байты просто пронумерованы 32-битным числом. То, что эти адреса замаплены не пойми куда, какие-то вообще не замаплены или даже принципиально недоступны - не важно, это не свойство адресации а уже детали конкретного изделия.

Нет. В i386 используется, ЕМНИП, двухуровневая страничная адресация.

Альтернатива - сегментная адресация, она была единственным вариантом на 16-битных процах

Правильно. В реальном режиме используется сегментная адресация. И она тоже нелинейная.

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

Сишнику всё равно приходится учитывать кэши, страницы и прочую такую срань

Если хочешь выжимать производительность то да, приходится.

Но так как C не даёт к этому доступа вообще

Ну это враньё, очень даже даёт.

Если у тебя два значения в пределах одного блока выравненного по 64 байтам, они попадут в одну линию кэша и будет чики-пуки

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

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

Да, тоже посмеялся с этого. Слава богу я не использую realloc: либо выделяю память при старте (встройка), либо использую другой язык (x86).

Вообще я так понял, раньше стандарт realloc(ptr, 0) вполне себе позволял (если мне память не изменяет), а теперь они ломают кучу кода. Как то не в духе наших любимых комитетчиков, которые не дадут в обиду обратную совместимость.

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

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

Что такое «нативная реализация»? В x86 страничная адресация по самые помидоры, да ещё и с MMU. Линейная адресация она где-то совсем внизу, на уровне DRAM.

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

Ну попробуй обратиться к ячейке памяти 1. Что случится? :))))))

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

Правда при обращении к незампленной памяти сигнал прилетает. А так нормально, да.

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

Как ты умудрился страничную адресацию (половина ядерных API страницами с офсетами оперирует , лол) превратить в плоскую?

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

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

ENGLISH, MOTHERFUCKER, DO YOU SPEAK IT? Прочитай уже статью. Виртуальное адресное пространство нелинейно именно потому, что оно так реализовано в процессоре!

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

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

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

Слово «плоская» означает, что все указатели одинаково полезны и имеют одинаковые характеристики по скорости доступа и т.д.

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

Ну сколько можно…

Да пойми ж ты, люди не удовлетворением чьих-то идеалов занимаются а решением практических проблем. Им нужен язык, который поддерживает конструкции вида *(char*)0x1234 = 5;, но при этом поддерживает так же и структурное программирование (без второго требования это был бы ассемблер). Этот язык существует и называется Си. То, что какие-то комитет-графоманы пытаются приватизировать это название и закрепить за ним какой-то свой свод правил, каким он должен быть - не важно. Всем плевать.

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

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

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

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

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

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

Практическая проблема – это наполнение базы CVE?

Им нужен язык, который поддерживает конструкции вида (char)0x1234 = 5;,

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

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

Нет, не плевать. Потому что авторы компиляторов реализуют именно то, что написано в стандарте. А не то, что у тебя в голове варится. На мнения сишных шизофреников авторы компиляторов почему-то болт клали. Но треды в багзилле гцц о том, что ОЛОЛО НОВЫЙ ГЦЦ СЛОМАЛО МОЙ КОД! очень доставляют.

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

Так они все генерят какие-то баги. GCC генерит баги. Шланг генерит баги. MSVC тоже генерит баги! Что ж ты будешь делоць-то?

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

Прекращай демагогию.

Количество уровней в таблицах страничной трансляции тут вообще ни при чём, как и эта трансляция вообще.

Повторю ещё раз (хотя наверно бесполезно), линейность это исключительно тот факт, что у ячеек сквозная нумерация, и ничего кроме него. Это общепринятый в ИТ смысл этого слова, как для памяти (линейная против сегментной seg:offs из двух чисел, так и для жёстких дисков - линейная со сквозной нумерацией секторов против трёхмерной C:H:S). Нигде тот факт, что номера, видимые потребителю адресации, могут не совпадать с номерами на каком-то более низком уровне, значения не имеет.

У меня складывается впечатление что ты под этим словом подразумеваешь какие-то свои фантазии.

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

Опять придирки к очевидным фактам. Ты можешь сделать next = (void*)(((char*)ptr)+1);.

И вляпаешься в padding, ага. Или получишь невыравненный адрес и performance penalty. Обожаю сишных быдлокодеров, каждый раз как в первый раз.

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

В x86 страничная адресация

Страничная не адресация а трансляция. Адресация остаётся линейной.

Ну попробуй обратиться к ячейке памяти 1. Что случится?

Зависит от обстоятельств. В большинстве случаев сегфолт, но вообще на эту ячейку можно выделить память.

Правда при обращении к незампленной памяти сигнал прилетает

Язык тут ни при чём, это особенности платформы.

Как ты умудрился страничную адресацию (половина ядерных API страницами с офсетами оперирует , лол) превратить в плоскую?

Речь про то, что видно процессу.

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

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

Да не использует процессор эту твою сквозную нумерацию. Её не существует. Существуют страницы памяти. Существует многоуровневая адресация самих страниц памяти.

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

Слово «плоская» означает, что все указатели одинаково полезны и имеют одинаковые характеристики по скорости доступа и т.д.

Нет, не означает. Найди себе толковый словарь, что ли. Плоская = линейная = одномерная.

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

Это общепринятый в ИТ смысл этого слова, как для памяти (линейная против сегментной seg:offs из двух чисел, так и для жёстких дисков - линейная со сквозной нумерацией секторов против трёхмерной C:H:S).

Давай почитаем википедию?

Flat memory model or linear memory model refers to a memory addressing paradigm in which «memory appears to the program as a single contiguous address space.» The CPU can directly (and linearly) address all of the available memory locations without having to resort to any sort of bank switching, memory segmentation or paging schemes.

Я специально выделил жирненьким ту часть, которую ты упускаешь.

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

Слово «плоская» означает, что все указатели одинаково полезны и имеют одинаковые характеристики по скорости доступа и т.д.

Нет, не означает. Найди себе толковый словарь, что ли. Плоская = линейная = одномерная.

Ну лол же! Комментом выше тебе указали, что ты неправ.

Опять же, как в твою одномерность кеши-то укладываются? А никак не укладываются.

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

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

Имеет. Линейная адресация означает, что все адреса представлены в памяти в виде одномерного массива. Наличие страниц памяти и многоуровневой системы адресации этих страниц означает, что адресация нелинейная

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

Ты можешь сделать next = (void*)(((char*)ptr)+1);.

И это, кстати, не делает адресацию линейной :D

Потому что здесь ты используешь тот факт, что арифметика указателей для char * дает тебе сдвиг на один байт. Лулз в том, что это все ещё арифметика указателей, и ptr может быть хоть структурой с сотней полей, а ptr + 1 может оказаться меньшим физическим адресом. Да, так разумеется никто не делает, но абстракция арифметики указателей это позволяет. Потому что C поддерживает не только линейную память :D

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

У кешей нет адреса, они работают внутри проца.

Вообще, есть :)

Ты можешь отдельно замапить L2 и/или L3 кэш и развлекаться как хочешь. Нужна только плата с поддержкой Coreboot.

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

Но моё любимое – это всё равно NUMA. Потому что просто огромные кущи сишного кода ломаются и начинают глючить, если запустить их на двухпроцессорной системе и забыть прибить к одному из физических процессоров. Локинг в большей части сишного кода сделан просто чудовищно через жопу. Короче, ад и червиё.

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

https://en.cppreference.com/w/c/memory/realloc

Раньше было:

if new_size is zero, the behavior is implementation defined (null pointer may be returned (in which case the old memory block may or may not be freed), or some non-null pointer may be returned that may not be used to access storage).

Стало:

if new_size is zero, the behavior is undefined.

Теперь смотрим в glibc:

if size is equal to zero, and ptr is not NULL, then the call is equivalent to free(ptr)

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

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

Адрес байта в памяти записывается одним числом, что тебе тут непонятно то?

Ты вообще умеешь читать? Ты способен осознать прочитанное? Тебе, мать твою, ТРИ человека, пытаются объяснить, что адресация в современных процессорах нелинейная. Ты даже не пытаешься понять, что тебе пишут. Ты просто повторяешь, как попугай, что адрес байта записывается одним числом. Ты вообще способен думать и анализировать?

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

Я просто поражаюсь такому фантастическому упрямству. Такое ощущение, что он свято верит, что современные компьютеры являются абсолютно точными реализациями модели фон Неймана. А защитные механизмы психики не дают ему осознать, что это представление не соответствует действительности последние 50 лет

hateWin ★☆
()