Открыть сервис

Null pointer dereference

Null pointer dereference (разыменование нулевого указателя) — это ошибка выполнения программы, возникающая при попытке обратиться к области памяти по адресу, который равен нулю (или, в общем случае, не является валидным адресом, доступным для чтения или записи). В языках программирования, поддерживающих указатели (например, C, C++, Rust, Java), нулевой указатель обычно обозначает отсутствие объекта или неопределённое состояние. Разыменование такого указателя приводит к аварийному завершению программы (segmentation fault, access violation) или к неопределённому поведению, если операционная система не перехватывает обращение к нулевому адресу.

Причины возникновения

Null pointer dereference может возникать по нескольким причинам:

  • Логические ошибки в коде: программист не проверяет возвращаемое значение функции, которая может вернуть нулевой указатель (например, функция malloc в C при неудачном выделении памяти возвращает NULL).
  • Отсутствие инициализации: указатель объявлен, но не инициализирован, и по умолчанию получает значение NULL (в некоторых языках) или мусорное значение, которое может оказаться нулевым.
  • Ошибки при работе с динамической памятью: после освобождения памяти указатель не обнуляется, и последующее обращение к нему приводит к разыменованию висячего указателя (dangling pointer), который может указывать на нулевой адрес.
  • Уязвимости в программном обеспечении: злоумышленник может специально вызвать разыменование нулевого указателя, чтобы спровоцировать отказ в обслуживании (DoS) или, в редких случаях, выполнить произвольный код (например, при использовании эксплойтов, обходящих защиту памяти).

Механизм на уровне операционной системы

Современные операционные системы (Windows, Linux, macOS) защищают страницу памяти, начинающуюся с адреса 0. При попытке чтения или записи по этому адресу процессор генерирует исключение (page fault), которое операционная система преобразует в сигнал (например, SIGSEGV в Unix-подобных системах) или структурированное исключение (SEH в Windows). Программа, не обработавшая это исключение, аварийно завершается. В некоторых встраиваемых системах или в режиме ядра нулевой адрес может быть доступен, что приводит к неопределённому поведению, включая повреждение данных или выполнение произвольного кода.

Примеры на языках программирования

C и C++

``c int ptr = NULL; ptr = 42; // разыменование нулевого указателя — неопределённое поведение ``

В C и C++ разыменование нулевого указателя является неопределённым поведением, то есть программа может упасть, работать некорректно или даже выполнить опасные действия. Компиляторы (например, GCC, Clang) могут оптимизировать код, предполагая, что указатель не равен NULL, что иногда приводит к неожиданным ошибкам.

Java

В Java нулевой указатель представлен значением null. Разыменование null вызывает исключение NullPointerException (NPE), которое может быть перехвачено блоком try-catch.

``java String s = null; int length = s.length(); // выбрасывает NullPointerException ``

Rust

Rust стремится предотвратить разыменование нулевого указателя на этапе компиляции, используя тип Option<T>. Вместо нулевого указателя применяется Option::None. Для безопасного доступа к значению требуется явная обработка (match, unwrap, if let). Однако в unsafe-блоках Rust допускает работу с сырыми указателями, и разыменование нулевого указателя в таком контексте приводит к неопределённому поведению.

``rust let ptr: const i32 = std::ptr::null(); unsafe { let val = ptr; // неопределённое поведение } ``

Методы предотвращения

  • Проверка указателей на NULL: перед разыменованием необходимо явно проверять, не равен ли указатель нулю. В C/C++ это делается через if (ptr != NULL). В Java и C# — через if (obj != null).
  • Использование безопасных конструкций языка: в Rust — тип Option<T>, в Kotlinnullable-типы с оператором ?., в Swift — опциональные типы.
  • Инициализация указателей: при объявлении указатель следует сразу присвоить либо адрес существующего объекта, либо NULL (чтобы избежать мусорных значений).
  • Обнуление указателей после освобождения памяти: после вызова free (в C) или delete (в C++) указатель следует присвоить NULL.
  • Статический анализ кода: инструменты вроде Coverity, PVS-Studio, Clang Static Analyzer выявляют потенциальные разыменования нулевого указателя на этапе компиляции.
  • Динамический анализ: санитайзеры (AddressSanitizer, UndefinedBehaviorSanitizer) в GCC/Clang обнаруживают разыменования нулевого указателя во время выполнения.
  • Использование исключений: в языках с поддержкой исключений (Java, C#, Python) можно перехватывать исключения, связанные с нулевым указателем, но это не рекомендуется для предотвращения — лучше избегать самой ситуации.

Последствия

  • Аварийное завершение программы: наиболее частое последствие — программа падает с сообщением об ошибке (segmentation fault, access violation).
  • Уязвимости безопасности: в некоторых архитектурах и контекстах (например, в ядре ОС) разыменование нулевого указателя может привести к выполнению произвольного кода. Известные уязвимости: CVE-2009-1895 (NULL pointer dereference в ядре Linux), CVE-2017-6074 (в драйвере DCCP).
  • Неопределённое поведение: в C и C++ компилятор может оптимизировать код, исходя из предположения, что разыменование нулевого указателя не происходит, что приводит к трудноуловимым ошибкам.

Статистика

По данным исследований (например, отчёты компании Coverity, 2010-е годы), разыменование нулевого указателя входит в тройку наиболее распространённых дефектов в программном обеспечении на C и C++. В проектах на Java NullPointerException является одним из самых частых исключений времени выполнения. Введение в Kotlin и Swift nullable-типов позволило значительно снизить частоту таких ошибок в этих языках.

Интересные факты

  • В некоторых операционных системах (например, в ранних версиях MS-DOS) нулевой адрес был доступен для чтения и записи, что делало разыменование нулевого указателя не фатальным, но приводящим к повреждению данных.
  • В языке C++11 и более новых стандартах введён ключевое слово nullptr, которое является безопасной альтернативой макросу NULL (который в C++ может быть неявно преобразован в целочисленный ноль).
  • В проекте Linux kernel существует специальный механизм защиты — CONFIG_ILLEGAL_POINTER_VALUE, который позволяет настроить адрес, используемый для нулевого указателя, чтобы усложнить эксплуатацию уязвимостей.

Источники

  • ISO/IEC 9899:2011 (C11 standard) — раздел 6.5.3.2 (Address and indirection operators).
  • ISO/IEC 14882:2020 (C++20 standard) — раздел 8.3.2 (References).
  • «The Art of Software Security Assessment» — Mark Dowd, John McDonald, Justin Schuh.
  • «Secure Coding in C and C++» — Robert C. Seacord.
  • Common Weakness Enumeration: CWE-476 (NULL Pointer Dereference).
  • Документация Rust: The Rust Reference — «Behavior considered undefined».
  • Отчёты Coverity Scan — статистика дефектов в открытом ПО.

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →