int func(char* str)
{
char buffer[100];
unsigned short len = strlen(str);
if(len >= 100)
{
return (-1);
}
strncpy(buffer,str,strlen(str));
return 0;
}
Этот код уязвим для атаки переполнения буфера, и я пытаюсь выяснить, почему. Я думаю, что это связано с len
тем, чтобы быть объявленным short
вместо int
, но я не совсем уверен.
Любые идеи?
c
security
buffer-overflow
Джейсон
источник
источник
strncpy
. В этом случае это не так.strlen
вычисляется, используется для проверки достоверности, а затем он снова абсурдно вычисляется - это СУХОЙ сбой. Если бы второеstrlen(str)
было заменено наlen
, не было бы возможности переполнения буфера, независимо от типаlen
. Ответы не касаются этого вопроса, им просто удается избежать этого.Ответы:
На большинстве компиляторов максимальное значение
unsigned short
равно 65535.Любое значение выше этого оборачивается, поэтому 65536 становится 0, а 65600 становится 65.
Это означает, что длинные строки правильной длины (например, 65600) пройдут проверку и переполнят буфер.
Используйте
size_t
для хранения результатаstrlen()
, а не дляunsigned short
сравненияlen
с выражением, которое непосредственно кодирует размерbuffer
. Так, например:источник
len
в качестве третьего аргумента strncpy. Использование strlen снова глупо в любом случае./ sizeof(buffer[0])
- обратите внимание, чтоsizeof(char)
в C всегда 1 (даже если char содержит gazillion битов), так что это излишне, когда нет возможности использовать другой тип данных. Тем не менее ... благодарность за полный ответ (и спасибо за отзывчивость к комментариям).char[]
иchar*
это не одно и то же. Есть много ситуаций, в которыхchar[]
неявно преобразуется вchar*
. Например,char[]
точно так же, какchar*
при использовании в качестве типа для аргументов функции. Однако конвертация не происходит заsizeof()
.buffer
в какой-то момент, выражение автоматически обновится. Это очень важно для безопасности, потому что объявлениеbuffer
может быть довольно далеко от проверки в реальном коде. Таким образом, легко изменить размер буфера, но не забудьте обновить его в каждом месте, где используется размер.Проблема здесь:
Если строка превышает длину целевого буфера, strncpy все равно скопирует ее. Вы основываете количество символов строки в качестве числа для копирования вместо размера буфера. Правильный способ сделать это заключается в следующем:
Это ограничивает объем копируемых данных фактическим размером буфера минус один для нулевого завершающего символа. Затем мы устанавливаем последний байт в буфере нулевым символом в качестве дополнительной защиты. Причина этого в том, что strncpy будет копировать до n байтов, включая завершающий ноль, если strlen (str) <len - 1. Если нет, то ноль не копируется, и у вас есть сценарий сбоя, потому что теперь ваш буфер имеет неопределенный строка.
Надеюсь это поможет.
РЕДАКТИРОВАТЬ: После дальнейшего изучения и ввода от других, возможное кодирование для функции следующим образом:
Поскольку мы уже знаем длину строки, мы можем использовать memcpy, чтобы скопировать строку из местоположения, на которое ссылается str, в буфер. Обратите внимание, что на странице руководства для strlen (3) (в системе FreeBSD 9.3) указано следующее:
Который я интерпретирую как то, что длина строки не включает ноль. Вот почему я копирую len + 1 байт, чтобы включить ноль, и тест проверяет, чтобы убедиться, что длина <размер буфера - 2. Минус один, потому что буфер начинается с позиции 0, и минус другой, чтобы убедиться, что есть место для нуля.
РЕДАКТИРОВАТЬ: Оказывается, размер чего-то начинается с 1, а доступ начинается с 0, так что -2 раньше был неправильным, потому что он вернул бы ошибку для всего> 98 байтов, но это должно быть> 99 байтов.
РЕДАКТИРОВАТЬ: Хотя ответ о беззнаковых коротких, как правило, является правильным, так как максимальная длина, которая может быть представлена, составляет 65 535 символов, это на самом деле не имеет значения, потому что если строка длиннее, значение будет перенесено. Это все равно, что взять 75 231 (что 0x000125DF) и замаскировать верхние 16 бит, что даст вам 9695 (0x000025DF). Единственная проблема, с которой я сталкиваюсь, это первые 100 символов после 65 535, поскольку проверка длины разрешит копирование, но во всех случаях будет копироваться только до первых 100 символов строки, и нулевое завершение строки . Таким образом, даже в случае проблемы с переносом, буфер все равно не будет переполнен.
Это может или не может само по себе представлять угрозу безопасности в зависимости от содержимого строки и того, для чего вы ее используете. Если это просто прямой текст, который читается человеком, то обычно проблем нет. Вы просто получаете усеченную строку. Однако, если это что-то вроде URL или даже последовательности команд SQL, у вас могут возникнуть проблемы.
источник
func
... и любую другую когда-либо написанную функцию C, которая принимает в качестве аргументов строки с NUL-концевыми символами. Воспоминание о том, что вход не завершен NUL, совершенно не имеет смысла.len >= 100
) был выполнен для одного значения, но длина копии была присвоена другое значение ... это является нарушением принципа СУХОЙ. Простой вызовstrncpy(buffer, str, len)
исключает возможность переполнения буфера и выполняет меньше работы, чемstrncpy(buffer,str,sizeof(buffer) - 1)
... хотя здесь это просто более медленный эквивалентmemcpy(buffer, str, len)
.Даже если вы используете
strncpy
, длина обрезки по-прежнему зависит от переданного указателя строки. Вы не представляете, какой длины эта строка (то есть расположение нулевого терминатора относительно указателя). Таким образом,strlen
один только вызов открывает вам уязвимость. Если вы хотите быть более безопасным, используйтеstrnlen(str, 100)
.Полный исправленный код будет:
источник
strlen
тогда доступ за концом буфера?strnlen
не решает проблему, если то, что предлагает orlp, в любом случае, верно.buffer
. «поскольку str может указывать на буфер из 2 байтов, ни один из которых не является NUL». - это не имеет значения, как это верно для любой реализацииfunc
. Вопрос здесь о переполнении буфера, а не UB, потому что ввод не завершен NUL.Ответ с упаковкой правильный. Но есть проблема, я думаю, не была упомянута, если (len> = 100)
Ну, если бы Len был 100, мы скопировали бы 100 элементов, и у нас не было бы трейлинга \ 0. Это явно означало бы, что любая другая функция, зависящая от правильной завершенной строки, будет выходить далеко за пределы исходного массива.
ИМХО неразрешимая строка из C является ИМХО. Тебе лучше иметь некоторые ограничения до звонка, но даже это не поможет. Проверка границ отсутствует, поэтому переполнение буфера всегда возможно и, к сожалению, произойдет ....
источник
strncpy()
и друзья, а функции выделения памяти, какstrdup()
и друзья. Они соответствуют стандарту POSIX-2008, поэтому они достаточно портативны, хотя и недоступны в некоторых проприетарных системах.buffer
является локальной для этой функции и не используется в другом месте. В реальной программе нам нужно было бы изучить, как она используется ... иногда неправильное NUL-завершение является правильным (первоначальное использование strncpy состояло в создании 14-байтовых записей каталога UNIX - дополненных NUL и не заканчивающихся NUL). «Строка, проблематичная из C, ИМХО неразрешима» - в то время как C - это отвратительный язык, который превзошел намного лучшую технологию, безопасный код может быть написан на нем, если используется достаточно дисциплины.if (len >= 100)
является условием, когда проверка заканчивается неудачей , а не когда она проходит, что означает, что не существует случая, когда копируется ровно 100 байтов без терминатора NUL, поскольку эта длина включена в условие сбоя.Помимо проблем безопасности, связанных с вызовом
strlen
более одного раза, обычно не следует использовать строковые методы для строк, длина которых точно известна [для большинства строковых функций существует только очень узкий случай, когда их следует использовать - для строк, для которых максимум длина может быть гарантирована, но точная длина не известна]. Как только длина входной строки известна, а длина выходного буфера известна, следует выяснить, насколько большой регион должен быть скопирован, а затем использовать егоmemcpy()
для фактического выполнения рассматриваемого копирования. Хотя возможно, что этоstrcpy
может быть лучше, чемmemcpy()
при копировании строки длиной всего 1-3 байта, на многих платформахmemcpy()
, вероятно, будет более чем в два раза быстрее при работе с большими строками.Несмотря на то, что в некоторых ситуациях безопасность достигается за счет производительности, в такой ситуации безопасный подход также является более быстрым. В некоторых случаях может быть разумным написать код, который не защищен от вводов странного поведения, если код, предоставляющий входы, может гарантировать, что они будут вести себя хорошо, и если защита от вводов плохого поведения может снизить производительность. Проверка того, что длины строк проверяются только один раз, повышает как производительность, так и безопасность, хотя можно сделать еще одну вещь для обеспечения безопасности даже при отслеживании длины строки вручную: для каждой строки, которая, как ожидается, будет иметь конечный ноль, пишите конечный ноль явно чем ожидание исходной строки, чтобы иметь его. Таким образом, если бы вы писали
strdup
эквивалент:Обратите внимание, что последний оператор, как правило, может быть опущен, если memcpy обработал
len+1
байты, но если другой поток должен был изменить исходную строку, результатом может быть строка назначения, не оканчивающаяся NUL.источник
strlen
более одного раза ?strlen
и предпринял какое-то действие, основанное на возвращенном значении (которое, по-видимому, было причиной его вызова в первую очередь), то повторный вызов any (1) всегда даст тот же ответ, что и первый, в этом случае это просто потраченная впустую работа, или (2) может иногда (потому что что-то другое - возможно, другой поток - изменило строку в это время) дать другой ответ, и в этом случае код, который делает некоторые вещи с длиной (например, выделение буфера) может принимать размер, отличный от кода, который делает другие вещи (копирование в буфер).