Статичний аналіз Wireshark засобами PVS-Studio



У цій статті я розповім, як використовувати PVS-Studio для статичного аналізу програмного коду на мові С/C++ на прикладі open-source проекту Wireshark. Почну я з короткого опису аналізатора мережевого трафіку Wireshark і продукту PVS-Studio. Опишу підводні камені процесу складання і підготовки проекту до статичного аналізу. Спробую сформувати загальну картину про продукт PVS-Studio, його переваги і зручності використання, приводячи попередження аналізатора, приклади коду і власні коментарі.


Аналізатор мережевого трафіку Wireshark
Для демонстрації можливостей PVS-Studio, потрібно знайти відомий, корисний і цікавий open-source проект, аналіз якого ще ніхто не робив. Я зупинився на Wireshark, т. к. сам до нього не байдужий, а якщо ви ще про нього не знаєте, то можливо після прочитання цієї статті розділіть мої почуття до нього.

Стрімкий розвиток інтернету і велику кількість фільмів про програмістах-хакерів давно привернуло мою увагу до комп'ютерних мереж. А зараз, мені здається, що будь-якого кваліфікованого системного адміністратора, програміста, який піклується про безпеку, потрібно розбиратися в мережних технологіях.

В основі мереж лежать прийом і передача даних за певними протоколами. Для того щоб проводити дослідження мережевих додатків і протоколів, а також, щоб знаходити проблеми в роботі мережі, і, що важливо, з'ясовувати причини цих проблем, необхідно використовувати інструменти для захоплення і аналізу мережевого трафіку або сніфери.

Wireshark — це досить відомий інструмент для захоплення і аналізу мережевого трафіку з графічним інтерфейсом користувача. Програма базується на бібліотеці для захоплення мережевого трафіку Рсар, і дозволяє розбирати пакети переважної більшості відомих мережевих протоколів, відображаючи значення кожного поля протоколу будь-якого рівня.

Wireshark — багатоплатформовий інструмент, що розповсюджується під вільною ліцензією GNU GPL, призначений для роботи в Windows і Linux. Для формування графічного інтерфейсу використовуються бібліотеки GTK+ та Qt.

Документацію та вихідні коди програми можна знайти на сайт.

Статичний аналізатор коду PVS-Studio
Статичний аналіз коду дозволяє знаходити помилки в програмному забезпеченні, без запуску додатків і незалежно від робочого оточення. Використовуючи статичний аналіз можна підвищити якість програмного продукту, скоротити час на розробку і тестування, а також забезпечити безпеку.

PVS-Studio — це статичний аналізатор C/C++/C++11 коду, що підтримує компілятори MS Visual С++, GNU GCC (MinGW), Clang, Borland C++.

PVS-Studio містить наступні діагностики:
  • діагностика загального призначення;
  • діагностика 64-бітових помилок;
  • діагностика можливих оптимізацій.
Додаткову інформацію про PVS-Studio можна знайти на сайт.

Складання проекту Wireshark
Для проведення статичного аналізу завантажити вихідні коди самої останньої стабільної версії Wireshark 1.12.4. Складання будемо проводити в операційній системі Windows 7 для платформи Win64 з використанням компілятора, що входить в Visual Studio 2013. Додатково встановимо бібліотеки Qt SDK 5.4.1 і WinPcap 4.1.3.

Складання будемо здійснювати з командного рядка з використанням nmake. Для правильної роботи складальних скриптів встановимо Cygwin і Python 2.7.9.

Додаткову інформацію про збірці можна знайти на сайт.

Незважаючи на те, що збірка проводилася в повній відповідності з інструкцією, виник ряд помилок. Для їх усунення потрібно:
  • Прописати шлях до Cygwin у змінній оточення PATH, щоб зробити доступною з консолі командну оболонку bash.
  • Відключити ACL управління доступом для NTFS в Cygwin, щоб надати власнику права на запис, читання і запуск файлів.
  • Встановити додатковий пакет dos2unix в Cygwin. Т. к. для компіляції потрібна утиліта u2d.
  • Потрібно скопіювати файл Makefile.nmake з «asn1\hnbap» в «asn1\kerberos», щоб запрацювала команда очищення «clean» для nmake.

Проведення статичного аналізу засобами PVS-Studio
У мене встановлена PVS-Studio 5.25 з ліцензією, але для початкового знайомства з програмою можна скачати і встановити демонстраційну версію.

В ознайомлювальному режимі доступні попередження тільки першого рівня, можна зробити лише 50 переходів по коду після установки і 50 переходів після відправки інформації про себе. Після 100 переходів потрібна ліцензія, умови покупки якій Ви можете знайти на сайті. Звичайно, ці 100 переходів недостатньо для використання, і вони даються для першого знайомства з програмою. Якщо хочеться більш щільно познайомитися з аналізатором, то можна написати в підтримку отримати на кілька днів реєстраційний ключ.

Оскільки складання проекту Wireshark здійснюється з допомогою nmake з командного рядка, нам потрібна система моніторингу, яка входить в комплект PVS-Studio. Вона відстежує запуски компіляторів і збирає інформацію про їх оточенні: робочу директорію, командний рядок, повний шлях до компилируемого файлу, змінні оточення процесу.

Для моніторингу запустимо «Пуск\PVS-Studio\PVS-Studio Standalone», виберемо пункт меню «Tools\Analyze Your Files ...» і натиснемо кнопку «Start Monitoring». Далі з командного рядка запустимо складання проекту «nmake-f Makefile.nmake all», як описано вище. Перевіримо, що збірка пройшла успішно і завершимо моніторинг, натиснувши на кнопку «Stop monitoring».

Запасемося терпінням, т. к. далі автоматично запуститься статичний аналіз. Після його завершення збережемо plog-файл звіту, щоб не запускати складання та статичний аналіз кілька разів.

Вже на цьому етапі у програмі PVS-Studio Standalone можна приступити до пошуку помилок. Але щоб використовувати просунуті можливості навігації за кодом IntelliSense, я рекомендую відкрити наш звіт в Microsoft Visual Studio.

Для цього виконаємо ряд дій:
  1. Створимо порожній проект Visual С++ в папці вихідного Wireshark.
  2. В Solution Explorer перейдемо в режим відображення файлів.
  3. Додамо исходники в наш проект.
  4. Відкриємо plog-файл звіту використовуючи плагін: «PVS-Studio\Open Analysis Report».
Ну ось ми і підійшли до найцікавішого — пошуку помилок.

Пошук помилок в проекті Wireshark
Приступимо до пошуку помилок, переглядаючи попередження PVS-Studio і використовуючи навігацію IntelliSense.

З самого початку мене залучили коментарі в коді:
void decode_ex_CosNaming_NamingContext_notfound (....)
{
....
(void)item; /* Avoid coverity param_set_but_unused 
parse warning */
....
/* coverity[returned_pointer] */
item = proto_tree_add_uint (....);
....
}

Ймовірно, проект Wireshark вже регулярно перевіряється статичним аналізатором Coverity, який використовується в проектах, що вимагають високої надійності. До таких проектів відноситься: програмне забезпечення для медичних пристроїв, для ядерних станцій, авіації і останнім часом для вбудованих систем. Так що цікаво буде знайти помилки, які Coverity пропустив.

Щоб сформувати загальну картину можливостей PVS-Studio будемо шукати помилки різних типів, які важко виявити через невизначеного поведінки під час тестування, що вимагають просунутих знань мови C/C++ і просто цікаві. Для цього нам буде достатньо попереджень першого і побіжного перегляду попереджень другого рівня.

Код:
typedef struct AIRPDCAP_SEC_ASSOCIATION {
....
AIRPDCAP_KEY_ITEM *key;
....
}; 

void AirPDcapWepMng (....,AIRPDCAP_KEY_ITEM* key, 
AIRPDCAP_SEC_ASSOCIATION *sa,....)
{
....
memcpy(key, &sa->key, sizeof(AIRPDCAP_KEY_ITEM));
....
}

Попередження: V512 A call of the 'memcpy' function will lead to the '& sa->key' buffer becoming out of range. airpdcap.c 1192

Мови C/C++ забезпечують ефективну низькорівневу роботу з оперативною пам'яттю за рахунок відсутності вбудованих перевірок меж масивів при читанні і запису. Помилки заповнення, копіювання і порівняння буферів пам'яті, можуть призвести до невизначеного поведінці програми або помилок сегментації, які важко виявити.

Для заповнення структури 'AIRPDCAP_KEY_ITEM', що знаходиться за адресою 'key', використовується адреса 'sa->key' на таку ж структуру, а адреса вказівника на неї. Щоб виправити цю помилку, досить прибрати зайву операцію отримання адреси '&'.

Код:
typedef struct _h323_calls_info {
e_guid_t *guid;
....
} h323_calls_info_t;

static const e_guid_t guid_allzero = {0, 0, 0, 
{ 0, 0, 0, 0, 0, 0, 0, 0 } };

void q931_calls_packet (....)
{
h323_calls_info_t *tmp2_h323info;
....
memcmp(&tmp2_h323info->guid, &guid_allzero, 16) == 0;
....
}

Попередження: V512 A call of the 'memcmp' function will lead to overflow of the buffer '& tmp2_h323info->guid'. voip_calls.c 1570

Ще один приклад з неправильним використанням буфера. В одному з аргументів функції 'memcmp()' передається покажчик на покажчик на структуру 'e_guid_t', замість покажчика на неї.

Код:
#define ETHERCAT_MBOX_HEADER_LEN ((int) sizeof(ETHERCAT_MBOX_HEADER))

void dissect_ecat_datagram (....)
{
if (len >= sizeof(ETHERCAT_MBOX_HEADER_LEN) &&....)
{
....
}
}

Попередження: V568 it's odd that the argument of sizeof() is the operator '(int) sizeof (ETHERCAT_MBOX_HEADER)' expression. packet-ethercat-datagram.c 519

При роботі з пам'яттю в C++ використовується оператор 'sizeof()', який повертає розмір об'єкта або буфера в байтах. У нашому випадку 'sizeof()' поверне в байтах розмір типу 'int', замість розміру структури 'ETHERCAT_MBOX_HEADER'. Для виправлення помилки потрібно прибрати зайву операцію 'sizeof()'.

Код:
void Proto_new (....) {
....
if (!name[0] || !desc[0])
luaL_argerror(L,WSLUA_ARG_Proto_new_NAME,
"must not be an empty string");
....
if ( name ) {
....
loname_a = g_ascii_strdown(name, -1);
....
}
....
}

Попередження: V595 The 'name' pointer was utilized before it was verified against nullptr. Check lines: 1499, 1502. wslua_proto.c 1499

Зазвичай, щоб показати, що вказівник не посилається на об'єкт, в нього записують спеціальне нульове значення, а перед використанням покажчика роблять додаткові перевірки. Статичний аналіз дозволяє знайти не дістають перевірки, які можуть порушити безпеку, і надлишкові перевірки, які сильно захаращують код.

Перевірка покажчика 'name' проводиться після використання 'name[0]'. З одного боку, ця перевірка надлишкова, якщо вказівник не нульовий, а з іншого боку, якщо він нульовий, все одно виникне помилка.

Код:
void create_byte_graph (....)
{
....
u_data->assoc=(sctp_assoc_info_t*)g_malloc(
sizeof(sctp_assoc_info_t));
u_data->assoc=userdata->assoc;
....
}

Попередження: V519 The 'u_data->доц' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 1526, 1527. sctp_byte_graph_dlg.c 1527

У мовах C/C++ — виділення і звільнення пам'яті виконується вручну. Помилки звільнення виділеної пам'яті можуть призводити до витоків пам'яті.

Функція 'g_malloc()' виділяє ділянку динамічної пам'яті розміром 'sizeof(sctp_assoc_info_t)' байт і повертає покажчик на нього. Але після зміни значення змінної, що зберігає цей покажчик, ми не зможемо ні отримати доступ до цієї дільниці, ні звільнити його, що призведе до витоку пам'яті.

Код:
PacketList::PacketList(QWidget *parent)
{
QMenu *submenu;
....
submenu = new QMenu(tr("Colorize with Filter"));
/*ctx_menu_.addMenu(submenu);*/
submenu = new QMenu(tr("Copy"));
ctx_menu_.addMenu(submenu);
....
}

Попередження: V519 The 'submenu' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 287, 363. packet_list.cpp 363

У конструкторі динамічно створюються елементи візуального інтерфейсу і додаються в об'єктну ієрархію Qt. Це дозволяє проводити рекурсивне знищення створених об'єктів при видаленні об'єкта верхнього рівня. Проте один з пунктів меню так і не був доданий в об'єктну структуру, що призведе до витоку пам'яті.

Код:
void dissect_display_switch(gint offset, guint msg_len,....)
{
....
if((address_byte&DISPLAY_WRITE_ADDRESS_LINE_FLAG)
!=DISPLAY_WRITE_ADDRESS_LINE_FLAG)
offset+=1;msg_len-=1;
....
}

Попередження: V640 The code's operational logic does not correspond with its formatting. The second statement will always be executed. It is possible that curly brackets are missing. packet-unistim.c 1134

Не правильна розстановка фігурних дужок '{}' для виділення блоків умовних операторів 'if' може призвести до помилок.

Тіло умовного оператора 'if' буде складатися з однієї інструкції, хоча форматування і логіка програми вимагають кілька. Щоб виправити помилку, необхідно укласти декілька інструкцій у фігурні дужки '{}'.

Код:
void dissect_ssc_readposition (....)
{
....
switch (service_action) {
....
case LONG_FORM:
if (!(flags & MPU)) {
....
} else
/*offset += 16;*/
break;
....
}
....
}

Попередження: V705 It is possible that 'else' block was forgotten or commented out, thus altering the program's operation logics. packet-scsi-ssc.c 831

Забавно, але всього лише один коментар, який може привести до зміни логіки роботи програми. Вихід з блоку 'case LONG_FORM' буде виконаний тільки при спрацьовуванні 'else', що неминуче призведе до помилки.

Код:
void set_has_console(gboolean set_has_console)
{
has_console = has_console;
}

Попередження: V570 The 'has_console' variable is assigned to itself. console_win32.c 235

Також зустрічаються помилки, пов'язані з неуважністю. Передбачається, що функція 'set_has_console()' змінює значення 'has_console' на 'set_has_console', проте цього не відбувається. Щоб виправити помилку, необхідно змінної 'has_console' присвоювати значення, передане за допомогою аргументу 'set_has_console'.

Код:
void dissect_dcc(tvbuff_t *tvb, packet_info *pinfo, 
proto_tree *tree, void *data _U_)
{
client_is_le = ( (tvb_get_guint8(tvb, offset+4) 
| tvb_get_guint8(tvb, offset+4)) 
&&(tvb_get_guint8(tvb, offset+8) 
| tvb_get_guint8(tvb, offset+9)) 
&& (tvb_get_guint8(tvb, offset+12) 
| tvb_get_guint8(tvb, offset+13)) );
}

Попередження: V501 There are identical sub-expressions 'tvb_get_guint8(tvb, offset + 4)' to the left and to the right of the '|' operator. packet-dcc.c 272

Повторюється вираз tvb_get_guint8(tvb, offset+4). За аналогією можна припустити, що планували написати tvb_get_guint8(tvb, offset+5).

Були й інші помилки, про які я не став писати, щоб не захаращувати статтю. Наведених прикладів має бути достатньо, щоб показати можливості статичного аналізу, і привернути увагу людей до PVS-Studio. Якщо необхідно отримати повне уявлення про можливості PVS-Studio, на сайті можна знайти на список всіх можливих попереджень. Більш ретельний аналіз Wireshark можуть здійснити самі розробники. Їм буде набагато легше зрозуміти, чи є щось помилкою чи ні.

Висновок
Підозрілих ділянок коду знайшлося не так багато. Ймовірно, через використання статичного аналізатора Coverity, коментарі про який ми бачили. Тому всім рекомендую регулярно використовувати статичні аналізатори у своїх проектах, щоб виявляти помилки ще до тестування на етапі написання коду.

Бажаю всім успіхів в програмуванні і поменше помилок.


Якщо хочете поділитися цією статтею з англомовної аудиторією, то прошу використовувати посилання на переклад: Andrey Kalashnikov. Static Analysis of Wireshark by PVS-Studio.

Прочитали статтю і є питання?Часто до наших статей задають одні і ті ж питання. Відповіді на них ми зібрали тут: Відповіді на питання читачів статей про PVS-Studio і CppCat, версія 2015. Будь ласка, ознайомтеся зі списком.


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.