Читаем 19 смертных грехов, угрожающих безопасности программ полностью

Как правило, ничего неожиданного при этом не происходит, и неявное приведение в результате применения операторов может даже помочь избежать некоторых переполнений. Но бывают и сюрпризы. Во–первых, в системах, где имеется тип 64–разрядного целого, было бы логично ожидать, что коль скоро unsigned short и signed short приводятся к int, а операторное приведение не нарушает корректность результата (по крайней мере, если вы потом не выполняете понижающего приведения до 16 битов), то unsigned int и signed int будут приводиться к 64–разрядному типу (_int64). Если вы думаете, что все так и работает, то вынуждены вас разочаровать – по крайней мере, до той поры, когда стандарт C/C++ не станет трактовать 64–разрядные целые так же, как остальные.

Вторая неожиданность заключается в том, что поведение изменяется еще и в зависимости от оператора. Все арифметические операторы (+, – ,*,/,%) подчиняются приведенным выше правилам. Но им же подчиняются и поразрядные бинарные операторы (&,|,А); поэтому (unsigned short) | (unsigned short) дает int! Те же правила в языке С распространяются на булевские операторы (&&,|| и !), тогда как в С++ возвращается значение встроенного типа bool. Дополнительную путаницу вносит тот факт, что одни унарные операторы модифицируют тип, а другие–нет. Оператор дополнения до единицы (~) изменяет тип результата, поэтому -((unsigned short)0) дает int, тогда как операторы префиксного и постфиксного инкремента и декремента (++, – ) типа не меняют.

Один программист с многолетним стажем работы предложил следующий код для проверки того, возникнет ли переполнение при сложении двух 16–разрядных целых без знака:

...

bool IsValidAddition(unsigned short x, unsigned short y)

{

if(x + y < x)

return false;

return true;

}

Вроде бы должно работать. Если результат сложения двух положительных чисел оказывается меньше какого–то слагаемого, очевидно, что–то не в порядке. Точно такой же код должен работать и для чисел типа unsigned long. Увы, программист не учел, что компилятор оптимизирует всю функцию так, что она будет возвращать true.

Вспомним из предыдущего обсуждения, какой тип имеет результат операции unsigned short + unsigned short. Это int. Каковы бы ни были значения целых без знака, результат никогда не может переполнить тип int, поэтому сложение всегда выполняется корректно. Далее int сравнивается с unsigned short. Значение х приводится к типу int и, стало быть, никогда не будет больше х + у. Чтобы исправить код, нужно лишь привести результат обратно к unsigned short:

...

if((unsigned short)(x + y) < x)

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

Арифметические операции

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

Сложение и вычитание. Очевидная проблема при выполнении этих операций – возможность перехода через верхнюю и нижнюю границы объявленного типа. Например, если речь идет о 8–разрядных числах без знака, то 255 + 1 = 0. Или: 2 – 3 = 255. В случае 8–разрядных чисел со знаком 127 + 1 = -128. Менее очевидная ошибка возникает, когда числа со знаком используются для представления размеров. Если кто–то подсунет вам число–20, вы прибавите его к 50, получите 30, выделите буфер длиной 30 байтов, а затем попытаетесь скопировать в него 50 байтов. Все, вы стали жертвой хакера. Помните, особенно при программировании на языке, где переполнить целое трудно или невозможно, – что вычитание из положительного числа, в результате которого получается число, меньшее исходного, – это допустимая операция, и никакого исключения вследствие переполнения не будет, но поток исполнения программы может отличаться от ожидаемого. Если вы предварительно не проверили, что входные данные попадают в положенный диапазон, и не уверены на сто процентов, что переполнение невозможно, контролируйте каждую операцию.

Перейти на страницу:

Похожие книги

Основы программирования в Linux
Основы программирования в Linux

В четвертом издании популярного руководства даны основы программирования в операционной системе Linux. Рассмотрены: использование библиотек C/C++ и стан­дартных средств разработки, организация системных вызовов, файловый ввод/вывод, взаимодействие процессов, программирование средствами командной оболочки, создание графических пользовательских интерфейсов с помощью инструментальных средств GTK+ или Qt, применение сокетов и др. Описана компиляция программ, их компоновка c библиотеками и работа с терминальным вводом/выводом. Даны приемы написания приложений в средах GNOME® и KDE®, хранения данных с использованием СУБД MySQL® и отладки программ. Книга хорошо структурирована, что делает обучение легким и быстрым. Для начинающих Linux-программистов

Нейл Мэтью , Ричард Стоунс , Татьяна Коротяева

ОС и Сети / Программирование / Книги по IT
97 этюдов для архитекторов программных систем
97 этюдов для архитекторов программных систем

Успешная карьера архитектора программного обеспечения требует хорошего владения как технической, так и деловой сторонами вопросов, связанных с проектированием архитектуры. В этой необычной книге ведущие архитекторы ПО со всего света обсуждают важные принципы разработки, выходящие далеко за пределы чисто технических вопросов.?Архитектор ПО выполняет роль посредника между командой разработчиков и бизнес-руководством компании, поэтому чтобы добиться успеха в этой профессии, необходимо не только овладеть различными технологиями, но и обеспечить работу над проектом в соответствии с бизнес-целями. В книге более 50 архитекторов рассказывают о том, что считают самым важным в своей работе, дают советы, как организовать общение с другими участниками проекта, как снизить сложность архитектуры, как оказывать поддержку разработчикам. Они щедро делятся множеством полезных идей и приемов, которые вынесли из своего многолетнего опыта. Авторы надеются, что книга станет источником вдохновения и руководством к действию для многих профессиональных программистов.

Билл де Ора , Майкл Хайгард , Нил Форд

Программирование, программы, базы данных / Базы данных / Программирование / Книги по IT
Программист-прагматик. Путь от подмастерья к мастеру
Программист-прагматик. Путь от подмастерья к мастеру

Находясь на переднем крае программирования, книга "Программист-прагматик. Путь от подмастерья к мастеру" абстрагируется от всевозрастающей специализации и технических тонкостей разработки программ на современном уровне, чтобы исследовать суть процесса – требования к работоспособной и поддерживаемой программе, приводящей пользователей в восторг. Книга охватывает различные темы – от личной ответственности и карьерного роста до архитектурных методик, придающих программам гибкость и простоту в адаптации и повторном использовании.Прочитав эту книгу, вы научитесь:Бороться с недостатками программного обеспечения;Избегать ловушек, связанных с дублированием знания;Создавать гибкие, динамичные и адаптируемые программы;Избегать программирования в расчете на совпадение;Защищать вашу программу при помощи контрактов, утверждений и исключений;Собирать реальные требования;Осуществлять безжалостное и эффективное тестирование;Приводить в восторг ваших пользователей;Формировать команды из программистов-прагматиков и с помощью автоматизации делать ваши разработки более точными.

А. Алексашин , Дэвид Томас , Эндрю Хант

Программирование / Книги по IT