Что делает эту ошибку недетерминированной

Недавно я написал следующий глючный код на C:

#include <stdio.h>

struct IpAddr {
  unsigned char a, b, c, d;
};

struct IpAddr ipv4_from_str (const char * str) {
  struct IpAddr res = {0};
  sscanf(str, "%d.%d.%d.%d", &res.a, &res.b, &res.c, &res.d);
  return res;
}

int main (int argc, char ** argv) {
  struct IpAddr ip = ipv4_from_str("192.168.1.1");
  printf("Read in ip: %d.%d.%d.%d\n", ip.a, ip.b, ip.c, ip.d);
  return 0;
}

Ошибка в том, что я использую %d в sscanf, предоставляя указатели на беззнаковый символ шириной 1 байт. %d принимает указатель int шириной 4 байта, и разница приводит к записи за пределами границ. out-of-bound запись однозначно ub, и программа рухнет.

Меня смущает непостоянный характер ошибки. После 1000 запусков программа выдает ошибку сегментации до в операторе печати в 50% случаев, а сегментацию ошибки после в остальных 50% случаев. Я не понимаю, почему это может измениться. В чем разница между двумя вызовами программы? У меня сложилось впечатление, что расположение памяти в стеке согласовано, и небольшие тестовые программы, которые я написал, похоже, подтверждают это. Разве это не так?

Я использую gcc v11.3.0 на Debian bookworm, ядро ​​5.14.16-1, и я скомпилировал без каких-либо установленных флагов.

Здесь — это вывод сборки моего компилятора для справки.

Рандомизация макета адресного пространства: techtarget.com/searchsecurity/definition/…

Barmar 16.05.2022 22:33

@ Бармар, вот и все. Отключение ASLR вызывает регулярные сбои. Думаю, я неправильно понял, что на самом деле делает ASLR. Если вы опубликуете ответ, я отмечу его как правильный, в противном случае я напишу объяснение и укажу вам.

Carson 16.05.2022 22:44

Ваша функция ipv4_from_str перезаписывает младшие 3 байта сохраненного указателя кадра rbp, поэтому по возвращении rbp поврежден. Если он указывает на неверный адрес, произойдет сбой на следующей же инструкции movl %eax, -4(%rbp). Если он указывает на действительный адрес, вы продолжаете использовать мусорные значения, но leave в конце main перемещает rbp в rsp. Затем адрес возврата извлекается из некоторого случайного места в памяти и, скорее всего, указывает на то, что не является исполняемым кодом (ноль в моем тестовом прогоне), поэтому в этот момент происходит сбой.

Nate Eldredge 16.05.2022 22:55
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
Четыре эффективных способа центрирования блочных элементов в CSS
Четыре эффективных способа центрирования блочных элементов в CSS
У каждого из нас бывали случаи, когда нам нужно отцентрировать блочный элемент, но мы не знаем, как это сделать. Даже если мы реализуем какой-то...
4
3
38
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Неопределенное поведение означает, что может произойти что угодно, даже противоречивые результаты.

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

См. также Почему я не получаю ошибку сегментации, когда пишу за конец массива?

Другие вопросы по теме