LINUX.ORG.RU

Почему надо проверять malloc на NULL

 , ,


1

3

Вообще выделение памяти проверять.

А вот потому что:

#include <stdio.h>
int main()
{
  unsigned char *a = NULL;
  int b = 0;
  unsigned long n = (unsigned long)&b;
 
  printf("b = %d, n = %lu\n",b,n);
  a[n]=1;
  printf("b = %d\n",b);
  return 0;
}

Запуск

$ gcc nullptr.c -o nullptr
$ ./nullptr 
b = 0, n = 140731726099148
b = 1

Запускалось в 64-битном (x86_64) Linux. Как видно, никаких сегфолтов и прочих ошибок. Конечно, адрес в районе 127 Тб в примере далеко за пределами доступного почти на всех компьютерах, но нет никаких гарантий, что на какой-то системе с каким-то компилятором и настройками среды значение не окажется более доступным. Могут быть и другие архитектуры (32-битные например), если запускать от root'а, то в начало может быть разрешена запись и там иметься память процесса. Или ещё какие-то варианты.

shdown, monk, liksys, Xenius - я думаю вам понравится. Пример сочинился по ходу чтения обсуждения Вышло издание 2,92 книги «Программирование: введение в профессию» А. В. Столярова (комментарий)

★★★★★

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

Ну они не прямо так сделали:

Перехватываем libc’шный malloc()

Просто обернули вызов glibc-шного аллокатора в свою функцию.

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

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

при определённой изобретательности

Вопрос в том, какова вероятность, что процесс будет прибит OOM killer’ом vs malloc вернёт NULL, если память закончится.

В продакшене никогда не ловили, но вот на десктопе у меня если что-то кривое всю память отжирает, chromium постоянно сообщает, что процесс вкладки прибит 9-ым сигналом (SIGKILL; это значит, что пришёл OOM killer — всякое по типу systemd-oomd у меня выключено).

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

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

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

Потому что у тебя многозадачная ОС, это как минимум.

Многозадачные ОС были с 1969. А выдача больше памяти, чем есть, появилась где-то с Windows NT. Причём там это было сделано по уму: нехватка памяти не убивала процесс, а увеличивала своп. А Linux выдает всем сколько запросят, а потом прибивает кого-нибудь.

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

Видимо, придётся разжовывать прям до алфавита.

Запрос количества свободной памяти не имеет смысла, потому что здесь by design содержится race condition. Какое число бы тебе ни вернули, на результат полагаться нельзя.

При чём тут Windows NT, оверкоммит и прочая шизофазия, не ясно, потому что вопрос ветки был:

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

И правильно, что не предусмотрена.

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

Запрос количества свободной памяти не имеет смысла, потому что здесь by design содержится race condition. Какое число бы тебе ни вернули, на результат полагаться нельзя.

Если бы malloc работал правильно, он был бы и не нужен.

И правильно, что не предусмотрена.

В Linux есть /proc/meminfo.

monk ★★★★★
()

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

legolegs ★★★★★
()

Как видно, никаких сегфолтов и прочих ошибок.

Потому что в программе нет ошибок.

Конечно, адрес в районе 127 Тб в примере далеко за пределами доступного почти на всех компьютерах,

Где вы увидели адрес в районе 127 тб? Нет там такого. Похоже что автор не понимает строку a[n]=1

Разберём: здесь a равно 0, n равен адресу b. Это эквивалентно [&b]=1, то есть в b записали 1. Никакой ошибки.

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

Именно так почти всегда и нормально. g_malloc примерно так и реализован.

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

Где вы увидели адрес в районе 127 тб? Нет там такого.

Числовое значение &b.

Разберём: здесь a равно 0, n равен адресу b. Это эквивалентно [&b]=1, то есть в b записали 1. Никакой ошибки.

Вообще-то UB. Потому что доступ к b через адрес a стандартом запрещён. А смысл статьи в том и есть, что когда пишешь

a = malloc(...);
a[x] = y;

в общем случае нет никакой гарантии, что если a = NULL, то a[x] = y вызовет завершение программы.

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

нет никакой гарантии, что если a = NULL, то a[x] = y вызовет завершение программы.

Ну так если мы сошли с нулевой страницы, это должно быть очевидно?

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

Кроме просадки производительности в РБВ?

Там почти вся просадка за счёт длины указателей (и скорости памяти). Проверка безопасности идёт асинхронно с самим доступом к памяти.

Вот считал такты: Энтузиасты дизассемблировали микрокод i386 и создали открытый CPU z386 (комментарий)

13 тактов в обычном против 16 в РБВ. Но это в предположении, что L1 хватает. Удвоение длины указателей заставляет чаще обращаться к медленной памяти.

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

Я имел в виду, что если есть какой-то алгоритм, активно работающий с указателями и в нём в обычном режиме 64КБ указателей, то при переводе на РБВ будет заметное падение, так как рабочий набор станет 128КБ.

monk ★★★★★
()

Почему надо проверять malloc на NULL

NULL - это просто число (а адрес, это число), обозначающее отсутствие адреса, т.е. по данному адресу любая работа не имеет смысла. Не всегда для этой цели можно использовать NULL, например, mmap() возвращает число MAP_FAILED. Вообще, кроме NULL используется очень много разных чисел - https://ru.wikipedia.org/wiki/Hexspeak

ИМХО, поэтому, на NULL надо проверять тогда, когда это имеет смысл - в интерфейсных функциях. Т.е. в тех функциях, которыми будут пользоваться другие.

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


typedef struct
{
    int Var;
} MyObject_t;

void MyObjectInit(MyObject_t *this)
{
    if (this == NULL) // Такое условие сохранит работоспособность при любом значении NULL.
        return;
    this->Var = 0;
}

MyObject_t *MyObjectCreate()
{
    MyObject_t *this = malloc(sizeof(*this));
    MyObjectInit(this);

    return this;
}

void MyObjectFree(MyObject_t **this_ptr)
{
    MyObject_t *this;

    if (this_ptr == NULL)
        return;
    this = (*this_ptr);
    (*this_ptr) = NULL;
    if (this == NULL)
        return;

    free(this);
}

void MyObjectVarShow(MyObject_t *this)
{
    if (this == NULL)
        return;

    printf("0x%p.MyObject.Var = %i\n", this, this->Var);
}

int main()
{
    MyObject_t *MyObject;

    MyObject = MyObjectCreate(); // MyObject = NULL;
    MyObjectVarShow(MyObject);
    MyObjectFree(&MyObject);
}

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

Если к адресу прибавить 0, то будет тот же адрес. Вот это открытие.

Смысл не в этом, а в наглядной иллюстрации, что результат выделения памяти надо проверять. Не уповая на то, что нуля не будет или OOM Killer прибьет или еще на что-то. Даже при том, что на x86_64 приняты определенные меры (хотя бы очень большой адрес по умолчанию, чтобы случайных индексов было поменьше) , чтобы защититься от сбоев в этом случае.

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

Ну так если мы сошли с нулевой страницы, это должно быть очевидно?

Я специально написал пример так, чтобы это было очевидно. Потому что столкнулся с тем, что даже некоторые авторы учебников программирования, похоже, что не понимают, чем может быть чревато отсутствие проверки выделения памяти. В их учебнике в примерах результат malloc нигде не проверяется, а сами они сначала вообще были уверены, что на совремённых системах NULL получить невозможно, а когда их убедили, что таки возможно, неудобство видят только в необходимости проверки SIGSEGV и обещают подумать над отношением к результату malloc. С 2017-го года думают http://stolyarov.info/guestbook/archive/2#cmt127

anonymous_incognito ★★★★★
() автор топика
Ответ на: комментарий от anonymous_incognito
#include <stdio.h>
#include <stdint.h>

int main(void)
{
	uint32_t stack_var = 0;
	uint32_t *p = &stack_var;
        *p = 1;
	printf("%u\n", 0[p]);
	printf("%u\n", *(p + (uint32_t)0));
	return 0;
}
IvanRia
()
Ответ на: комментарий от luke

Это вам не буржуйская ересь! Это наш посконно-домотканый Режим Безопасных Вычислений! )
Но в целом похоже, кроме того, что Afaik для тегов используется ещё и половина ECC битов.

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

CHERI?

А для него вообще быстрая реализация существует?

Просто если в РБВ

  • указатель на начало выделенной области памяти/объекта (64 бита);
  • размер выделенной памяти/объекта (32 бита);
  • смещение относительно начала (32 бита).

и все проверки даже для L1 не тратят дополнительных тактов.

То в CHERI границы в формате плавающей точки. По-моему, преобразование https://riscv.github.io/riscv-cheri/#section_cap_bounds_decoding даже теоретически в несколько тактов не утрамбовать.

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

нет никакой гарантии, что если a = NULL, то a[x] = y вызовет завершение программы.

:-) помниться что в dos (real-mode x86) это вызовет установку вектора прерывания номер x

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

Только если int far *a или int huge *a. Если обычный указатель, то запишешь что-то в начале сегмента кода.

monk ★★★★★
()

Вообще выделение памяти проверять.

Смотрите, сишники познают мир.

А вот потому что

Нет, не поэтому.

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

Тогда при разыменовании NULL на уровне ассемблера не будет сегфолта.

Под фряхой и под рутом:

# ./map-zero-page
mmap: Invalid argument
Ошибка сегментации(core dumped)
iron ★★★★★
()
Ответ на: комментарий от shdown

Попробуйте сделать sysctl security.bsd.map_at_zero=1.

Это читерство)

# ./map-zero-page
0x0
x = 0
iron ★★★★★
()

Почему надо проверять malloc на NULL…

ты не поверишь - все Сишные функции нужно проверять! Это в плюсах православные exception

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

когда их убедили, что таки возможно, неудобство видят только в необходимости проверки SIGSEGV и обещают подумать над отношением к результату malloc. С 2017-го года думают http://stolyarov.info/guestbook/archive/2#cmt127

Мы тут говорим об отборном шизофренике, который не смог нормально свою CMS написать и орал во всё горло, что мерзкие утырки суют ему неправильный HTML, от чего всё едет и падает.

почему я не приемлю технические стандарты как явление и называю их создателей (поголовно всех, во всяком случае, ныне действующих) опасными международными террористами;

Чуваку «террористы» нормально программировать мешают. И, самое чудовищное, он ведь в МГУ преподавал.

yorshka
()

Кому вообще может прийти в голову НЕ проверять результат malloc? Какая-то тема странная. malloc может возвращать NULL во множестве ситуаций.

Если не хочется думать - хотя бы свою обёртку над malloc надо сделать и вызывать abort().

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

malloc может возвращать NULL во множестве ситуаций.

Я знаю две:

  1. когда система отказывает процессу в аллокации новой памяти.
  2. когда мы тестируем ПО на корректность обработки возвращаемых malloc’ом NULLов.

А какие другие?

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

Я думаю, что это множество ситуаций укладывается в твои два пункта.

Я, конкретно, сталкивался с такими ситуациями:

  1. Когда программа хочет памяти больше, чем есть на компьютере (RAM + swap).

  2. Когда программа работает в контейнере, которому выделено мало памяти (типичная ситуация на проде) и программе хочется больше.

  3. Когда программа запущена с ulimit-ограничениями.

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

А для него вообще быстрая реализация существует?

Кодасип говорят что их X730 от A730 отстаёт менее чем на пять процентов исходя из маркетинговых материалов. По числу транзисторов те же пять процентов. Но это только процессор, а в чип ещё модуль тегов для DDR пихать придётся, скорее всего.

То в CHERI границы в формате плавающей точки

Плавающую точку можно и за такт делать, был бы кремний. Для 9-стадийного суперскаляра всё более-менее в конвейер помещается, судя по опыту X730.

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

Кому вообще может прийти в голову НЕ проверять результат malloc? Какая-то тема странная. malloc может возвращать NULL во множестве ситуаций.

Как показывает практика, на удивление многим приходит в голову НЕ проверять. См. комментарий Андрей Карпова перед моим.

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

на удивление многим приходит в голову НЕ проверять.

Люди которые с пеной у рта кричат «конечно проверять!» ловко уходят от вопроса «а что собственно с этими ошибками делать?». И тут мы приходим к одному из двух сценариев:

  1. если обработка сводится к разновидности abort() то почему она не делается централизовано?
  2. если обработка нетривиальна - а как оно вообще тестировалось? При попытке «продать» мне вот это я бы требовал доказательств демонстрирующих что абсолютно каждый из этих «if»ов был hit во время тестирования. Было бы даже интересно посмотреть как народ выкрутиться пытался бы.
bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 1)
Ответ на: комментарий от bugfixer
  1. Есть g_malloc. Можно считать, централизованно.

  2. В смысле? Например, если это какая-то СУБД, то перед падением желательно привести все данные в целостное состояние (как правило, это можно сделать без дополнительных запросов памяти).

И в чём проблема протестировать? Запускаешь под ulimit и смотришь, как программа аккуратно завершается, а не падает в середине записи блока в файл.

В эпоху DOS вообще были программы, которые на NULL от malloc начинали использовать оверлеи и сбрасывать редкоиспользуемые данные в файлы (эдакий swap для отдельно взятой программы).

Сейчас на Linux смысла почти нет, так как ОС может программу прибить вообще в любой момент (недавно наблюдал, как нагруженная СУБД убивалась из-за того, что антивирус слишком много памяти использовал).

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

И в чём проблема протестировать?

В том что callsites много и протестировать нужно каждый. И сценариев довольно много, от «все аллокации фейлятся» до «аллокации длиннее X [иногда] фейлятся». Это неподъёмная работа с практической точки зрения.

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

Это неподъёмная работа с практической точки зрения.

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

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

Это неподъёмная работа с практической точки зрения.

Допустим, у вас при получении NULL из malloc-а зовется abort. Как вы обеспечиваете целостность данных с которыми работает приложение при наличии abort-ов в произвольный момент времени?

Эту самую целостность тестировать не нужно?

eao197 ★★★★★
()

а ничего так что если памяти нет у malloc (он NULL вернул), то с большой вероятностью она сейчас кончится и на стеке ?

при ошибках с памятью кроме abort() и longjmp() в аварийку делать ничего нельзя.

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

При попытке «продать» мне вот это я бы требовал доказательств демонстрирующих что абсолютно каждый из этих «if»ов был hit во время тестирования. Было бы даже интересно посмотреть как народ выкрутиться пытался бы.

А потребовать доказательств, что данным не настанет кабзда из-за работы с нулевым указателем или DOS сервису не будет не хочешь?

anonymous
()
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария