1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/mirrors_Tencent-secguide

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
C,C++安全指南.md 45 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 30.11.2024 14:26 474be8d

1. C/C++ использование ошибок

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

Не следует напрямую использовать устаревшие функции для копирования строк и ввода данных, такие как strcpy, strcat, sprintf, wcscpy и mbscpy. Эти функции могут выводить длинные строки без ограничения их длины. Если возможно, рекомендуется использовать безопасные версии этих функций с суффиксом _s, или использовать версии функций с префиксом n, такие как snprintf или vsnprintf.

При использовании функций типа sscanf для обработки строковых входных данных необходимо строго ограничивать длину строки с помощью формата, такого как %10s, и убедиться, что строка заканчивается символом \0. Если возможно, также рекомендуется использовать безопасную версию этих функций.

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

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

Некоторые функции, такие как strncpy и _snprintf, могут быть небезопасными. strncpy не следует рассматривать как безопасную версию strcpy. strncpy может не добавлять завершающий ноль при копировании строки, если длина превышает указанный предел.

Аналогично, MSVC _snprintf также может не завершать строку нулём при превышении указанного предела. Это может привести к утечке соседних данных или сбою программы.

В следующем примере кода показано, как правильно использовать функцию _snprintf:

// Good
char a[4] = {0};
_snprintf(a, sizeof(a), "%s", "AAAA");
a[sizeof(a) - 1] = '\0';
foo = strlen(a);

Рекомендуется использовать более высокоуровневые компоненты, такие как строки (string) и векторы (vector), в C++, чтобы повысить читаемость и безопасность кода. Перевод текста на русский язык:

1.2 Создание функций класса процесса: стандарты безопасности

Функции запуска процессов, такие как system, WinExec, CreateProcess и ShellExecute, требуют строгой проверки их параметров.

Для запуска процесса необходимо добавить двойные кавычки. Пример неправильного использования:

// Bad
WinExec("D:\\program files\\my folder\\foobar.exe", SW_SHOW);

Если существует файл «D:\program files\my.exe», то запустится my.exe, а foobar.exe не запустится.

Пример правильного использования:

// Good
WinExec("\"D:\\program files\\my folder\\foobar.exe\"", SW_SHOW);

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

Пример неправильного использования:

// Bad
std::string cmdline = "calc ";
cmdline += user_input;
system(cmdline.c_str());

Например, если пользователь введёт «1+1 && ls», то фактически будут выполнены команды calc 1+1 и ls, что приведёт к внедрению команды.

Необходимо проверять ввод пользователя на наличие недопустимых данных.

Пример правильного использования:

// Good
std::string cmdline = "ls ";
cmdline += user_input;

if (cmdline.find_first_not_of("1234567890.+-*/e ") == std::string::npos)
  system(cmdline.c_str());
else
  warning(...);

Связанные уязвимости:

  • высокий риск — выполнение кода;
  • высокий риск — повышение привилегий.

1.3 Минимизация использования _alloca и массивов переменной длины

Объём памяти, используемый функциями _alloca и массивами переменной длины, неизвестен во время компиляции. Особенно это опасно при использовании в циклах, так как в зависимости от реализации компилятора это может привести к:

  1. переполнению стека, то есть отказу в обслуживании;
  2. недостатку проверки стековой памяти в реализации компилятора, что может привести к выделению памяти вне стека и повреждению памяти. Это особенно критично для программ с небольшим размером стека, таких как прошивки IoT-устройств. В C++ массивы переменной длины также считаются нестандартным расширением, и их использование запрещено в стандартах кодирования.

Пример неправильного использования:

// Bad
for (int i = 0; i < 100000; i++) {
  char* foo = (char *)_alloca(0x10000);
  ..do something with foo ..;
}

void Foo(int size) {
  char msg[size]; // Неконтролируемый риск переполнения стека!
}

Пример правильного использования:

// Good
// Использование динамически выделяемой памяти кучи
for (int i = 0; i < 100000; i++) {
  char * foo = (char *)malloc(0x10000);
  ..do something with foo ..;
  if (foo_is_no_longer_needed) {
    free(foo);
    foo = NULL;
  }
}

void Foo(int size) {
  std::string msg(size, '\0');  // C++
  char* msg = malloc(size);  // C
}

Связанные уязвимости:

  • низкий риск — отказ в обслуживании;
  • высокий риск — повреждение памяти.

1.4 Соответствие параметров функциям printf

Все функции printf, такие как sprintf, snprintf и vprintf, должны соответствовать управляющим символам и параметрам.

Пример неправильного использования:

// Bad
const int buf_size = 1000;
char buffer_send_to_remote_client[buf_size] = {0};

snprintf(buffer_send_to_remote_client, buf_size, "%d: %p", id, some_string);  // %p должен быть заменён на %s

buffer_send_to_remote_client[buf_size - 1] = '\0';
send_to_remote(buffer_send_to_remote_client);

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

Пример правильного использования:

// Good
const int buf_size = 1000;
char buffer_send_to_remote_client[buf_size] = {0};

snprintf(buffer_send_to_remote_client, buf_size, "%d: %s", id, some_string);

buffer_send_to_remote_client[buf_size - 1] = '\0';
send_to_remote(buffer_send_to_remote_client);

Предыдущее использование может позволить злоумышленнику получить часть исходного указателя сервера и использовать его для нарушения защиты ASLR, облегчая атаку на программу.

Связанная уязвимость:

  • средний риск — утечка информации.

1.5 Предотвращение утечки значений указателей (включая %p)

Все функции семейства printf должны предотвращать утечку строковых значений, форматированных с помощью этих функций, которые могут содержать информацию о структуре программы. Например, если строка с %p будет передана программе, это может нарушить защиту ASLR и облегчить атаку на программу.

Значение %p должно использоваться только внутри программы и не должно выводиться наружу или получаться извне каким-либо образом.

Пример неправильного использования:

// Bad
// Если это открытый API:
uint64_t GetUniqueObjectId(const Foo* pobject) {
  return (uint64_t)pobject;
}

Пример правильного использования:

// Good
uint64_t g_object_id = 0;

void Foo::Foo() {
  this->object_id_ = g_object_id++;
}

// Если это открытый API:
uint64_t GetUniqueObjectId(const Foo* object) {
  if (object)
    return object->object_id_;
  else
    error(...);
}

Связанное уязвимость:

  • средний риск — утечка информации.

1.6 Не следует использовать строки, контролируемые пользователем, в качестве параметров формата функций printf

Если строка контролируется пользователем, она может быть использована для выполнения произвольного вредоносного кода через %n %p и другие подобные параметры.

Особенно важно учитывать это в следующих случаях: имена Wi-Fi, имена устройств и т. д.

Неправильное использование:

snprintf(buf, sizeof(buf), wifi_name);

Правильное использование:

snprinf(buf, sizeof(buf), "%s", wifi_name);

Связанная уязвимость:

  • высокий риск — выполнение кода;
  • высокий риск — повреждение памяти;
  • средний риск — утечка информации;
  • низкий риск — отказ в обслуживании.

1.7 Удаление массивов: использование delete[] вместо delete

Оператор delete используется для удаления объектов, не являющихся массивами, а оператор delete[] — для удаления массивов. Они вызывают соответственно operator delete[] и operator delete.

Неправильное использование:

// Bad
Foo* b = new Foo[5];
delete b;  // trigger assert in DEBUG mode

Поведение, зависящее от неопределённого поведения компилятора, может возникнуть при вызове delete для указателя, возвращаемого оператором new[]. Код, который зависит от такого неопределённого поведения, является ошибочным.

Правильное использование:

// Good
Foo* b = new Foo[5];
delete[] b;

Использование строк, векторов и интеллектуальных указателей, таких как std::unique_ptr<T[]> в C++, может устранить большинство случаев использования delete[], делая код более понятным.

Связанная уязвимость:

  • высокий риск — повреждение памяти;
  • средний риск — логическая ошибка;
  • низкий риск — утечка памяти;
  • низкий риск — отказ в обслуживании.

1.8 Внимание к неявным преобразованиям знаков

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

Пример неправильного использования:

// 1
unsigned char a = 1;
unsigned char b = 2;

if (a - b < 0)  // a - b = -1 (signed int)
  a = 6;
else
  a = 8;

// 2
unsigned char a = 1;
unsigned short b = 2;

if (a - b < 0)  // a
``` ```
b = -1 (signed int)
a = 6;
else
  a = 8;

В данном фрагменте кода переменная a всегда будет равна 8.

// 3
unsigned int a = 1;
unsigned short b = 2;

if (a - b < 0)  // a - b = 0xffffffff (unsigned int)
  a = 6;
else
  a = 8;
  
// 4
unsigned int a = 1;
unsigned int b = 2;

if (a - b < 0)  // a - b = 0xffffffff (unsigned int)
  a = 6;
else
  a = 8;

Здесь переменная a, равная 1, преобразуется в тип unsigned int, и затем происходит вычитание из неё переменной b, равной 2. Результат операции сравнения помещается в переменную a. Так как результат вычитания не может быть меньше нуля для типа unsigned, то выполняется блок else, где переменной a присваивается значение 8. В итоге значение переменной a будет равно 8.

Если ожидается, что значение переменной а должно быть равно 8, то код содержит ошибку:

// Bad
unsigned short a = 1;
unsigned short b = 2;

if (a - b < 0)  // a - b = -1 (signed int)
  a = 6;
else
  a = 8;

Здесь переменные a и b имеют тип unsigned short, который является знаковым целочисленным типом. Поэтому при вычитании двух переменных получается значение -1. Это значение сравнивается с нулём, и так как оно меньше нуля, выполняется блок if, где переменной a присваивается значение 6. В результате значение переменной a равно 6, а не 8.

Правильный код:

// Good
unsigned short a = 1;
unsigned short b = 2;

if ((unsigned int)a - (unsigned int)b < 0)  // a - b = 0xffff (unsigned short)
  a = 6;
else
  a = 8;

Здесь значения переменных a и b приводятся к типу unsigned int перед выполнением операции вычитания. Это позволяет избежать переполнения знакового целочисленного типа. Затем результат операции сравнивается с нулем, и если он меньше нуля, то переменной a присваивается значение 6. Иначе переменной a присваивается значение 8. В итоге значение переменной a будет равно 8. ### Перевод текста на русский язык

foo_thread1() и foo_thread2():

void foo_thread1() {
  __sync_fetch_and_add(&g_somechar, 3);
}

void foo_thread2() {
  __sync_fetch_and_add(&g_somechar, 1);
}

Для кода C после C11 рекомендуется использовать стандартную библиотеку atomic. Для кода C++ после C++11 рекомендуется использовать std::atomic.

Связанные уязвимости:

  • Высокий риск — повреждение памяти;
  • Средний риск — логические проблемы.

3.2. Обязательное внимание к условиям гонки, вызванным обработчиком сигнала

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

  1. Проблемы возникают, когда в обработчике сигналов происходят нереентерабельные функции или операции, чувствительные к состоянию. Обработчик сигналов может быть вызван в любое время. Например, вызов free() в обработчике сигнала может привести к другому условию гонки, что приведёт к двойному освобождению. Даже если указатель установлен в NULL после освобождения памяти, всё ещё существует вероятность гонки между освобождением памяти и установкой указателя в NULL.
  2. Установка одного и того же обработчика сигнала для нескольких сигналов особенно проблематична, так как это может привести к повторному входу обработчика. Например, malloc() и free() являются нереентерабельными, потому что они могут использовать глобальные или статические структуры данных для управления памятью и косвенно используются функциями, такими как syslog(), которые могут привести к повреждению памяти и выполнению кода.
// Bad
char *log_message;

void Handler(int signum) {
  syslog(LOG_NOTICE, "%s\n", log_m_essage);
  free(log_message);
  sleep(10);
  exit(0);
}

int main (int argc, char* argv[]) {
  log_message = strdup(argv[1]);
  signal(SIGHUP, Handler);
  signal(SIGTERM, Handler);
  sleep(10);
}

Проблемы можно избежать следующими способами:

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

Связанные уязвимости:

  • Высокий риск — повреждение памяти;
  • Средний риск — логические проблемы.

3.3. Рекомендация: обратить внимание на условия гонки «время проверки — время использования» (TOCTOU)

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

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

Например, в следующем примере файл мог быть обновлён после lstat и до printf, особенно из-за задержки в printf.

struct stat *st;

lstat("...", st);

printf("foo");

if (st->st_mtimespec == ...) {
  printf("Now updating things\n");
  UpdateThings();
}

Хотя TOCTOU трудно исправить, существуют следующие смягчающие меры:

  1. Ограничить взаимодействие с файлами, принадлежащими нескольким процессам.
  2. Если необходимо совместно использовать доступ к ресурсам между несколькими процессами или потоками, попробуйте ограничить время между «проверкой» (CHECK) и «использованием» (USE) ресурсов, чтобы они были как можно ближе друг к другу. Это не решит проблему полностью, но может усложнить успешное проведение атаки.
  3. Повторно проверить ресурс после использования, чтобы убедиться, что операция выполнена правильно.
  4. Обеспечить наличие эффективных механизмов блокировки для защиты ресурсов. Однако убедитесь, что блокировка выполняется перед проверкой, а не после неё, чтобы ресурсы во время проверки совпадали с ресурсами во время использования.

Связанная уязвимость:

  • Высокий риск — повреждение памяти;
  • Средний риск — логическая проблема.

4.1. Обязательно: не хранить пароли пользователей и другую конфиденциальную информацию в открытом виде

Пароли пользователей должны быть хешированы с использованием алгоритмов, таких как Argon2, scrypt, bcrypt или pbkdf2, перед сохранением в системе хранения.

Пользовательские данные, требующие конфиденциальности, должны шифроваться во время передачи и храниться в зашифрованном виде. Шифрование во время передачи может осуществляться с помощью протоколов аутентифицированного шифрования, таких как HTTPS.

Данные, хранящиеся в зашифрованном виде, могут быть защищены с помощью решений, подобных SQLCipher.

4.2. Обязательно: конфиденциальные данные пользователя, находящиеся в памяти, должны быть безопасно удалены

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

Ошибка:

#include <openssl/crypto.h>
#include <unistd.h>

    {
        ...
        string user_password(100, '\0');
        snprintf(&user_password, "password: %s", user_password.size(), password_from_input);
        ...
    }

Правильно:

    {
        ...
        string user_password(100, '\0');
        snprintf(&user_password, "password: %s", user_password.size(), password_from_input);
        ...
        OPENSSL_cleanse(&user_password[0], user_password.size());
    }

Связанная уязвимость:

  • Высокий риск — утечка конфиденциальной информации.

4.3. Обязательно: функции класса rand() должны быть правильно инициализированы

Функции rand() не обеспечивают высокую степень случайности. Перед использованием необходимо инициализировать их с помощью srand(). Неинициализированные случайные числа могут сделать некоторые данные предсказуемыми.

В следующем коде значение foo фиксировано после выполнения. Оно эквивалентно srand(1); rand();

// Bad
int main() {
  int foo = rand();
  return 0;
}

После правильной инициализации значение foo будет случайным.

// Good

int main() {
  srand(time(0));
  int foo = rand();
  return 0;
}

Связанная уязвимость:

  • Высокий риск — логический недостаток.

4.4. Обязательно: в случаях, требующих усиленного шифрования, не следует использовать слабые PRNG-функции

При генерации ключей/IV/Nonce для алгоритмов, таких как AES/SM1/HMAC, или закрытых ключей для RSA/ECDSA/ECDH, где требуется высокая безопасность, необходимо использовать криптографически безопасные генераторы случайных чисел (CSPRNG), а не обычные генераторы случайных чисел без криптографической безопасности, такие как rand().

Рекомендуемые CSPRNG включают:

  • функцию RAND_bytes() в OpenSSL;
  • функцию randombytes_buf() в libsodium;
  • системный вызов getrandom() в Linux kernel;
  • чтение файлов /dev/urandom или /dev/random;
  • функцию SecRandomCopyBytes() в Apple IOS;
  • функции BCryptGenRandom(), CryptGenRandom() или RtlGenRandom() в Windows. ### Текст запроса

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

Перевод

#include <unistd.h>

{
    unsigned char key[16];
    if (1 != RAND_bytes(&key[0], sizeof(key))) {  //... 错误处理
        return -1;
    }

    AES_KEY aes_key;
    if (0 != AES_set_encrypt_key(&key[0], sizeof(key) * 8, &aes_key)) {
        // ... 错误处理
        return -1;
    }

    ...

    OPENSSL_cleanse(&key[0], sizeof(key));
}

В этом фрагменте кода используется функция RAND_bytes для генерации случайного массива байтов. Затем эти байты используются для создания ключа шифрования с помощью функции AES_set_encrypt_key. Наконец, массив байтов очищается с помощью функции OPENSSL_cleanse.

Перевод:

#include <unistd.h>

{
    unsigned char key[16]; // Массив из 16 байт
    if (1 != RAND_bytes(&key[0], sizeof(key))) { // Если генерация случайных байтов не удалась
        return -1; // Завершить программу с ошибкой
    }

    AES_KEY aes_key; // Ключ шифрования
    if (0 != AES_set_encrypt_key(&key[0], sizeof(key) * 8, &aes_key)) { // Установить ключ шифрования
        // Если установка ключа шифрования не удалась, завершить программу с ошибкой
        return -1;
    }

    ...

    OPENSSL_cleanse(&key[0], sizeof(key)); // Очистить массив байтов
}

Далее в тексте обсуждается проблема использования функции rand() для генерации случайных чисел в контексте безопасности. Автор отмечает, что функция rand() не является криптографически безопасной и может использоваться только в некритичных приложениях. Вместо неё рекомендуется использовать более надёжные методы генерации случайных чисел, такие как random_device в C++11 или библиотеки, предоставляющие криптографически безопасные генераторы случайных чисел, например OpenSSL.

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

Автор также подчёркивает важность правильного использования функций генерации случайных чисел при разработке программного обеспечения, особенно в контексте обеспечения безопасности данных. Текст на языке C++:

payload, sizeof(payload));
}

int main() {
  MyStruct dst_stuct;
  dst_struct.buf = (char*)user_controlled_value;
  Write(dst_struct);
  return 0;
}

Текст на русском языке:

Связанные уязвимости:

  • «Высокий риск — повреждение памяти».

7 Цифровые операции

7.1 Обязательное предотвращение переполнения целых чисел

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

Пример ошибки (этот пример приводит к переполнению целых чисел при расчёте):

const int kMicLen = 4;
// Переполнение целых чисел
void Foo() {
  int len = 1;
  char payload[10] = { 0 };
  char dst[10] = { 0 };
  // Bad, из-за того, что len меньше 4, это приводит к вычислению длины копирования, которая вызывает переполнение целых чисел
  // len - kMicLen == 0xfffffffd
  memcpy(dst, payload, len - kMicLen);
}

Правильный пример:

void Foo() {
  int len = 1;
  char payload[10] = { 0 };
  char dst[10] = { 0 };
  int size = len - kMicLen;
  // Копирование перед проверкой размера
  if (size > 0 && size < 10) {
    memcpy(dst, payload, size);
    printf("memcpy good\n");
  }
}

Связанная уязвимость:

«Высокий риск — повреждение памяти»

7.2 Обязательное предотвращение ошибок Off-By-One

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

Ошибка:

char firstname[20];
char lastname[20];
char fullname[40];

fullname[0] = '\0';

strncat(fullname, firstname, 20);
// Второй вызов strncat() может добавить ещё 20 символов. Если эти 20 символов не заканчиваются нулевым символом, существует проблема безопасности
strncat(fullname, lastname, 20);

Правильно:

char firstname[20];
char lastname[20];
char fullname[40];

fullname[0] = '\0';

// При использовании таких функций, как strncat(), необходимо оставить один нулевой символ в конце буфера для предотвращения проблем безопасности
strncat(fullname, firstname, sizeof(fullname) - strlen(fullname) - 1);
strncat(fullname, lastname, sizeof(fullname) - strlen(fullname) - 1);

Для кода C++ настоятельно рекомендуется использовать такие компоненты, как string, vector и другие, вместо использования исходных указателей и массивов.

Связанная уязвимость:

«Высокий риск — повреждение памяти»

7.3 Обязательное избегание ошибок порядка байтов

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

Связанная уязвимость:

«Средний риск — логическая ошибка»

7.4 Обязательная проверка деления на ноль

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

Ошибка:

int divide(int x, int y) {
  return x / y;
}

Правильно:

int divide(int x, int y) {
  if (y == 0) {
    throw DivideByZero;
  }
  return x / y;
}

Связанная уязвимость:

«Низкий риск — отказ в обслуживании»

7.5 Обязательное предотвращение неправильного преобразования типов данных

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

Пример ошибочного кода:

int Foo() {
  int len = 1;
  unsigned int size = 9;
  // 1 < 9 - 10 ? Из-за смешивания беззнакового и знакового чисел в операции, результат будет рассчитан как беззнаковое число
  if (len < size - 10) {
    printf("Bad\n");
  } else {
    printf("Good\n");
  }
}

Пример правильного кода:

void Foo() {
  // Унификация обоих чисел для вычислений со знаком
  int len = 1;
  int size = 9;
  if (len < size - 10) {
    printf("Bad\n");
  } else {
    printf("Good\n");
  }
}

Связанная уязвимость:

«Высокий риск — повреждение памяти», «Средний риск — логическая ошибка».

7.6 Обязательная проверка границ данных при сравнении размеров

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

Ошибка:

void Foo(int index) {
  int a[30] = {0};
  // Здесь index является целым числом, и только рассматривается случай, когда оно меньше размера массива, но не проверяется, является ли оно больше или равно нулю
  if (index < 30) {
    // Если index отрицательное, произойдёт выход за границы массива
    a[index] = 1;
  }
}

Правильно:

void Foo(int index) {
  int a[30] = {0};
  // Проверка максимального и минимального значений индекса
  if (index >= 0 && index < 30) {
    a[index] = 1;
  }
}

Связанная уязвимость:

«Высокий риск — повреждение памяти»

8 Операции с указателями

8.1 Рекомендация: проверка использования sizeof на указателях

За исключением тестирования текущей длины указателя, использование sizeof на указателе обычно не требуется.

Правильно:

size_t pointer_length = sizeof(void*);

Возможно ошибочное использование:

size_t structure_length = sizeof(Foo*);

Может быть:

size_t structure_length = sizeof(Foo);

Связанная уязвимость:

«Средний риск — логическая ошибка»

8.2 Обязательно: проверка сравнения массивов с нулём

Ошибочный код:

int a[3];
...;

if (a > 0)
  ...;

Это сравнение всегда будет истинным и эквивалентно:

int a[3];
...;

if (&a[0])
  ...;

Можно включить достаточно строгие предупреждения компилятора (например, -Waddress в GCC, которое уже включено в -Wall), и настроить их как ошибки, чтобы обнаруживать такие проблемы во время компиляции.

Связанная уязвимость:

«Средний риск — логическая ошибка»

8.3 Обязательно: избегать присвоения фиксированных адресов указателям

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

Связанная уязвимость:

«Высокий риск — повреждение памяти»

8.4 Обязательно: проверять пустые указатели

Ошибка:

*foo = 100;

if (!foo) {
  ERROR("foobar");
}

Правильно:

if (!foo) {
  ERROR("foobar");
}

*foo = 100;

Ошибка:

void Foo(char* bar) {
  *bar = '\0';
}

Правильно:

void Foo(char* bar) {
  if(bar)
    *bar = '\0';
  else
    ...;
}

Связанная уязвимость:

«Низкий риск — отказ в обслуживании»

8.5 Обязательно: устанавливать указатели в NULL после освобождения

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

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/mirrors_Tencent-secguide.git
git@api.gitlife.ru:oschina-mirror/mirrors_Tencent-secguide.git
oschina-mirror
mirrors_Tencent-secguide
mirrors_Tencent-secguide
main