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

Недавно я написал следующий глючный код на 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
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
3
38
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

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

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

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

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