Для получения триального ключа
заполните форму ниже
Team License (базовая версия)
Enterprise License (расширенная версия)
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Бесплатная лицензия PVS-Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

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

Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
>
Эксперимент по проверке библиотеки glibc

Эксперимент по проверке библиотеки glibc

26 Фев 2014

Мы провели эксперимент по проверке библиотеки glibc с помощью PVS-Studio. Цель эксперимента посмотреть, насколько успешно анализатор может проверять Linux-проекты. Пока плохо может. Возникает огромное количество ложных срабатываний из-за использования нестандартных расширений. Однако, всё равно удалось найти кое что интересное.

0237_glibc_ru/image1-d51716039cd3dd2e67bca4e0ed9617d3.png

glibc

glibc - GNU C Library (GNU библиотека). Glibc является библиотекой Си, которая обеспечивает системные вызовы и основные функции, такие как open, malloc, printf и т.д. Библиотека C используется для всех динамически скомпонованных программ. Она написана Free Software Foundation для GNU операционных систем. glibc выпущена под лицензией GNU LGPL.

Взято из Wikipedia: glibc.

Не так давно в интернете появились новости, что вышла новая версия библиотеки glibc. Это подвигло нас на проверку этой библиотеки с помощью нашего анализатора PVS-Studio. А именно, была проверена версия glibc-2-19-90. К сожалению, на пару недель я отвлекся, поэтому нашёл время написать статью только сейчас. Я был занят большим сравнением нескольких статических анализаторов. Это очень важная для нас задача так как не перестают спрашивать, чем мы лучше Cppcheck и статического анализатора в Visual Studio 2013. Поэтому glibc пришлось немного подождать.

Найти что-то ужасное мы не рассчитывали и действительно не нашли. Библиотека glibc очень качественная и проверяемая многими анализаторами. Как минимум, это:

  • Coverity;
  • Clang;
  • Cppcheck.

Так что найти хоть что-то, это уже большое достижение.

В чем сложность анализа

Незнакомым с внутренней кухней инструментов статического анализа, они кажутся очень простыми утилитами. Это не так. Это очень сложные программы.

С толку могут сбивать такие инструменты, как RATS. Если кто-то смотрел код RATS, то видел, что это просто поиск определённых имён функций в файлах. Этот инструмент тоже называется статическим анализатором кода. Однако, он очень далёк от того, чем занимаются настоящие анализаторы кода. Статический анализ, это вовсе не поиск с помощью регулярных выражений [1].

Неоднократно мы повторяли, что Linux-версия, это вовсе не тоже самое, что перекомпилированный исполняемый модуль [2]. Между исполняемым модулем и программным продуктом лежит пропасть. Одно из препятствий, поддержка специфичных расширений и тому подобное.

Что это такое, стороннему человеку совершенно непонятно. Вот от видит, в программе вызов функции strcmp():

cmpres = strcmp (newp->from_string, root->from_string);

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

cmpres = __extension__ ({ size_t __s1_len, __s2_len;
  (__builtin_constant_p (newp->from_string) &&
  __builtin_constant_p (root->from_string) &&
  (__s1_len = strlen (newp->from_string),
  __s2_len = strlen (root->from_string),
  (!((size_t)(const void *)((newp->from_string) + 1) -
  (size_t)(const void *)(newp->from_string) == 1) ||
  __s1_len >= 4) &&
  (!((size_t)(const void *)((root->from_string) + 1) -
  (size_t)(const void *)(root->from_string) == 1) ||
  __s2_len >= 4)) ?
  __builtin_strcmp (newp->from_string, root->from_string) :
  (__builtin_constant_p (newp->from_string) &&
  ((size_t)(const void *)((newp->from_string) + 1) -
  (size_t)(const void *)(newp->from_string) == 1) &&
  (__s1_len = strlen (newp->from_string), __s1_len < 4) ?
  (__builtin_constant_p (root->from_string) &&
  ((size_t)(const void *)((root->from_string) + 1) -
  (size_t)(const void *)(root->from_string) == 1) ?
   __builtin_strcmp (newp->from_string, root->from_string) :
  (__extension__ ({ const unsigned char *__s2 =
  (const unsigned char *) (const char *) (root->from_string);
  int __result = (((const unsigned char *) (const char *)
  (newp->from_string))[0] - __s2[0]);
  if (__s1_len > 0 && __result == 0) {
  __result = (((const unsigned char *) (const char *)
  (newp->from_string))[1] - __s2[1]);
  if (__s1_len > 1 && __result == 0) { __result =
  (((const unsigned char *) (const char *)
  (newp->from_string))[2] - __s2[2]);
  if (__s1_len > 2 && __result == 0)
  __result = (((const unsigned char *)
  (const char *) (newp->from_string))[3] -
  __s2[3]); } } __result; }))) :
  (__builtin_constant_p (root->from_string) &&
  ((size_t)(const void *)((root->from_string) + 1) -
  (size_t)(const void *)(root->from_string) == 1) &&
  (__s2_len = strlen (root->from_string), __s2_len < 4) ?
  (__builtin_constant_p (newp->from_string) &&
  ((size_t)(const void *)((newp->from_string) + 1) -
  (size_t)(const void *)(newp->from_string) == 1) ?
  __builtin_strcmp (newp->from_string, root->from_string) :
  (- (__extension__ ({ const unsigned char *__s2 =
  (const unsigned char *) (const char *) (newp->from_string);
  int __result = (((const unsigned char *) (const char *)
  (root->from_string))[0] - __s2[0]);
  if (__s2_len > 0 && __result == 0) { __result =
  (((const unsigned char *) (const char *)
  (root->from_string))[1] - __s2[1]);
  if (__s2_len > 1 && __result == 0)
  { __result = (((const unsigned char *)
  (const char *) (root->from_string))[2] -
  __s2[2]); if (__s2_len > 2 && __result == 0)
  __result = (((const unsigned char *) (const char *)
  (root->from_string))[3] - __s2[3]); } } __result; })))) :
  __builtin_strcmp (newp->from_string, root->from_string))));
});

Анализатор не готов к такому повороту событий и временами выдаёт на подобных конструкциях бессмысленные ложные сообщения.

Поясню про ложные сообщения на более простом примере. Пусть у нас есть строка кода:

assert(MAP_FAILED == (void *) -1);

Макрос assert() раскрывается в следующий код:

((((void *) -1) == (void *) -1) ? (void) (0) :
  __assert_fail ("((void *) -1) == (void *) -1",
    "loadmsgcat.c", 840, __PRETTY_FUNCTION__));

Анализатор PVS-Studio выдаёт ложное предупреждение касательно сравнения (((void *) -1) == (void *) -1):

V501 There are identical sub-expressions to the left and to the right of the '==' operator: ((void *) - 1) == (void *) - 1 loadmsgcat.c 840

Мы не удивлены этому. Всё это мы уже проходили, ведя разработку для программ, собираемых с помощью Visual C++. Там тоже много всякого интересного и необычного. Нужно проделать большую работу, чтобы научить анализатор понимать, что есть что. Нужно научить его понять, что он имеет дело с макросом "assert" который безобиден и просто проверяет что макрос MAP_FAILED равен "(void *) -1". Всё это уже сделано для Visual C++. А для Linux нет.

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

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

Найденные подозрительные участки кода

Хотя проект glibc проверяется многими инструментами, кое что интересного всё-таки удалось найти. Давайте посмотрим на эти участки кода.

Странное выражение

char *DCIGETTEXT (....)
{
  ....
  /* Make CATEGORYVALUE point to the next element of the list. */
  while (categoryvalue[0] != '\0' && categoryvalue[0] == ':')
    ++categoryvalue;
  ....
}

V590 Consider inspecting this expression. The expression is excessive or contains a misprint. dcigettext.c 582

Условие можно упросить до:

while (categoryvalue[0] == ':')

Возможно, это не ошибка и первая часть условия (categoryvalue[0] != '\0') просто лишняя. Однако, вдруг должно быть написано так:

while (categoryvalue[0] != '\0' && categoryvalue[0] != ':')

Разыменование указателя до проверки

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

static enum clnt_stat
clntraw_call (h, proc, xargs, argsp, xresults, resultsp, timeout)
     CLIENT *h;
     u_long proc;
     xdrproc_t xargs;
     caddr_t argsp;
     xdrproc_t xresults;
     caddr_t resultsp;
     struct timeval timeout;
{
  struct clntraw_private_s *clp = clntraw_private;
  XDR *xdrs = &clp->xdr_stream;
  ....
  if (clp == NULL)
    return RPC_FAILED;
  ....
}

V595 The 'clp' pointer was utilized before it was verified against nullptr. Check lines: 145, 150. clnt_raw.c 145

Рядом в этом файле можно увидеть аналогичный дефект: V595 The 'clp' pointer was utilized before it was verified against nullptr. Check lines: 232, 235. clnt_raw.c 232

Опасная оптимизация (уязвимость)

char *
__sha256_crypt_r (key, salt, buffer, buflen)
     const char *key;
     const char *salt;
     char *buffer;
     int buflen;
{
  ....
  unsigned char temp_result[32]
  ....
  memset (temp_result, '\0', sizeof (temp_result));
  ....
  .... // temp_result далее не используется
}

V597 The compiler could delete the 'memset' function call, which is used to flush 'temp_result' buffer. The RtlSecureZeroMemory() function should be used to erase the private data. sha256-crypt.c 385

Компилятор вправе удалить вызов функции memset() при компиляции Release версии. Точнее он не только в праве, но и обязан это сделать с целью оптимизации. Буфер 'temp_result' после вызова функции memset() нигде не используется, следовательно и сам вызов функции тоже лишний.

Мы имеем дело с уязвимостью, так как не будут очищены приватные данные. Следует заменить функцию memset() на более подходящую. Анализатор предлагает RtlSecureZeroMemory(), которой конечно нет в Linux. Но есть аналоги.

Аналогичная ситуация: V597 The compiler could delete the 'memset' function call, which is used to flush 'temp_result' buffer. The RtlSecureZeroMemory() function should be used to erase the private data. sha512-crypt.c 396

Undefined behavior

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

Вот что нам говорит стандарт языка Си о сдвигах:

The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 * 2 pow E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 * 2 pow E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2 pow E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.

Из стандарта следует, что неправильно сдвигать отрицательные числа. Однако, это очень распространённая операция в библиотеке glibc.

Пример сдвига влево:

static void init_cacheinfo (void)
{
  ....
  count_mask = ~(-1 << (count_mask + 1));
  ....
}

V610 Undefined behavior. Check the shift operator '<<. The left operand '-1' is negative. cacheinfo.c 645

Пример сдвига вправо:

utf8_encode (char *buf, int val)
{
  ....
  *buf = (unsigned char) (~0xff >> step);
  ....
}

Выражение "~0xff" имеет тип 'int' равно -256.

Вот список всех мест, где можно наблюдать некорректные сдвиги:

  • strxfrm_l.c 68
  • clock_nanosleep.c 38
  • ifaddrs.c 786
  • xdr_intXX_t.c 35
  • xdr_intXX_t.c 41
  • private.h 327
  • private.h 331
  • zic.c 696
  • zdump.c 212
  • zdump.c 216
  • timer_create.c 47
  • timer_create.c 49
  • loop.c 331
  • loop.c 437
  • mktime.c 207
  • mktime.c 208
  • mktime.c 211
  • mktime.c 212
  • mktime.c 230
  • mktime.c 298
  • mktime.c 298
  • ld-collate.c 298

Использование неинициализированной переменной

static int send_vc(....)
{
  ....
  int truncating, connreset, resplen, n;
  ....
  #ifdef _STRING_ARCH_unaligned
    *anssizp2 = orig_anssizp - resplen;
    *ansp2 = *ansp + resplen;
  #else
  ....
}
V614 Uninitialized variable 'resplen' used. res_send.c 790

Некорректное форматирование строк

В некоторых местах для распечатки знаковых переменных используется '%u'. Ещё в некоторых местах для распечатки беззнаковых переменных используется '%d'. Это конечно мелочи, но их тоже стоит упомянуть.

Пример:

typedef unsigned int __uid_t;
typedef __uid_t uid_t;

int
user2netname (...., const uid_t uid, ....)
{
  ....
  sprintf (netname, "%s.%d@%s", OPSYS, uid, dfltdom);
  ....
}

V576 Incorrect format. Consider checking the fourth actual argument of the 'sprintf' function. The SIGNED integer type argument is expected. netname.c 51

Другие соответствующие сообщения:

  • Consider checking the second actual argument of the 'printf' function. The SIGNED integer type argument is expected. locarchive.c 1741
  • Consider checking the fourth actual argument of the 'printf' function. The SIGNED integer type argument is expected. locarchive.c 1741
  • Consider checking the fifth actual argument of the 'fprintf' function. The SIGNED integer type argument is expected. res_debug.c 236
  • Consider checking the third actual argument of the 'sprintf' function. The UNSIGNED integer type argument is expected. inet_net_ntop.c 134
  • Consider checking the fourth actual argument of the 'sprintf' function. The SIGNED integer type argument is expected. ns_print.c 500
  • Consider checking the fifth actual argument of the 'sprintf' function. The SIGNED integer type argument is expected. ns_print.c 500
  • Consider checking the third actual argument of the 'sprintf' function. The SIGNED integer type argument is expected. ns_print.c 572
  • Consider checking the fourth actual argument of the 'sprintf' function. The SIGNED integer type argument is expected. ns_print.c 572
  • Consider checking the fifth actual argument of the 'sprintf' function. The SIGNED integer type argument is expected. ns_print.c 572
  • Consider checking the third actual argument of the 'sprintf' function. The UNSIGNED integer type argument is expected. ns_print.c 628
  • Consider checking the fourth actual argument of the 'sprintf' function. The UNSIGNED integer type argument is expected. ns_print.c 628
  • Consider checking the fifth actual argument of the 'sprintf' function. The UNSIGNED integer type argument is expected. ns_print.c 628
  • Consider checking the third actual argument of the 'sprintf' function. The SIGNED integer type argument is expected. ns_print.c 645
  • Consider checking the third actual argument of the 'sprintf' function. The UNSIGNED integer type argument is expected. ns_print.c 685
  • Consider checking the second actual argument of the 'printf' function. The SIGNED integer type argument is expected. nis_print.c 209
  • Consider checking the second actual argument of the 'printf' function. The SIGNED integer type argument is expected. sprof.c 480

Заключение

Выбран не удачный проект для начала проверки кода из мира Linux. Он слишком качественный. :) Сложно написать интересную статью про ошибки. Но не беда. нас ждёт немало других известных и интересных проектов в Linux, которые мы попроверяем для демонстрации возможностей анализатора PVS-Studio.

Дополнительные ссылки

Популярные статьи по теме
PVS-Studio для проверки лабораторных работ на C и C++

Дата: 06 Июл 2022

Автор: Андрей Карпов

Встретил очередной вопрос на Stack Overflow от человека, изучающего язык C++. Количество подобных вопросов можно сократить, используя PVS-Studio. Человек сразу может получить ответ, не отвлекая други…
Игра: найди ошибку в C++ коде

Дата: 29 Июн 2022

Автор: Андрей Карпов

Авторы анализатора PVS-Studio предлагают вам проверить свою внимательность и развлечься. Попробуйте быстро отыскать баг в фрагменте исходного кода и ткнуть в него мышкой.
Тем, кто учится программировать и решил написать вопрос на Stack Overflow: "Почему код не работает?"

Дата: 28 Июн 2022

Автор: Андрей Карпов

На сайте Stack Overflow много вопросов от людей, ещё только изучающих языки программирования. Лайфхак: ответы на многие эти вопросы можно получить сразу, запустив анализатор кода. Получится быстрее.
В мире антропоморфных животных: PVS-Studio проверил Overgrowth

Дата: 23 Июн 2022

Автор: Владислав Столяров

Недавно в сети появилась новость о том, что был открыт исходный код игры Overgrowth. Мы не смогли пройти мимо и проверили его качество с помощью PVS-Studio. Давайте же вместе посмотрим, где больше ин…
Как написать рефлексию для C++

Дата: 21 Июн 2022

Автор: Гость

C++ поистине противоречивый язык. Старый добрый С существует аж с 1972 года, С++ появился в 1985 и сохранил с ним обратную совместимость. За это время его не раз хоронили: сперва Java, теперь его пот…

Комментарии (0)

Следующие комментарии
Этот сайт использует куки и другие технологии, чтобы предоставить вам более персонализированный опыт. Продолжая просмотр страниц нашего веб-сайта, вы принимаете условия использования этих файлов. Если вы не хотите, чтобы ваши данные обрабатывались, пожалуйста, покиньте данный сайт. Подробнее →
Принять