Ще раз про витік атомів і баге VCL

Введення
Переглядаючи стрічку, натрапив на статтю Неправильне використання атомів і важковловима бага в VCL . Після прочитання виникла думка описати ще одну проблему в тій же самій області, про яку не розказано в цій статті. Наша команда натрапила на неї самостійно, потім виявилося, що це вже відомий баг VCL. Розробка ведеться на Delphi 7, і я не впевнений, чи існує помилка в більш нових версіях. Судячи з наведених трохи нижче посиланнях, є, як є і виправлення. На виправлення версії 7 сподіватися, зі зрозумілих причин, не доводиться.
 
У статті MrShoor описується переповнення т.зв. таблиці атомів у разі, якщо додаток на Delphi завершено некоректно, і деякі атоми, не видаляються. Виявляється, для переповнення таблиці атомів зовсім необов'язково «вбивати насильно» вашу програму. Цілком достатньо його запустити і коректно закрити, але багато-багато разів поспіль.
Давайте подивимося, як це відбувається:
 
 
Опис механізму переповнення
Після отримання чергової скарги а-ля «out of memory», ми виявили, що таблиця атомів забита елементами виду ControlOfs <шістнадцятковий ID>. Ці елементи з'являються при запуску кожної програми (а наш сервер додатків запускає екземпляри по одному для кожного СОДІНЕНІЯ), і залишаються в таблиці назавжди.
 
Розглянемо ще раз ділянку коду з InitControls в Controls.pas — той же самий, що і в згаданій вище статті:
 
 
WindowAtomString := Format('Delphi%.8X',[GetCurrentProcessID]);
  WindowAtom := GlobalAddAtom(PChar(WindowAtomStrinjg));
  ControlAtomString := Format('ControlOfs%.8X%.8X', [HInstance, GetCurrentThreadID]);
  ControlAtom := GlobalAddAtom(PChar(ControlAtomString));
  RM_GetObjectInstance := RegisterWindowMessage(PChar(ControlAtomString));

 
Сама остання рядок, як здавалося, теж створює атом. При реєстрації нового типу повідомлення також в таблицю додається новий атом, який щоразу має нове ім'я. Проблема в тому, що RegisterWindowsMessage см. MSDN в принципі не передбачає зворотного unregister дії, тому що дана функція дозволяє декільком програмам використовувати деякий Message ID спільно:
 
The RegisterWindowMessage function is typically used to register messages for communicating between two cooperating applications.
 
If two different applications register the same message string, the applications return the same message value. The message remains registered until the session ends .
Це вже серйозно, тому що у нас немає ніяких важелів впливу на ситуацію. Закрити такий атом стороннім додатком неможливо — програма, запропонована в статті, що спонукала мене до написання даного поста, тут безсила. Так як таблиця атомів має коріння в 16 бітної епосі, що накладає обмеження на розмір таблиці, то цей прикрий баг досить швидко виводив серверну частину з ладу, тому що не було можливості запустити жодне Delphi додаток без перезавантаження системи.
 
Ось приклад того, як виглядає таблиця атомів після 20 ітерацій «нормальний запуск-коректне завершення» для простої програми з 1 вікна:
 
Червоним виділені ті атоми, що залишилися якраз після 20 запусків.
 
Ця помилка була описана в кількох місцях, наприклад:
 Детальний опис помилки
 Більш короткий bug report
Ну, і звичайно ж, Stack Overflow
 
 
Метод рішення
Оскільки видалити атом пост-фактум не можна, потрібно не допустити його створення. Наша команда пішла шляхом IAT хука, який перехоплює виклик до RegisterWindowMessageA і у випадку, якщо реєструється повідомлення з ім'ям виду ControlOfs <щось там>, замість нього реєструється будь-який інший ідентифікатор, який однаковий для всіх додатків. Як виявилося, йому зовсім не обов'язково було бути унікальним, на що також вказується в баг репорті, на який я вже посилався .
 
Код хука тривіальний, як і механізм установки його. Більш того, в інтернеті є готові бібліотеки для Delphi для хуков на IAT. Сам хук всього навсього перевіряє максимально швидким способом, не відповідає чи реєстроване повідомлення префіксу ControlOfs, і, у разі, якщо відповідає, відбувається підміна на RM_GetObjectInstance — подібний ідентифікатор, однаковий для всіх додатків, з викликом оригінальної RegisterWindowMessage.
 
Сподіваюся, комусь це допоможе уникнути довгої і важкої налагодження.

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

0 коментарів

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