В случае переполнения целых чисел, каков результат (unsigned int) * (int) ?без знака или int?

StackOverflow https://stackoverflow.com/questions/721861

  •  05-09-2019
  •  | 
  •  

Вопрос

В случае переполнения целых чисел, каков результат (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 (считая от нуля).

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top