5. Написание безопасного кода

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

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

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

Конечно, обработка ошибок и проверка – это неприятно, неудобно и раздражает, и поэтому это презирается программистами по всему миру. Уже шестьдесят лет мы живем в эпоху компьютеров, а такие простые вещи, как успешность открытия файла или выделения памяти, по–прежнему не проверяются. Кажется, что просить программистов проверять каждый байт и каждый инвариант при чтении файла бесполезно. Но не делать этого – значить оставить программы уязвимыми для неверных данных. К счастью, этому можно помочь. При правильном использовании современные средства и технологии могут значительно облегчить головную боль по защите приложений от подобных ошибок. Широко используются три метода:

  • Контрольные суммы;
  • Подобные XML форматы на основе грамматики;
  • Код с проверкой, например, Java.

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

Такие надежные алгоритмы вычисления контрольной суммы, как MD5 и SHA, проделывают гораздо более сложные вычисления, чем просто взятие остатка от деления на 256. В языке Java есть классы java.security.DigestInputStream и java.security.DigestOutputStream, которые предоставляют удобные средства добавления к данным контрольной суммы. Использование одного из этих алгоритмов получения контрольной суммы снижает вероятность случайного повреждения до соотношения менее чем один к миллиарду (хотя по–прежнему остается возможность преднамеренных атак, как вы увидите далее).