Написание идиом Secure C и Secure C - PullRequest
41 голосов
/ 05 января 2010

«Обычный человек не хочет быть свободным. Он просто хочет быть в безопасности». - Н. Л. Менкен

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

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

Согласно Руководство по программированию GNU C getline:

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

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

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

Маллок возвращает ноль при ошибке:

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

/* 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 имеют размер inputStr и из inputStr не может быть прочитано больше символов, чем strlen (inputStr), кажется невозможным, учитывая все возможные значения для inputStr вызвать переполнение буфера

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

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

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

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

Ресурсы:

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

Ответы [ 7 ]

6 голосов
/ 05 января 2010

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

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

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

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

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

4 голосов
/ 09 января 2010
  1. Чтение из потока

Тот факт, что getline() «будет автоматически увеличивать блок памяти по мере необходимости», означает, что это может быть использовано как атака типа «отказ в обслуживании», поскольку было бы тривиально сгенерировать ввод, который был бы настолько длинным, что исчерпать доступную память для процесса (или, что еще хуже, системы!). Когда возникает нехватка памяти, другие уязвимости могут также вступить в игру. Поведение кода в нехватке / отсутствии памяти редко бывает хорошим, и его очень трудно предсказать. ИМХО, безопаснее установить разумные верхние границы для всего, особенно в чувствительных к безопасности приложениях.

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

  1. sscanf

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

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

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

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

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

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

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

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

1 голос
/ 06 января 2010

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

1 голос
/ 05 января 2010

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

0 голосов
/ 06 января 2010

Не используйте 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().


...