Що таке безпечна мова програмування?


54

Безпечні мови програмування (PL) набувають все більшої популярності. Цікаво, яке формальне визначення безпечного ПЛ. Наприклад, C не є безпечним, але Java - безпечним. Я підозрюю, що властивість "безпечно" слід застосовувати до реалізації ПЛ, а не до самої ПЛ. Якщо так, давайте обговоримо визначення безпечної реалізації ПЛ. Мої власні спроби формалізувати це поняття призвели до дивного результату, тому я хотів би почути інші думки. Будь ласка, не кажіть, що кожен PL має небезпечні команди. Ми завжди можемо взяти безпечну підмножину.


Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
Жил 'ТАК - перестань бути злим'

"ми завжди можемо взяти безпечну підмножину" Як ви можете бути впевнені, що отримана мова все ще є повною для Тьюрінга? (що зазвичай означає "мова програмування")
Effeffe

"властивість" safe "повинна застосовуватися до реалізації PL, а не до самої PL" - ви можете викликати сейф PL, якщо безпечна його реалізація існує.
Дмитро Григор’єв

Відповіді:


17

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

Кілька важливих з них:

  • Визначення Ендрю Райт та Маттіаса Феллейсена як "тип міцності" , яке в багатьох місцях (включаючи Вікіпедію) цитується як прийняте визначення поняття "безпека типу", і їх доказ 1994 року про те, що підмножина ML відповідає цьому.

  • Майкл Хікс перелічує тут декілька визначень „безпеки пам’яті” . Деякі - це списки типів помилок, які не можуть виникнути, а деякі засновані на розгляді покажчиків як можливостей. Java гарантує, що жодна з цих помилок неможлива (якщо ви явно не використовуєте функцію, позначену unsafe), якщо сміттєзбірник керує всіма розподілами та розсилками. Іржа дає таку ж гарантію (знову ж таки, якщо ви чітко не позначаєте код як unsafe) через систему афінного типу, яка вимагає, щоб змінна була або власником, або запозиченим, перш ніж використовуватись максимум.

  • Аналогічно, безпечний для потоків код зазвичай визначається як код, який не може виявляти певні типи помилок, що включають потоки та спільну пам'ять, включаючи перегони даних та тупикові місця. Ці властивості часто застосовуються на мовному рівні: Rust гарантує, що гонки даних не можуть відбуватися у своїй системі типів, C ++ гарантує, що його std::shared_ptrрозумні покажчики на ті самі об'єкти в декількох потоках не видалять об'єкт передчасно або не зможуть видалити його, коли остання посилання якщо це знищено, C і C ++ додатково мають atomicвбудовані в мову змінні, при цьому атомні операції гарантуються для забезпечення певних типів послідовності пам'яті при правильному використанні. MPI обмежує міжпроцесорне спілкування явними повідомленнями, а OpenMP має синтаксис, щоб забезпечити безпеку доступу до змінних з різних потоків.

  • Властивість, яку пам'ять ніколи не просочується, часто називають безпечною для простору. Автоматичне вивезення сміття - це одна мовна функція, яка забезпечує це.

  • Багато мов мають гарантію того, що його діяльність матиме чітко визначені результати та її програми будуть добре працювати. Як supercat наводив приклад вище, C робить це для арифметики без підпису (гарантовано безпечно обертатися), але не для підписаної арифметики (де переповнення дозволено викликати довільні помилки, оскільки C потрібно підтримувати процесори, які роблять дикі різні речі при підписанні арифметики переповнює), але тоді мова іноді мовчки перетворює непідписані величини в підписані.

  • У функціональних мовах є велика кількість інваріантів, що будь-яка добре сформована програма гарантовано підтримує, наприклад, що чисті функції не можуть викликати побічні ефекти. Вони можуть бути або не можуть бути описані як "безпечні".

  • Деякі мови, такі як SPARK або OCaml, розроблені таким чином, щоб сприяти доказуванню правильності програми. Це може бути або не може бути описано як "безпечне" від помилок.

  • Докази того, що система не може порушувати офіційну модель захисту (отже, напис, "Будь-яка система, яка є надійно захищеною, ймовірно, не є.")


1
Це може бути або не може бути описано як "безпечне" від помилок. Чи хотіли б ви трохи розібратися в цьому? Що ви маєте на увазі під "від помилок"?
scaaahu

2
@scaaahu Ось приклад веб-сайту, який офіційно підтверджує, що програмне забезпечення, як "доказувально безпечне". У цьому контексті мається на увазі програмне забезпечення для запобігання зіткнення літака, тому воно означає безпеку від зіткнень.
Девіслор

1
Я приймаю цю відповідь, оскільки в ній перераховані види безпеки. Я мав на увазі безпеку пам’яті.
beroal

Хоча у цій відповіді перераховано кілька корисних посилань та безліч прикладів, більшість із них повністю переплутані. Збір сміття гарантує, що пам'ять ніколи не просочується або не використовує "небезпечні" блоки автоматично, що забезпечує безпеку або переповнення підпису, що є не визначеною поведінкою, оскільки компіляторам С потрібно серйозно підтримувати деякі дивні процесори? І лише коротке слово для Ada / SPARK, яка є єдиною із згаданих мов, яка серйозно сприймає безпеку.
ВТТ

93

Не існує формального визначення поняття "безпечна мова програмування"; це неформальне поняття. Швидше, мови, які претендують на безпеку, зазвичай дають точну формальну заяву про те, який вид безпеки вимагається / гарантується / надається. Наприклад, мова може забезпечити безпеку типу, безпеку пам’яті чи якусь іншу подібну гарантію.


13
Якщо говорити про addeumdum, якщо говорити про C vs Java, як пост OP: це безпека пам’яті, яка гарантується в Java, а не в C. Безпека типу забезпечується обома по-своєму. (Так, багато людей, які читають це, вже знають це, але, можливо, деякі й не знають).
Вальфрат

17
@Walfrat Це частина цього. Java також не має визначеної поведінки, що ми очікуємо від мови, яка називає себе "безпечною". Що стосується систем типів, я не думаю, що сильна система статичного типу - це те, що люди схильні означати під "безпечним". Динамічно набрані мови, такі як Python, як правило, "безпечні".
Макс Барраклу

2
Моє визначення безпеки типу - це перевірка компіляції, яка з цим справляється. Це може бути не формальним визначенням. Зауважте, що я сказав "тип безпеки", а не "безпечно". Для мене "безпечною" мовою є "моє" визначення "типу та безпеки пам'яті", і я думаю, що це може бути найпоширенішим. Звичайно, я не говорю про якісь підводні камені, такі як відображення / недійсний покажчик на C, що компіляція не може впоратися. Ще одне можливе визначення безпечності - це програма, яка не збивається з помилкою сегмента, як неітіалізований покажчик у C. Такі речі, як правило, надаються в Python та Java.
Вальфрат

7
@Walfrat Все, що отримує вас, - це мова, де синтаксис добре визначений. Це не гарантує, що виконання чітко визначене - і скільки разів я бачив аварію JRE, я можу вам сказати, що це як система не є "безпечною". З іншого боку, на C, MISRA вклала роботу, щоб уникнути невизначеної поведінки, щоб отримати більш безпечну підмножину мови, і компіляція C у асемблер набагато краще визначена. Отже, це залежить від того, що ви вважаєте "безпечним".
Грем

5
@MaxBarraclough - "Java також не має визначеної поведінки" - у Java немає невизначеної поведінки у тому сенсі, який використовується у специфікаціях C у мовному визначенні (хоча він дозволяє деякому коду створювати значення, які не мають жодного заздалегідь визначеного значення, наприклад, доступ до нього змінна, яка модифікується в іншому потоці, або отримуючи доступ до doubleабо longпід час модифікації в іншому потоці, що не гарантується, що не видасть половини одного значення, змішаного певним чином, із половиною іншого), але специфікація API проте в деяких випадках є невизначена поведінка.
Жуль

41

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

Одне із запропонованих тлумачень терміна, який може вам бути цікавим, це:

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

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


7
Проблема зупинки, звичайно, говорить, що незалежно від мови, завжди знайдуться програми, поведінка яких не можна передбачити з мовного визначення. Отже, будь-яке визначення, яке залежить від "прогнозування поведінки кожної програми на мові", є принципово хибним для будь-якої мови Тюрінга.
MSalters

15
@MSalters Це популярне непорозуміння проблеми зупинки. Невирішеність проблеми зупинки означає, що механічно виводити поведінку довільної програми немовно механічно не можна . Але можливо , що для будь-якої даної програми, поведінка передбачувано. Просто ви не можете зробити комп'ютерну програму, яка робить це передбачення.
Жил "ТАК - перестань бути злим"

7
@Giles: Це не так. Припустимо, існує доказ неприпинення для кожної програми, що не припиняється. Потім ви можете перерахувати докази неприпинення, щоб визначити, чи припиняється дана програма. Тож проблема зупинки вирішена. Протиріччя. Таким чином, деякі програми, які не припиняють, є, очевидно, не припиненими.
Кевін

9
@Gilles: Я прекрасно знаю той факт, що багато програм банально доведено зупиняти чи ні. Але тут твердження буквально стосується поведінки кожної програми. Доказ теореми припинення показує, що існує принаймні одна програма, для якої це неправда. Це просто неконструктивний доказ, він не скаже вам, яку програму не можна визначити.
MSalters

8
@MSalters Я думаю, що біт мається на увазі, що твердження стосується маломасштабної поведінки програми, а не масштабної поведінки. Наприклад, візьмемо гіпотезу Коллаца . Окремі кроки алгоритму прості та чітко визначені, але поведінка, що виникає (скільки ітерацій до зупинки, і якщо вона взагалі є) - це що завгодно, але. - "Прогноз" тут використовується неформально. Це може бути краще записано як "знати, як виконується будь-яке задане у довільній програмі".
RM

18

Безпечний не є бінарним, це континуум .

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

  • Безпека пам’яті: мова та її реалізація запобігають різноманітним помилкам, пов’язаним з пам’яттю, такими як використання після використання, без подвійного доступу, поза межами доступу, ...
  • Безпека типу: мова та її реалізація запобігають різноманітним помилкам, пов’язаним з типом, наприклад, неперевірені касти,

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

Просто неправильні програми рідко вважаються "небезпечними" (лише помилками), а термін "безпека", як правило, зарезервований для гарантій, що впливають на нашу здатність міркувати про програму. Таким чином, C, C ++ або Go, що мають не визначену поведінку, небезпечні.

І звичайно, є мови з небезпечними підмножинами (Java, Rust, ...), які цілеспрямовано окреслюють області, де розробник несе відповідальність за підтримку мовних гарантій, а компілятор перебуває в режимі "відключення". Мови, як правило, називаються безпечними , незважаючи на цей випускний люк, прагматичне визначення.


7
Я б сказав, що це решітка.
PatJ

1
Більшість мов програмування мають небезпечні функції (наприклад, Obj.magicв Ocaml). І на практиці вони справді потрібні
Василь Старинкевич

4
@BasileStarynkevitch: Дійсно. Я думаю, що будь-яка мова з FFI обов'язково містить певний рівень небезпеки, оскільки для виклику функції C потрібно буде "в'язати" об'єкти GC'ed і вручну переконатися, що підписи обох сторін збігаються.
Маттьє М.

15

Хоча я не погоджуюся з відповіддю DW, я думаю, що це залишає одну частину "безпечного" без розгляду.

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

Слід зазначити, що у цих різних понять є різні класи помилок, і ці класи не є взаємовиключними, і вони не охоплюють усіх форм помилок. Просто для того, щоб взяти 2 приклади DW, питання про те, чи є певне місце пам’яті певним об'єктом, є питанням безпеки типу та безпеки пам’яті.

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


10
Це досить не тематично для цього сайту, оскільки інженерія програмного забезпечення насправді не є наукою, але я не згоден з вашим емпіричним твердженням. Використання хороших бібліотек не збереже вас на небезпечних мовах, оскільки ви не захищені від неправильного їх використання. Безпечні мови дозволяють отримати більше гарантій від автора бібліотеки та дозволять отримати більше впевненості, що ви правильно їх використовуєте.
Жил "ТАК - перестань бути злим"

3
Я з MSalters з цього приводу. - "Безпечні мови дозволяють отримати більше гарантій від автора бібліотеки та дозволять вам отримати більшу впевненість у тому, що ви правильно їх використовуєте." Це не послідовник для всіх практичних цілей.
Капітан Жирафа

9

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

Традиційно компілятори C обробляли б багато дій у визначеному Стандартом режимі у «звичайних» випадках, обробляючи безліч кутових справ «у спосіб, характерний для навколишнього середовища». Якщо хтось використовував процесор, який би вийшов з ладу і загорівся, якщо сталося числове переповнення, і хотілося уникнути загоряння процесора, потрібно було б написати код, щоб уникнути числового переповнення. Якщо ж хтось користувався процесором, який би абсолютно щасливо скорочував значення у способі доповнення двох, не довелося б уникати переповнення у випадках, коли таке укорочення призведе до прийнятної поведінки.

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

 uint32_t test(uint16_t x)
 {
   if (x < 50000) foo(x);
   return x*x; // Note x will promote to "int" if that type is >16 bits.
 }

"Сучасний" компілятор C, що надає щось подібне, може зробити висновок, що оскільки обчислення x * x переповнюється, якщо x більше 46340, він може виконувати виклик "foo" беззастережно. Зауважте, що навіть якщо програма буде аномально закінчуватися, якщо x знаходиться поза діапазоном, або функція повертає будь-яке значення в таких випадках, виклик foo () з поза діапазоном x може завдати шкоди набагато більше будь-яка з цих можливостей. Традиційний C не забезпечить жодних засобів безпеки за винятком того, що постачає програміст та базову платформу, але дозволить запобіжникам обмежити шкоду від несподіваних ситуацій. Сучасний C обійде будь-які засоби безпеки, які не на 100% ефективні, щоб тримати все під контролем.


3
@DavidThornley: Можливо, мій приклад був занадто тонким. Якщо intце 32 біти, тоді xбуде отримано підвищення до підписання int. Судячи з обгрунтування, автори стандарту очікується , що ні-дивні реалізації лікуватимуть підписаних і непідписаних типів в еквівалентним чином за межами деяких конкретних випадках, але GCC іноді «оптимізує» таким чином , щоб зламатися , якщо uint16_tшляхом uint16_tмноження дає результат , що перевершує INT_MAX , навіть коли результат використовується як неподписане значення.
supercat

4
Хороший приклад. Це одна з причин, чому ми завжди повинні (з GCC або Clang) компілювати -Wconversion.
Девіслор

2
@Davislor: Ах, я щойно помітив, що godbolt змінив порядок, у якому перераховані версії компілятора, тому вибір останньої версії gcc у списку дає останню, а не саму ранню. Я не думаю, що попередження є особливо корисним, оскільки воно схильне позначити безліч ситуацій, як return x+1;це не повинно бути проблематичним, а передача результату на uint32_t може задушити повідомлення, не виправляючи проблему.
supercat

2
@supercat Усунення тестів є безглуздим, якщо компілятор вимагає повернути тести в інше місце.
користувач253751

3
@immibis: Директива "перевірено припустити" може дозволити компілятору замінити багато тестів або перевірку, яка буде виконуватися багато разів у циклі, з одним тестом, який може бути піднятий поза циклом. Це краще, ніж вимагати від програмістів додавати чеки, які не потребують машинного коду для того, щоб програма відповідала вимогам, щоб переконатися, що компілятор не "оптимізує" перевірки, необхідні для задоволення вимог.
supercat

7

У мові є кілька шарів правильності. У порядку збільшення абстракції:

  • Мало програм без помилок (лише ті, для яких можна довести правильність). Інші вже згадували, що обмеження помилок є, таким чином, найбільш конкретним аспектом безпеки. Мови, які працюють у віртуальній машині, такі як Java та .net, як правило, більш безпечні в цьому відношенні: помилки програми зазвичай перехоплюються та обробляються певним чином. 1
  • На наступному рівні помилки, виявлені під час компіляції замість часу виконання, роблять мову більш безпечною. Синтаксично правильна програма також повинна бути максимально семантично правильною. Звичайно, компілятор не може знати велику картину, тому це стосується рівня деталізації. Сильні та виразні типи даних є одним із аспектів безпеки на цьому рівні. Можна сказати, що мова має ускладнювати певні помилки(вводити помилки, позамежений доступ, неініціалізовані змінні тощо). Інформація про тип часу виконання, наприклад масиви, які несуть інформацію про довжину, уникають помилок. Я запрограмував Ada 83 в коледжі і виявив, що компілююча програма Ada зазвичай містить, можливо, на порядок менше помилок, ніж відповідна програма C. Просто скористайтеся здатністю Ада визначати цілі типи, які не можна присвоїти без явного перетворення: Цілі космічні кораблі зазнали аварії, тому що ноги та метри були плутані, чого можна було б тривіально уникнути з Ada.

  • На наступному рівні мова повинна передбачати засоби для уникнення кодового коду. Якщо вам доведеться написати власні контейнери або їх сортування, або їх об'єднання, або якщо ви повинні написати свій власний, string::trim()ви будете помилятися. Оскільки рівень абстрагування підвищується, цей критерій включає як належну мову, так і стандартну бібліотеку мови.

  • У ці дні мова повинна забезпечити засоби для одночасного програмування на мовному рівні. Паралельно важко виправитись і, можливо, неможливо зробити правильно без мовної підтримки.

  • Мова має забезпечувати модуляризацію та співпрацю. Сильні, розроблені, визначені користувачем типи зверху допомагають створювати виразні API.

Дещо ортогонально визначення мови повинно бути зрозумілим; мова та бібліотеки повинні бути добре задокументовані. Неправильна або відсутня документація призводить до поганих і неправильних програм.


1 Оскільки зазвичай правильність віртуальної машини неможливо довести, такі мови можуть дещо парадоксально не підходити для дуже суворих вимог безпеки.


1
+1 Для чіткого пояснення по шару. Питання до вас, цілі космічні кораблі розбилися через те, що ноги і метри були плутані, чого можна було б тривіально уникнути з Адою. , ти говориш про програму Mars Probe, загублену через просту математичну помилку ? Ви не знаєте мову, якою вони користувалися для цього космічного корабля?
scaaahu

2
@scaaahu Так, я думаю, що я мав на увазі це. Ні, я не знаю мови. Насправді, читаючи звіт, здається, що дані, що надсилаються зондом, оброблялися програмним забезпеченням на землі, створюючи файл даних, який потім використовувався для визначення рівня тяги. Просте введення мови не застосовується в цьому сценарії. До речі, у них виникли декілька проблем із наземним програмним забезпеченням та форматом файлів даних, плутанина, яка перешкоджала ранньому виявленню проблеми. Тож історія не є прямим аргументом для сильного набору тексту, але все-таки застереження.
Пітер - Відновіть Моніку

1

Будь ласка, не кажіть, що кожен PL має небезпечні команди. Ми завжди можемо взяти безпечну підмножину.

Кожна мова, яку я знаю, має способи написання незаконних програм, які можна (компілювати та) запускати. І кожна мова, яку я знаю, має безпечну підмножину. Отже, яке ваше питання насправді?


Безпека є багатовимірною та суб'єктивною.

Деякі мови мають безліч небезпечних операцій. Інші мають менше таких операцій. У деяких мовах спосіб за замовчуванням робити щось по суті є небезпечним. В інших же спосіб за замовчуванням безпечний. У деяких мовах є явна "небезпечна" підмножина. В інших мовах такого підмножини взагалі немає.

У деяких мовах "безпека" означає виключно безпеку пам'яті - послугу, пропоновану стандартною бібліотекою та / або час виконання, коли порушення доступу до пам'яті ускладнюється або неможливо. В інших мовах "безпека" явно включає безпеку потоку. В інших мовах "безпека" посилається на гарантію того, що програма не вийде з ладу (вимога, яка включає в себе заборону не допускати вилучень будь-якого виду). Нарешті, у багатьох мовах "безпека" позначає безпеку типу - якщо система типів певним чином узгоджується, її називають "звуковою" (до речі, у Java та C # немає повністю звукових типів).

І в деяких мовах усі різні значення "безпеки" вважаються підмножинами безпеки типу (наприклад, Rust і Pony досягають безпеки ниток завдяки властивостям системи типів).


-1

Ця відповідь трохи ширша. Слова безпека та безпека в останні десятиліття були понівечені певними політично орієнтованими частинами англомовного суспільства, так що їх загальне вживання майже не має визначення. Однак для технічних предметів я все ще повертаюся до визначення "безпеки" та "безпечності" як: пристрій, який запобігає ненавмисному використанню чогось або, що робить випадкове використання істотно складнішим, і стан, який знаходиться під захистом такого пристрою .
Таким чином, безпечна мова має певний пристрій для обмеження певного класу помилок. Звичайно, обмеження в деяких випадках виникають з незручністю або навіть неможливістю, і це не означає, що "небезпечні" мови призводять до помилок. наприклад, у мене на вилах немає захисних пробок, і я десятиліттями вдавався, не докладаючи великих зусиль, щоб уникнути колоти око під час їжі. Звичайно менше зусиль, ніж було б витрачено на використання пробок. Тож Безпека має певні витрати, за які її потрібно судити. (пробкова виделка - це посилання на персонажа Стіва Мартіна)

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