Написание защищенных C и безопасных идиом C

"Средний человек не хочет быть свободным, он просто хочет быть в безопасности". - H. Л. Менкен

Я пытаюсь написать очень безопасный C. Ниже я перечисляю некоторые методы, которые я использую и спрашиваю, они такие же безопасные, как я считаю. Пожалуйста, не стесняйтесь оторвать мой код/​​предубеждения в клочья. Любой ответ, который находит даже самую тривиальную уязвимость или учит меня новой идее, будет высоко оценен.

Чтение из потока:

В соответствии с учебником по программированию GNU C getline:

Функция getline будет автоматически увеличить блок памяти по мере необходимости, через realloc функции, поэтому никогда не бывает недостатка пространства - одна из причин, почему getline - это так безопасно. [..] Обратите внимание, что getline может безопасно обрабатывать вашу линию ввода, нет вопрос, как долго это длится.

Я предполагаю, что getline должен под всеми входами, предотвратить переполнение буфера, возникающее при чтении с поток.

  • Является ли мое предположение правильным? Существуют ли входы и/или схемы распределения, при которых это может привести к эксплойту? Например, если первый символ из потока - это странный символ управления, возможно 0x08 BACKSPACE (ctl-H).
  • Проделана ли какая-либо работа, чтобы математически доказать, что getline является безопасным?

Malloc возвращает Null при сбое:

Если malloc обнаруживает ошибку, malloc возвращает указатель NULL. Это представляет угрозу безопасности, поскольку все еще можно применить арифметику указателя к указателю NULL (0x0), таким образом wikipedia рекомендует

/* Allocate space for an array with ten elements of type int. */
int *ptr = (int*)malloc(10 * sizeof (int));
if (ptr == NULL) {
    /* Memory could not be allocated, the program should handle 
       the error here as appropriate. */
} 

Безопасный sscanf:

При использовании sscanf Я привык выделять строки, которые нужно удалить, до размера строка ввода, надеюсь, избегает возможности перерасхода. Например:

const char *inputStr = "a01234b4567c";
const char *formatStr = "a%[0-9]b%[0-9]c":
char *str1[strlen(inputStr)];
char *str2[strlen(inputStr)];

sscanf(inputStr, formatStr, str1, str2);

Поскольку str1 и str2 - это размер вводаStr, и не больше символов, чем strlen (inputStr), можно читать из inputStr, это кажется невозможным, если задано все возможные значения для вводаStr, чтобы вызвать буфер переполнение?

  • Правильно ли я? Есть ли странные угловые случаи, о которых я не думал?
  • Есть ли лучшие способы написать это? Библиотеки, которые уже решили это?

Общие вопросы:

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

  • Какие еще безопасные C-идиомы существуют?
  • Какие угловые случаи мне нужны, чтобы всегда проверять?
  • Как я могу написать модульные тесты для обеспечения соблюдения этих правил?
  • Как я могу применять ограничения в тестируемости или доказуемо правильно?
  • Любая рекомендуемая статическая/динамическая аналитическая техника или инструменты для C?
  • Какими безопасными методами вы придерживаетесь, и как вы их оправдываете себе и другим?

Ресурсы:

Многие из ресурсов были заимствованы из ответов.

Ответ 1

  • Чтение из потока

Тот факт, что getline() "будет автоматически увеличивать блок памяти по мере необходимости" означает, что это может быть использовано как атака отказа в обслуживании, так как было бы тривиально генерировать ввод, который был так долго исчерпайте доступную память для процесса (или, что еще хуже, систему!). После возникновения проблемы нехватки памяти другие уязвимости также могут вступить в игру. Поведение кода в low/no memory редко бывает приятным и очень трудно предсказать. IMHO безопаснее устанавливать разумные верхние границы во всем, особенно в приложениях, чувствительных к безопасности.

Кроме того (как вы ожидаете, отметив специальные символы), getline() предоставляет только буфер; он не дает никаких гарантий относительно содержимого буфера (поскольку безопасность полностью зависит от приложения). Поэтому дезинфекция ввода по-прежнему является важной частью обработки и проверки пользовательских данных.

  1. sscanf

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

  1. Общие комментарии

    • Доступны функции Fuzzing, которые генерируют случайный ввод (как действительный, так и недействительный), который можно использовать для проверки обработки ввода
    • Управление буфером имеет решающее значение: переполнение буфера, недополнение, нехватка памяти
    • Условия гонки могут быть использованы в защищенном коде
    • Двоичные файлы могут быть обработаны для ввода недопустимых значений или негабаритных значений в заголовки, поэтому код формата файла должен быть прочным и не предполагать, что бинарные данные действительны.
    • Временные файлы часто могут быть источником проблем безопасности и должны быть тщательно управляемы
    • Вставка кода может использоваться для замены вызовов библиотеки системы или среды выполнения вредоносными версиями
    • Плагины предоставляют огромный вектор для атаки
    • В качестве общего принципа я хотел бы предложить четко определенные интерфейсы, в которых пользовательские данные (или любые данные извне приложения) считаются недействительными и враждебными до тех пор, пока они не будут обработаны, дезинфицированы и не проверены, и единственный способ ввода пользовательских данных приложение

Ответ 2

Я думаю, что ваш пример sscanf ошибочен. Он может все еще переполняться при использовании таким образом.

Попробуйте это, в котором указано максимальное количество прочитанных байтов:

void main(int argc, char **argv)
{
  char buf[256];
  sscanf(argv[0], "%255s", &buf);
}

Взгляните на эту статью IBM dev о защите от переполнения буфера.

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

Ответ 3

G'day,

Хорошее место, чтобы начать смотреть на это, Дэвид Уилер превосходно защищает сайт кодирования.

Его бесплатная онлайн-книга "" Безопасное программирование для Linux и Unix HOWTO "" - отличный ресурс, который регулярно обновляется.

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

Любой инструмент статического анализа, такой как Flawfinder, является всего лишь инструментом. Никакой инструмент не может заменить человеческую мысль! Короче говоря, "дурак с инструментом все еще дурак" . Ошибочно думать, что инструменты анализа (например, flawfinder) заменяют обучение и знание безопасности

Я лично использовал ресурсы Дэвида уже несколько лет и считаю их отличными.

НТН

веселит,

Ответ 5

Янник Мой разработал самую слабую систему предустановок Hoare/Floyd для C во время его PhD и применил ее к библиотеке управляемых строк CERT. Он нашел множество ошибок (см. Стр. 197 его мемуаров). Хорошей новостью является то, что библиотека теперь более безопасна для его работы.

Ответ 6

Вы также можете посмотреть на веб-сайте Les Hatton здесь и в его книге Безопаснее C, которую вы можете получить из Амазонки.

Ответ 7

Не используйте gets() для ввода, используйте fgets(). Чтобы использовать fgets(), если ваш буфер автоматически назначен (т.е. "В стеке" ), используйте эту идиому:

char buf[N];
...
if (fgets(buf, sizeof buf, fp) != NULL)

Это будет продолжаться, если вы решите изменить размер buf. Я предпочитаю эту форму:

#define N whatever
char buf[N];
if (fgets(buf, N, fp) != NULL)

потому что первая форма использует buf для определения второго аргумента и более ясна.


Проверить возвращаемое значение fclose().