Чи можуть методи перевірки програм запобігти появі помилок жанру Heartbleed?


9

З приводу помилки із сердечкою, Брюс Шнайер написав у своїй крипто-грамі від 15 квітня: "Катастрофічний" - це правильне слово. За шкалою від 1 до 10 це 11. ' Я прочитав кілька років тому, що ядро ​​певної операційної системи було суворо перевірено сучасною системою перевірки програм. Чи можуть, таким чином, запобігти появі помилок жанру Heartbleed, застосовуючи методи перевірки програм, чи це все ще нереально або навіть принципово неможливо?


2
Ось цікавий аналіз цього питання Дж. Регера.
Мартін Бергер

Відповіді:


6

Щоб відповісти на ваше запитання найбільш стисло - так, ця помилка могла бути спіймана формальними інструментами перевірки. Дійсно, властивість "ніколи не надсилати блок, який перевищує розмір надісланого сигналу", досить просто формалізувати в більшості мов специфікації (наприклад, LTL).

Проблема (яка є поширеною критикою проти формальних методів) полягає в тому, що специфікації, які ви використовуєте, написані людьми. Дійсно, формальні методи лише перекладають проблему на пошук помилок з пошуку помилок на визначення того, що таке помилки. Це непросте завдання.

Крім того, формально перевірити програмне забезпечення, як відомо, важко через проблеми вибуху. У цьому випадку це особливо актуально, оскільки багато разів, щоб уникнути вибуху держави, ми абстрагуємо межі. Наприклад, коли ми хочемо сказати, що "за кожним запитом йде грант, протягом 100000 кроків", нам потрібна дуже довга формула, тому ми абстрагуємо її до формули "з кожним запитом зрештою слідує грант".

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

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


5

Комерційні перевіряючі програми, такі як Klocwork або Coverity, можливо, змогли знайти Heartbleed, оскільки це порівняно просто "забув зробити помилку перевірки меж", що є однією з головних проблем, для якої вони покликані перевірити. Але є набагато простіший спосіб: використовувати непрозорі абстрактні типи даних, які добре перевірені, щоб не було перевищення буфера.

Існує ряд абстрактних типів даних "безпечного рядка", доступних для програмування на С. Той, з ким я найбільше знайомий, - це Vstr . Автор, Джеймс Antill, має велике обговорення про те, чому вам потрібно струнний абстрактний тип даних зі своїми власними конструкторами / фабричних методів , а також список інших строкових абстрактних типів даних для C .


2
Покритість не знайшла Heartbleed, дивіться цей аналіз Джона Регера.
Мартін Бергер

Приємне посилання! Це демонструє справжню мораль історії: перевірка програми не може компенсувати погано розроблені (або неіснуючі) абстракції.
Мандрівна логіка

2
Це залежить від того, що ви маєте на увазі під перевіркою програми. Якщо ви маєте на увазі статичний аналіз, то так, це завжди наближення, як прямий наслідок теореми Райса. Якщо ви перевіряєте повну поведінку в пробірці інтерактивної теореми, то ви отримуєте чавунну гарантію того, що програма відповідає її специфікаціям, але це надзвичайно трудомістко. І ви все ще стикаєтеся з проблемою, що ваші технічні характеристики можуть бути неправильними (див., Наприклад, вибух Ariane 5).
Мартін Бергер

1
@MartinBerger: Coverity знаходить це зараз .
Відновіть Моніку - М. Шредер

4

Якщо ви вважаєте "  технікою перевірки програми  " комбінацію перевірки обмеженої тривалості виконання та таємниці, так, саме ця помилка могла бути спіймана .

Правильне запалювання призведе до того, що тепер сумно відоме memcpy(bp, pl, payload);читання через межу блоку пам'яті pl. Перевірка обмеженої тривалості роботи може, в принципі, зафіксувати будь-який такий доступ, і на практиці в цьому конкретному випадку навіть налагоджена версія, mallocяка переймається перевіркою параметрів memcpy, зробила б цю роботу (тут не потрібно возитися з MMU) . Проблема полягає в тому, що виконання тестів на плавлення на кожному типі мережевого пакету вимагає зусиль.


1
Хоча це взагалі вірно, IIRC, у випадку OpenSSL, автори реалізували власне управління внутрішньою пам’яттю таким чином, що було набагато менше шансів memcpyпотрапити на справжню межу (великого) регіону, яку спочатку вимагали від системи malloc.
Вільям Прайс

Так, у випадку OpenSSL, як це було в момент помилки, memcpy(bp, pl, payload)довелося б перевіряти межі, використовувані mallocзаміною OpenSSL , а не системою malloc. Це виключає автоматизовану перевірку обмежень на бінарному рівні (принаймні, без глибоких знань про mallocзаміну). Повинна бути рекомпіляція з майстром рівня джерела, використовуючи, наприклад, макроси C, що замінюють маркер mallocабо будь-яку іншу заміну OpenSSL; і, здається, нам потрібно те саме, за memcpyвинятком дуже хитромудрих трюків ММУ.
fgrieu

4

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

Арифметика вказівника, як це зазвичай формулюється, не є голосною, оскільки система типів насправді не означає, що вона повинна означати. Ви можете повністю уникнути цієї проблеми, працюючи мовою зібраного сміття (звичайний підхід, який змушує вас також платити за абстрагування). Або ви можете бути набагато більш конкретними щодо того, які типи покажчиків ви використовуєте, щоб компілятор міг відхилити все, що непослідовно або просто не може бути доведено правильним як написане. Це підхід деяких мов, як Руст.

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

  • int * x; // Неправдиве твердження. x існує і не вказує на int
  • int * y = z; // Вірно лише, якщо z доведено, що z вказує на інт
  • * (x + 3) = 5; // Вірно лише, якщо (x + 3) вказує на int у тому ж масиві, що і x
  • int c = a / b; // Вірно лише, якщо b ненульовий, наприклад: "nonzero int b = ...;"
  • нульовий int * z = NULL; // nullable int * - не те саме, що int *
  • int d = * z; // Неправдиве твердження, оскільки z є нульовим
  • якщо (z! = NULL) {int * e = z; } // Добре, тому що z не є нульовим
  • вільний (у); int w = * y; // Неправдиве твердження, тому що y більше не існує при w

У цьому світі покажчики не можуть бути нульовими. Відміни NullPointer не існують, і покажчики не повинні перевірятись на нікчемність ніде. Натомість "нульовий int *" - це інший тип, який може отримати своє значення або до нуля, або до вказівника. Це означає, що в точці, де невідмінна припущення, ви або перейдете до журналу свого винятку, або вниз по нульовій гілці.

У цьому світі помилок масиву поза межами також не існує. Якщо компілятор не може довести, що він знаходиться в межах, спробуйте переписати його, щоб компілятор міг це довести. Якщо це не вдається, то вам доведеться поставити Успенське вручну на цьому місці; компілятор може виявити протиріччя цьому згодом.

Крім того, якщо ви не можете мати покажчик, який не ініціалізований, у вас не буде вказівників на неініціалізовану пам'ять. Якщо у вас є вказівник на звільнену пам'ять, його слід відхилити компілятором. У Іржі існують різні типи покажчиків, щоб зробити такі докази розумними для очікування. Існують виключно вказівники, що належать (тобто: немає псевдонімів), вказівники на глибоко незмінні структури. Тип зберігання за замовчуванням незмінний тощо.

Існує також питання застосування фактично чітко визначеної граматики на протоколах (до яких входять члени інтерфейсу), щоб обмежити площу вхідної поверхні саме тим, що передбачається. Річ у "правильності" полягає в: 1) Позбавленні від усіх невизначених станів 2) Забезпечення логічної послідовності . Складність потрапляння туди має багато спільного з використанням надзвичайно поганих інструментів (з точки зору правильності).

Саме тому дві найгірші практики - глобальні змінні та гото. Ці речі заважають ставити умови перед / після / інваріантними навколо будь-чого. Це також, чому типи такі ефективні. У міру того, як типи посилюються (зрештою, використовуючи залежні типи для врахування фактичного значення), вони підходять як конструктивні докази коректності самі по собі; що робить невідповідні програми компіляції не вдається.

Майте на увазі, що мова йде не лише про німі помилки. Йдеться також про захист бази коду від розумних інфільтраторів. Будуть випадки, коли вам доведеться відхилити подання без переконливого машинного доказу важливих властивостей, таких як "слідує формально визначений протокол".


2

Засоби статистичного аналізу, такі як Coverity, дійсно можуть знайти HeartBleed та подібні дефекти. Повне розкриття: Я співпрацював із Покриттям.

Дивіться мій пост у блозі про наше дослідження щодо HeartBleed та про те, як було розширено наш аналіз для його виявлення:

http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html


1

автоматизована / формальна перевірка програмного забезпечення є корисною і може допомогти в деяких випадках, але, як вказували інші, це не срібна куля. можна відзначити, що OpenSSL є вразливим у тому, що його відкритий код, і все ж використовується комерційний та загальногалузевий, широко використовуваний, а також не є ретельно перевіреним перед випуском (можна задатися питанням, чи є в проекті навіть якісь платні розробники). дефект був виявлений в основному за допомогою огляду коду після випуску, і код, очевидно, був переглянутий перед випуском (зауважте, хоча, мабуть, немає способу відстежити, хто робив огляд внутрішнього коду). "навчальний момент" із сердечним набором (серед багатьох інших) є в основному кращим оглядом коду в ідеалі, перш ніж випустити esp високочутливого коду, можливо, краще відстежувати. можливо, OpenSSL тепер буде піддаватися більшій увазі.

більше Bkg від засобів масової інформації, що деталізують його походження:

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.