Стандартный подход к созданию ответственных приложений для бизнеса и критически важной инфраструктуры — это подход «снизу вверх», когда начинают с надежного фундамента, выбирая аппаратную архитектуру и системное ПО, а поверх него разрабатывают приложение и обеспечивают его безопасное выполнение. Убедиться в надежности микропрограммного и системного ПО (ОС, гипервизор, диспетчер ресурсов и т. д.) помогают формальные методы доказательства корректности микроядерной системы [1].

Под управлением Linux, операционной системы с монолитным ядром, сегодня работают многие современные системы и устройства: облачные инфраструктуры, смартфоны, автомобильные компьютеры и другие устройства, критичные к безопасности встроенного программного обеспечения [2]. Однако доказать корректность работы ОС Linux современными формальными методами практически нереально — если в самом микроядре насчитывается лишь 10 тыс. строк кода, то для ядра Linux пришлось бы верифицировать более 20 млн строк. Более того, опыт показал, что Linux всегда содержит исправимые ошибки, так что формальное доказательство корректности в любом случае даст отрицательный результат.

Статический анализ репозиториев открытого кода обычно выявляет в среднем 0,61 дефекта на тысячу строк, и, несмотря на постоянно проводимые исправления, число неустраненных ошибок в ядре остается на уровне примерно 5 тыс. [3]; правда, не все они могут использоваться для проведения атак. Другое исследование показало, что за последние пять лет в Linux было исправлено около 500 ошибок, связанных с нарушением безопасности, и они присутствовали в ядре в течение пяти лет. В проприетарном коде плотность дефектов несколько выше, чем в открытых проектах, а значит, коммерческое ПО тоже не застраховано от наличия уязвимостей.

Защита и безопасность приложений

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

Если при создании драйверов разработчики полагаются на надежность операционной системы, то при написании критически важных приложений они доверяют только подконтрольным им  компонентам.  При выполнении ответственного приложения в облаке системные сервисы и ОС могут находиться под административным контролем третьей стороны. Современные расширения набора инструкций процессора, в частности Intel Software Guard Extensions (SGX), позволяют приложениям сохранять состояния в защищенных областях памяти — «анклавах», доступа к которым нет даже у самого привилегированного кода, включая ОС и гипервизор. Однако SGX более чем на порядок замедляет выполнение своих команд, если рабочий срез данных анклава не помещается в расширенный кэш страниц SGX, размер которого составляет сейчас лишь около 90 Мбайт. Поэтому данные состояния для анклава нужно ограничивать в объеме — не только для минимизации базы доверенных вычислений (совокупности элементов, влияющих на защищенность системы), но и для того, чтобы сохранить приемлемую производительность. Для выполнения этих требований была реализована и проверена архитектура на основе микросервисов (рис. 1).

Рис. 1. Обеспечение компактности состояния, хранимого в анклаве, с использованием микросервисов. Такой подход позволяет свести к минимуму доверенную вычислительную базу и поддерживать достаточное быстродействие
Рис. 1. Обеспечение компактности состояния, хранимого в анклаве, с использованием микросервисов. Такой подход позволяет свести к минимуму доверенную вычислительную базу и поддерживать достаточное быстродействие

 

Приложения на основе микросервисов

Архитектура микроядра, пожалуй, более всего подходит для создания систем повышенной надежности. Тем не менее такие системы нередко создаются на базе монолитных ядер наподобие Linux, поэтому при создании надежных систем следует опираться на использование унаследованных программных и аппаратных компонентов, которым можно доверять до известной степени, — так проще обеспечить целостность, конфиденциальность и корректное выполнение кода.

В этом случае можно разбить приложения на серию микросервисов, отвечающих за определенную функцию. Сами микросервисы слабо связаны и поддерживают горизонтальное эластичное масштабирование: сбои и деградация быстродействия устраняются путем запуска новых экземпляров сервиса вместо аварийных или замедлившихся. Микросервисы хорошо сочетаются с современными фреймворками управления контейнерами, такими как Kubernetes и Docker Swarm, имеющими средства обеспечения высокой доступности, балансировки нагрузки и масштабирования приложений. Пока еще нет версий для встроенных систем и автомобильных компьютеров, но в ближайшее время они должны появиться.

Разберем теперь, как обеспечить целостность, конфиденциальность и корректность выполнения микросервисов при помощи программных средств и расширений команд процессора.

Обеспечение корректности

Если приложение требует достаточно высокой надежности (скажем, допускается максимум один системный сбой за 10**9 часов эксплуатации), то на корректную работу центрального процессора уже рассчитывать нельзя. Несмотря на то что современные процессоры распознают и маскируют большинство нерегулярных ошибок, для критически важных приложений их уровень распознавания еще недостаточен. Поскольку высокопроизводительные процессоры отличаются большой степенью недетерминированности, традиционный жесткий параллелизм здесь нереализуем — нужны программные средства синхронизации. Преимущество такой программной защиты в том, что ее можно комбинировать с механизмом быстрого восстановления на основе транзакционной памяти процессоров. Как показал  опыт,  этот способ позволяет распознавать и маскировать большинство нерегулярных ошибок. В данном случае накладные затраты ресурсов примерно такие же, как при аппаратной синхронизации, но при этом маскируется около 90% нерегулярных ошибок и поддерживаются недетерминированные процессоры и приложения.

Для некоторых критически важных приложений необходимо распознавать еще и конструктивные дефекты процессора, используя программную синхронизацию (каждая команда выполняется дважды) в сочетании с арифметическим кодированием. Данный способ позволяет с высокой надежностью распознавать ошибки процессора, а накладные расходы при этом не превышают 150%. Примерно такой же результат дает аппаратно реализованный жесткий параллелизм с учетом того, что синхронизируемые ядра приходится замедлять, чтобы синхронизация сохранялась после коррекции ошибки одного из ядер.

Целостность и конфиденциальность

Для обеспечения конфиденциальности и целостности микросервисов требуется защищенный контейнер. С точки зрения контейнерного движка (в данном случае имеется в  виду  Docker без дополнений) защищенный контейнер не отличается от обычного. В каждом защищенном контейнере в анклавах SGX выполняется один экземпляр микросервиса (рис. 2). Регистры процессора, основная память, файлы и сетевые коммуникации прозрачно шифруются, благодаря чему обеспечиваются конфиденциальность и целостность, в том числе защита от атак со стороны более привилегированного ПО, например гипервизора и ОС. При этом быстродействие близко к скорости работы системы без виртуализации при условии, что анклавы умещаются в EPC.

Рис. 2. Каждый защищенный контейнер выполняется внутри отдельного экземпляра микросервиса в анклаве SGX, благодаря чему соблюдаются конфиденциальность и целостность и исключаются атаки со стороны программного обеспечения с более высокими привилегиями (ОС или гипервизора)
Рис. 2. Каждый защищенный контейнер выполняется внутри отдельного экземпляра микросервиса в анклаве SGX, благодаря чему соблюдаются конфиденциальность и целостность и исключаются атаки со стороны программного обеспечения с более высокими привилегиями (ОС или гипервизора)

 

Микросервисы упрощают защиту от атак, совершаемых через интерфейс системных вызовов (например, атак уровня ядра, основанных на манипуляциях с результатами вызовов). Для улучшения масштабируемости и снижения числа зависимостей микросервисы могут частично замещать сервисы ОС (например, сервис файловой системы). Многие микросервисы пользуются лишь ограниченным подмножеством системных вызовов, которые можно прозрачно защитить с помощью контейнерной инфраструктуры.

Приложения можно создавать на основе уже имеющихся микросервисов или путем разработки новых. Например, в приложении можно задействовать сервисы memcached или Redis. Многие существующие сервисы написаны на небезопасных языках, к каковым можно отнести Си и C++. Поэтому,  как  и ядро ОС, такие сервисы нужно защищать от совершаемых через сетевые API атак, таких как переполнение буфера или использование форматирующих строк. В решении задачи обеспечения безопасности поможет механизм для компиляторов, реализующий дополнительную защиту существующего кода. Поскольку критически важные приложения должны быть высокодоступными, созданный механизм терпим к сбоям, например к ошибкам доступа, но останавливает сервис только в том случае, если продолжение его работы создает угрозу безопасности.

***

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

Литература

  1. G. Klein, G. Heiser, T. Murray. It’s Time for Trustworthy Systems. IEEE Security & Privacy. — 2012. — Vol. 10, № 2. — P. 67–70.
  2. L. Bulwahn, T. Ochs, D. Wagner. Research on an Open-Source Software Platform for Autonomous Driving Systems, tech. report. BMW Car IT GmbH. Oct. 2013. URL: http://www.bmw-carit.de/downloads/publications/ResearchOnAnOpenSourceSoftwarePlatformForAutonomousDrivingSystems.pdf. (дата обращения: 01.03.2017). 
  3. Coverity Scan Open Source Report 2014, Coverity Scan. 2015. URL: http://go.coverity.com/rs/157-LQW-289/images/2014-Coverity-Scan-Report.pdf. (дата обращения: 01.03.2017).

Кристоф Фетцер (chrostof.fetzer@tu-dresden.de) — Дрезденский технический университет (ФРГ).

Christof Fetzer, Building Critical Applications Using Microservices. IEEE Security & Privacy, November/December 2016, IEEE Computer Society. All rights reserved. Reprinted with permission.