В случае переполнения целых чисел, каков результат (unsigned int) * (int) ?без знака или int?
Вопрос
В случае переполнения целых чисел, каков результат (unsigned int) * (int)
? unsigned
или int
?Какой тип имеет оператор индексации массива (operator[]
) принимать за char*
: int
, unsigned int
или что-то еще?
Я проводил аудит следующей функции, и внезапно возник этот вопрос.Функция имеет уязвимость в строке 17.
// Create a character array and initialize it with init[]
// repeatedly. The size of this character array is specified by
// w*h.
char *function4(unsigned int w, unsigned int h, char *init)
{
char *buf;
int i;
if (w*h > 4096)
return (NULL);
buf = (char *)malloc(4096+1);
if (!buf)
return (NULL);
for (i=0; i<h; i++)
memcpy(&buf[i*w], init, w); // line 17
buf[4096] = '\0';
return buf;
}
Рассмотрите оба варианта w
и h
являются очень большими целыми числами без знака.У умножения в строке 9 есть шанс пройти проверку.
Теперь проблема в строке 17.Умножать int i
с unsigned int w
:если результат будет int
, возможно, что продукт отрицательный, что приводит к доступу к позиции, которая находится перед buf
.Если результат будет unsigned int
, продукт всегда будет положительным, что приведет к доступу к позиции, которая находится после buf
.
Трудно написать код, чтобы оправдать это: int
слишком велик.У кого-нибудь есть идеи по этому поводу?
Существует ли какая-либо документация, в которой указывается тип продукта?Я искал это, но пока ничего не нашел.
Я полагаю, что в том, что касается уязвимости, независимо от того, (unsigned int) * (int)
производит unsigned int
или int
не имеет значения, потому что в скомпилированном объектном файле это просто байты.Следующий код работает одинаково независимо от типа продукта:
unsigned int x = 10;
int y = -10;
printf("%d\n", x * y); // print x * y in signed integer
printf("%u\n", x * y); // print x * y in unsigned integer
Следовательно, не имеет значения, какой тип возвращает умножение.Важно, принимает ли потребительская функция int
или unsigned
.
Вопрос здесь заключается в следующем не насколько плоха функция или как улучшить функцию, чтобы сделать ее лучше.Эта функция, несомненно, имеет уязвимость.Вопрос заключается в точном поведении функции, основанном на предписанном поведении из стандартов.
Решение
Чтобы ответить на ваш вопрос:тип выражения, умножающего int и unsigned int, будет unsigned int в C / C ++.
Чтобы ответить на ваш подразумеваемый вопрос, одним из достойных способов справиться с возможным переполнением в целочисленной арифметике является использование "IntSafe
" набор подпрограмм от Microsoft:
http://blogs.msdn.com/michael_howard/archive/2006/02/02/523392.aspx
Он доступен в SDK и содержит встроенные реализации, так что вы можете изучить, что они делают, если вы находитесь на другой платформе.
Другие советы
выполните вычисление w * h в long long, проверьте, больше ли MAX_UINT
Редактировать :альтернатива :if overflown (w * h) /h != w (это всегда так ?!должно быть, верно ?)
Убедитесь, что w * h не переполняется, ограничив w и h.
Тип w*i
в вашем случае он не подписан.Если я правильно прочитал стандарт, правило заключается в том, что операнды преобразуются в более крупный тип (с его подписью) или беззнаковый тип, соответствующий подписанному типу (который является unsigned int
в вашем случае).
Однако, даже если он без знака, это не предотвращает обтекание (запись в память перед buf
), потому что это может быть так (на платформе i386 это так), что p[-1]
это то же самое , что p[-1u]
.Во всяком случае, в вашем случае, оба buf[-1]
и buf[big unsigned number]
было бы неопределенным поведением, поэтому вопрос со знаком / без знака не так важен.
Обратите внимание, что подписанный / неподписанный имеет значение в других контекстах - например. (int)(x*y/2)
дает различные результаты в зависимости от типов x
и y
, даже при отсутствии неопределенного поведения.
Я бы решил вашу проблему, проверив наличие переполнения в строке 9;поскольку 4096 - довольно маленькая константа, а 4096 * 4096 не переполняется на большинстве архитектур (вам нужно проверить), я бы сделал
if (w>4096 || h>4096 || w*h > 4096)
return (NULL);
При этом не учитывается случай , когда w
или h
равно 0, возможно, вы захотите проверить это, если это необходимо.
В общем, вы могли бы проверить наличие переполнения следующим образом:
if(w*h > 4096 || (w*h)/w!=h || (w*h)%w!=0)
В C / C ++ p[n]
нотация - это действительно кратчайший путь к написанию *(p+n)
, и эта арифметика указателя учитывает знак.Итак p[-1]
является допустимым и ссылается на значение непосредственно перед *p
.
Таким образом, знак здесь действительно имеет значение, результат арифметического оператора с целым числом соответствует набору правил, определенных стандартом, и это называется целочисленными повышениями.
Ознакомьтесь с этой страницей: INT02-C.Понимать правила преобразования целых чисел
2 изменения делают его более безопасным:
if (w >= 4096 || h >= 4096 || w*h > 4096) return NULL;
...
unsigned i;
Обратите также внимание, что запись в буфер или чтение из него - не менее плохая идея.Так что вопрос не в том, буду ли яw может стать отрицательным, но будет ли 0 <= яh +w <= 4096 удержаний.
Так что важен не тип, а результат h * i.Например, не имеет значения, является ли это (без знака)0x80000000 или (int)0x80000000, программа в любом случае выдаст ошибку seg.
Для C обратитесь к "Обычным арифметическим преобразованиям" (C99:Раздел 6.3.1.8, ANSI C K & R A6.5) для получения подробной информации о том, как обрабатываются операнды математических операторов.
В вашем примере применяются следующие правила:
С99:
В противном случае, если тип операнда с целочисленным типом со знаком может представлять все значения типа операнда с целочисленным типом без знака, тогда операнд с целочисленным типом без знака тип преобразуется в тип операнда с целочисленным типом со знаком.
В противном случае оба операнда преобразуются в целочисленный тип без знака соответствующий типу операнда с целочисленным типом со знаком.
ANSI C:
В противном случае, если один из операндов имеет значение unsigned int, другой преобразуется в unsigned int.
Почему бы просто не объявить i как unsigned int?Тогда проблема исчезнет.
В любом случае, i * w гарантированно будет <= 4096, поскольку код проверяет это, поэтому он никогда не переполнится.
memcpy(&buf[яw > -1 ?iw < 4097?iw :0 :0], инициализация, w);Я не думаю, что тройной расчет iw действительно ухудшает производительность)
w * h может переполниться, если w и / или h достаточно велики и следующая проверка может пройти успешно.
9. if (w*h > 4096)
10. return (NULL);
При смешанных операциях int , unsigned int значение int повышается до unsigned int , и в этом случае отрицательное значение 'i' станет большим положительным значением.В таком случае
&buf[i*w]
был бы доступ к значению вне пределов.
Беззнаковая арифметика выполняется как модульная (или с переносом), поэтому произведение двух больших целых чисел без знака легко может быть меньше 4096.Умножение int и unsigned int приведет к получению unsigned int (см. Раздел 4.5 стандарта C ++).
Следовательно, учитывая большое w и подходящее значение h, вы действительно можете попасть в беду.
Убедиться, что целочисленная арифметика не переполняется, довольно сложно.Один из простых способов - преобразовать в формат с плавающей запятой и выполнить умножение с плавающей запятой и посмотреть, является ли результат вообще разумным.Как предположил qwerty, long long можно было бы использовать, если он доступен в вашей реализации.(Это распространенное расширение в C90 и C ++, существует в C99 и будет в C ++ 0x.)
В текущем проекте C1X есть 3 параграфа о вычислении (НЕПОДПИСАННОГО ТИПА1) X (ПОДПИСАННОГО ТИПА2) в 6.3.1.8 обычных арифметических покрытиях, N1494,
Рабочая группа 14:C - Статус проекта и основные этапы
В противном случае, если операнд, имеющий целочисленный тип без знака, имеет ранг больше или равен рангу типа другого операнда, то операнд с целочисленный тип со знаком преобразуется в тип операнда с беззнаковым целочисленный тип.
В противном случае, если тип операнда с целочисленным типом со знаком может представлять все значения типа операнда с целочисленным типом без знака, тогда операнд с целочисленным типом без знака преобразуется в тип операнд с целочисленным типом со знаком.
В противном случае оба операнда преобразуются в целочисленный тип без знака соответствующий типу операнда с целочисленным типом со знаком.
Таким образом, если a - это unsigned int, а b - int, синтаксический анализ (a * b) должен сгенерировать код (a * (unsigned int)b).Переполнится, если b < 0 или a * b > UINT_MAX.
Если a - это unsigned int, а b - long большего размера, (a * b) должен генерировать ((long)a * (long)b).Переполнится , если a * b > LONG_MAX или a * b < ЛОНГ_МИН.
Если a - это unsigned int, а b - long одинакового размера, (a * b) должен сгенерировать ((unsigned long)a * (unsigned long)b).Переполнится, если b < 0 или a * b > ULONG_MAX.
На ваш второй вопрос о типе, ожидаемом "индексатором", появляется ответ "целочисленный тип", который допускает любой (подписанный) целочисленный индекс.
6.5.2.1 Подписка на массив
Ограничения
1 Одно из выражений должно иметь тип ‘указатель на полный тип объекта’, другое выражение должно иметь целочисленный тип, а результат имеет тип ‘type’.
Семантика
2 Постфиксное выражение, за которым следует выражение в квадратных скобках [], является подписанным обозначение элемента объекта array.Определение оператора подстрочного индекса [] заключается в том, что E1[E2] идентично (*((E1)+(E2))).Из-за правил преобразования, которые применяются к двоичному оператору +, если E1 является объектом массива (эквивалентно, указателем на начальный элемент объекта массива), а E2 - целое число, E1[E2] обозначает E2-й элемент E1 (считая от нуля).
Компилятор должен выполнить статический анализ и предупредить разработчика о возможности переполнения буфера, когда выражение указателя является переменной массива, а индекс может быть отрицательным.То же самое касается предупреждения о возможном превышении размера массива, даже если индекс положительный или без знака.
Чтобы на самом деле ответить на ваш вопрос, без указания оборудования, на котором вы работаете, вы не знаете, и в коде, предназначенном для переносимости, вы не должны зависеть от какого-либо конкретного поведения.