c-String уязвимости - PullRequest
       7

c-String уязвимости

1 голос
/ 11 марта 2019

Я читал об уязвимостях в строках в C, а затем наткнулся на этот код. Может ли кто-нибудь дать мне объяснение, почему это происходит? Заранее спасибо.

int main (int argc, char* argv[]) {
  char a[16];
  char b[16];
  char c[32];
  strncpy(a, "0123456789abcdef", sizeof(a));
  strncpy(b, "0123456789abcdef", sizeof(b));
  strncpy(c, a, sizeof(c));

  printf("a = %s\n", a);
  printf("b = %s\n", b);
  printf("c = %s\n", c);
}

Выход:

a = 0123456789abcdef0123456789abcdef
b = 0123456789abcdef
c = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

Ответы [ 3 ]

4 голосов
/ 11 марта 2019

n в strncpy не означает то же самое, что n в strncat или snprintf; strncpy был создан для манипулирования строками буфера фиксированного размера, такими как записи каталога, поэтому он копирует не более n символов, заполняя неиспользуемые NUL ( = байт 0 = '\0' = null символ = ... ), но если нет свободных, он не добавляет NUL. Следовательно, цель strncpy не обязательно будет заканчиваться NUL, поэтому, если вы попытаетесь манипулировать ею как строкой C, вас ожидают сюрпризы.

Это именно то, что происходит в этом случае. Ваши буферы a и b имеют ту же длину, что и строка, которую вы в них копируете; strncpy не завершает их с помощью NUL, поэтому, когда третий strncpy или более поздний printf попытается прочитать их, результатом будет чье-то предположение (читай: это неопределенное поведение, поэтому может произойти все что угодно), так как NUL не мешает им продолжать чтение в несвязанной памяти.

Что касается конкретного получаемого вами результата, то это зависит от того, как именно a, b и c располагаются в памяти (действительно, на моей машине я получаю разные результаты), от того, как strncpy написан точно (так как он не предназначен для вызова на перекрывающихся строках) и от того, как именно оптимизатор решил исказить ваш код (помните: чтение за пределами границ является неопределенным поведением, поэтому оптимизатору разрешается предполагать, что этого никогда не произойдет при перегруппировке ваш код).

Возможное объяснение фактического поведения, которое вы видите, состоит в том, что c, a и b последовательно располагаются в памяти, в этом порядке, а остальная часть стека оказывается заполненной NUL ( здесь я использую ° в качестве заполнителя для NUL 1 ):

°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°...
c                               a               b               ?

То, что происходит, должно выглядеть примерно так:

  • 0123456789abcdef копируется в a без завершения NUL, так как достигло максимально допустимых символов (16).

    °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°0123456789abcdef°°°°°°°°°°°°°°°°°°°°...
    c                               a               b               ?
    
  • 0123456789abcdef копируется в b, без завершения NUL (как и прежде).

    °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°0123456789abcdef0123456789abcdef°°°°...
    c                               a               b               ?
    
  • a копируется в c; так как a не завершено NUL, strncpy продолжает чтение, прямо в пространство b, копируя полные 32 символа, которые ему разрешено копировать. Поскольку он достиг 32 символов, NUL не пишется.

    0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef°°°°...
    c                               a               b               ?
    
  • a напечатано; так как он не завершен NUL, printf продолжает читать в памяти, а именно b, печатая 32 символа.

  • b напечатано; он явно не завершен NUL, но память после него содержит NUL, поэтому он останавливается после того, как были скопированы 16 символов;
  • c напечатано; так как это не NUL завершено, printf продолжает чтение по всей длине c, a и b (на конце которого находится NUL, который останавливает печать), печатая 64 символа.

Помните: это только возможное объяснение вывода, который вы показываете. Это не обязательно правильно и, конечно, не является договорным (на моей машине я получаю различный вывод в зависимости от флагов компиляции и даже при разных запусках, в зависимости от того, что происходит при загрузке в стек).


  1. Я бы хотел использовать символ,, но часто он отображается с нефиксированной шириной даже в кодовых блоках, что нарушает искусство ASCII.
1 голос
/ 11 марта 2019

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

Функция strncpy фактически никогда не предназначалась для использования с строками C, а с древним форматом строк Unix, в котором не использовалось нулевое завершение. Эту функцию следует избегать для большинства целей. В частности, это не «безопасная версия strcpy» - но более опасная функция, чем strcpy, как мы видим из ошибок здесь.

Решение состоит в том, чтобы заранее проверить размер копируемого файла перед копированием. А затем используйте strcpy. Например:

char a[16];
const char to_copy[] = "0123456789abcdef";
_Static_assert(sizeof(to_copy) <= sizeof(a), "to_copy is too big");
strcpy(a, to_copy);

Чтобы исправить вашу текущую программу, вам нужно выделить место для нулевого терминатора, например:

#include <string.h>
#include <stdio.h>

int main (void)
{
  char a[16+1];
  char b[16+1];
  char c[32+1];
  const char to_copy[] = "0123456789abcdef";
  _Static_assert(sizeof(to_copy) <= sizeof(a), "to_copy is too big");
  _Static_assert(sizeof(to_copy) <= sizeof(b), "to_copy is too big");

  strcpy(a, to_copy);
  strcpy(b, to_copy);
  strcpy(c, a);

  printf("a = %s\n", a);
  printf("b = %s\n", b);
  printf("c = %s\n", c);
}
1 голос
/ 11 марта 2019

Чтение за концом строки - неопределенное поведение (UB).С UB нет никаких гарантий, что код будет вести себя так или иначе.Поведение может отличаться в разных системах, компиляторах, компоновщиках, флагах компиляции / компоновки, в зависимости от (казалось бы) несвязанного кода и версии всего вышеперечисленного.

Во многих системах переменные последовательно располагаются в стекев обратном порядке.Замените ваш printf на:

printf("a (%p) = %s\n", a, a);
printf("b (%p) = %s\n", b, b);
printf("c (%p) = %s\n", c, c);

Он печатает адреса массивов:

a (0x7fff559adad0) = 0123456789abcdef<F0>ښU<FF>
b (0x7fff559adac0) = 0123456789abcdef0123456789abcdef<F0>ښU<FF>
c (0x7fff559adaa0) = 0123456789abcdef<F0>ښU<FF>

Как видно из адресов, распечатка b начинается с адреса 0x7fff559adac0, но продолжается хорошопо адресу a (который начинается через 16 байтов после начала b).

Также обратите внимание, что строки имеют ненужный конец.Дело в том, что в строке отсутствует терминатор '\ 0', а printf продолжает читать следующий мусор (UB сам по себе).

Это происходит потому, что:

strncpy(a, "0123456789abcdef", sizeof(a));

устанавливает [], чтобы все его байты были равны "0123456789abcdef" без нулевого терминатора.Отсутствие '\ 0' printf не знает, где остановиться, и приведет к UB.

strncpy(b, "0123456789abcdef", sizeof(b));

также устанавливает b [], чтобы все его байты были равны "0123456789abcdef" без нулевого терминатора.Здесь также любой printf вызывает UB.Но на этот раз вместо случайного мусора он просто читает следующую строку.

Чтобы добавить оскорбление к травме, строка

 strncpy(c, a, sizeof(c));

читает 32 байта из 16-байтового массива.Это тоже UB.В вашей (и моей) системе это читает a и много мусора после него.Теоретически, это может привести к сбою вашей программы с нарушением доступа или ошибкой сегментации.

Некоторые вирусы и черви используют такие переполнения для чтения или записи данных, которые они не должны.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...