В случае целочисленных переполнений, что является результатом (unsigned int) * (int)? без знака или int? - PullRequest
8 голосов
/ 06 апреля 2009

В случае целочисленных переполнений, что будет результатом (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.

Вопрос здесь , а не , насколько плоха функция или как ее улучшить, чтобы сделать ее лучше. Функция, несомненно, имеет уязвимость. Речь идет о точном поведении функции, основанном на предписанном поведении стандартов.

Ответы [ 13 ]

4 голосов
/ 06 апреля 2009

выполните вычисление w * h в длинном лонге, проверьте, больше ли MAX_UINT

РЕДАКТИРОВАТЬ: альтернатива: если переполнен (w * h) / h! = W (это всегда так?! Должно быть, верно?)

2 голосов
/ 06 апреля 2009

В C / C ++ нотация p[n] на самом деле является ярлыком для записи *(p+n), и эта арифметика указателей учитывает знак. Таким образом, p[-1] является действительным и относится к значению непосредственно перед *p.

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

Проверьте эту страницу: INT02-C. Понимать правила преобразования целых чисел

2 голосов
/ 06 апреля 2009

Тип 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)
2 голосов
/ 06 апреля 2009

Чтобы ответить на ваш вопрос: тип выражения, умножающего int и unsigned int, будет беззнаковым int в C / C ++.

Чтобы ответить на ваш подразумеваемый вопрос, один из приличных способов справиться с возможным переполнением в целочисленной арифметике - использовать набор «IntSafe» от Microsoft:

http://blogs.msdn.com/michael_howard/archive/2006/02/02/523392.aspx

Он доступен в SDK и содержит встроенные реализации, чтобы вы могли изучить, что они делают, если вы работаете на другой платформе.

2 голосов
/ 06 апреля 2009

Убедитесь, что w * h не переполняется, ограничивая w и h.

1 голос
/ 06 апреля 2009

Сведения о C см. В разделе «Обычные арифметические преобразования» (C99: раздел 6.3.1.8, ANSI C K & R A6.5) для получения подробной информации о том, как обрабатываются операнды математических операторов.

В вашем примере применяются следующие правила:

C99:

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

В противном случае оба операнда преобразуются к целому типу без знака в соответствии с типом операнд со знаком целого типа.

ANSI C:

В противном случае, если один из операндов имеет тип unsigned int, другой преобразуется в тип без знака.

1 голос
/ 06 апреля 2009

2 изменения делают его более безопасным:

if (w >= 4096 || h >= 4096 || w*h > 4096)  return NULL;

...

unsigned i;

Обратите внимание, что не менее плохая идея писать или читать после конца буфера. Поэтому вопрос не в том, может ли i w стать отрицательным, а в том, что 0 <= i </em> h + w Значит, важен не тип, а результат h * i. Например, не имеет значения, является ли это (без знака) 0x80000000 или (int) 0x80000000, программа все равно будет вызывать сбой.

0 голосов
/ 18 сентября 2010

В текущем черновике C1X есть 3 параграфа по вычислению (UNSIGNED TYPE1) X (SIGNED TYPE2) в 6.3.1.8 Обычные арифметические покрытия, N1494,

РГ 14: C - Состояние проекта и основные этапы

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

В противном случае, если тип операнда со знаком целого типа может представлять все значения типа операнда с целым типом без знака, то операнд с целым типом без знака преобразуется в тип операнд со знаком целого типа.

В противном случае оба операнда преобразуются в целочисленный тип без знака соответствует типу операнда с целочисленным типом со знаком.

Таким образом, если a - беззнаковое целое, а b - целое, анализ (a * b) должен генерировать код (a * (беззнаковое целое) b). Переполнится, если b <0 или a * b> UINT_MAX.

Если a - беззнаковое целое число, а b длиннее большого размера, (a * b) должно сгенерировать ((long) a * (long) b). Переполнится, если a * b> LONG_MAX или a * b

Если a - беззнаковое целое, а b - длинное с одинаковым размером, (a * b) должно сгенерировать ((длинное без знака) a * (длинное без знака) b). Переполнится, если b <0 или a * b> ULONG_MAX.

На ваш второй вопрос о типе, ожидаемом «indexer», ответом будет «integer type», который допускает любой (подписанный) целочисленный индекс.

6.5.2.1 Массив подписки

Ограничения

1 Одно из выражений должно иметь указатель типа «указатель на полный тип объекта», другое выражение должно иметь целочисленный тип, а результат имеет тип «тип».

Семантика

2 Постфиксное выражение, за которым следует выражение в квадратных скобках [], является индексом обозначение элемента объекта массива. Определение подстрочного оператора [] является то, что E1 [E2] идентична (* ((E1) + (E2))). Из-за правил преобразования, которые применить к бинарному оператору +, если E1 является объектом массива (эквивалентно, указатель на начальный элемент объекта массива) и E2 является целым числом, E1 [E2] обозначает E2-й элемент E1 (считая с нуля).

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

0 голосов
/ 06 апреля 2009

Арифметика без знака выполняется как модульная (или циклическая), поэтому произведение двух больших беззнаковых целых может легко быть меньше 4096. Умножение int и unsigned int приведет к беззнаковому int (см. Раздел 4.5 Стандарт C ++).

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

Сложно убедиться, что целочисленная арифметика не переполняется. Одним из простых способов является преобразование в число с плавающей запятой и выполнение умножения с плавающей запятой, а также проверка приемлемости результата. Как предполагает qwerty, long long можно использовать, если он доступен в вашей реализации. (Это общее расширение в C90 и C ++, существует в C99 и будет в C ++ 0x.)

0 голосов
/ 06 апреля 2009

w * h может переполниться, если w и / или h достаточно велики и следующая проверка может пройти.

9.      if (w*h > 4096)
10.         return (NULL);

В случае int, unsigned int смешанных операций int повышается до unsigned int, и в этом случае отрицательное значение «i» становится большим положительным значением. В этом случае

&buf[i*w]

будет иметь доступ к недопустимому значению.

...