Расширения Intel SGX, учебное руководство. Часть 7, доработка анклава

https://software.intel.com/en-us/articles/intel-software-guard-extensions-tutorial-part-7-enclave-development
  • Перевод
В седьмой части серии учебных материалов, посвященных расширениям Intel Software Guard Extensions (Intel SGX), мы вернемся к работе с анклавом и немного доработаем его, чтобы он стал проще и эффективнее. Мы рассмотрим, как прокси-функции передают данные между незащищенной областью памяти и анклавом, а также поговорим об одной из расширенных возможностей синтаксиса языка Enclave Definition Language (EDL).



Вместе с этой частью серии предоставляется исходный код. В этой части мы перенесли приложение на Intel SGX SDK версии 1.7, а также в качестве среды разработки используем Microsoft Visual Studio* Professional 2015.

Прокси-функции


При создании анклава с помощью Intel SGX SDK интерфейс к анклаву задается на языке EDL. EDL указывает, какие функции являются вызовами анклава (ECALL, функции, входящие в анклав), а какие являются внешними вызовами (OCALL, вызовы, направленные изнутри анклава к недоверенным функциям).

При сборке проекта инструмент Edger8r, входящий в состав пакета Intel SGX SDK, анализирует EDL-файл и создает последовательность прокси-функций. Эти прокси-функции представляют собой оболочки вокруг настоящих функций, заданных на языке EDL. Каждый вызов ECALL и OCALL получает пару прокси-функций: доверенную половину и недоверенную половину. Доверенные функции помещаются в EnclaveProject_t.h и EnclaveProjct_t.c и добавляются в папку Autogenerated Files проекта анклава. Недоверенные функции помещаются в EnclaveProject_u.h и EnclaveProject_u.c и добавляются в папку Autogenerated Files проекта, который будет взаимодействовать с анклавом.

Ваша программа не вызывает функции ECALL и OCALL напрямую, она вызывает прокси-функции. Если нужно вызвать ECALL, следует вызвать недоверенную прокси-функцию для этого ECALL, которая, в свою очередь, вызывает доверенную прокси-функцию внутри анклава. Затем эта прокси-функция вызывает «настоящую» функцию ECALL, а возвращенное значение передается недоверенной функции. Эта последовательность изображена на рис. 1. Если нужно вызвать OCALL, используется обратная последовательность: следует вызвать доверенную прокси-функцию для OCALL, которая вызывает недоверенную прокси-функцию снаружи анклава, которая, с свою очередь, вызывает «настоящую» функцию OCALL.


Рисунок 1. Прокси-функции для ECALL

Прокси-функции отвечают за выполнение следующих действий.
  • Передача данных в анклав и из анклава
  • Помещение возвращаемого значения настоящей функции ECALL или OCALL в адрес, на который ссылается параметр указателя
  • Возврат состояния успеха или сбоя самой функции ECALL или OCALL в виде значения sgx_status_t

Из этого следует, что у каждой ECALL или OCALL может быть два возвращаемых значения. Первое — успех вызова самой функции ECALL или OCALL (т. е. нам удалось успешно войти в анклав или выйти из него), второе — возвращаемое значение функции, которая вызывается внутри ECALL или OCALL.
Синтаксис функций ECALL ve_lock() и ve_unlock() на языке EDL в анклаве нашего приложения Tutorial Password Manager показан ниже:

enclave {
   trusted {
      public void ve_lock ();
      public int ve_unlock ([in, string] char *password);
    }
}

А вот прототипы недоверенных прокси-функций, созданные инструментом Edger8r.

sgx_status_t ve_lock(sgx_enclave_id_t eid);
sgx_status_t ve_unlock(sgx_enclave_id_t eid, int* retval, char* password);

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

Обеим прокси-функциям требуется идентификатор анклава, который передается с первым параметром eid. Функция ve_lock() не имеет параметров и не возвращает значения, поэтому не требует дальнейших изменений. Функция ve_unlock(), напротив, использует параметры и возвращает значение. Второй аргумент прокси-функции — это указатель на адрес, в котором будет храниться значение, возвращенное настоящей функцией ve_unlock() в анклаве. В нашем случае возвращается значение типа int. Затем следует параметр настоящей функции char *password.

Передача данных


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

Для решения этой проблемы в Intel SGX SDK предлагается копировать содержимое буферов данных в анклавы и из них, а функции ECALL и OCALL должны работать с этими копиями исходных буферов памяти. При передаче указателя в анклав необходимо указать на языке EDL, в каком направлении передается буфер, на который ссылается указатель: в вызов, из вызова или в обоих направлениях; затем указывается размер буфера. Прокси-функции, созданные в программе Edger8r, используют эту информацию, чтобы проверить, что указанный диапазон адресов не пересекается с границей анклава; скопировать данные в анклав или из анклава согласно указанному направлению, а затем подменить исходный указатель указателем на копию буфера.

Это медленный, но безопасный подход к передаче данных и указателей между незащищенной памятью и памятью анклава. Впрочем, у этого подхода есть определенные недостатки, из-за которых он в некоторых случаях становится нежелательным.
  • Низкая скорость, поскольку проверяется и копируется каждый буфер памяти.
  • Для хранения копий буферов данных требуется дополнительное пространство кучи в анклаве.
  • Синтаксис EDL слишком сложен.

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

Решение: user_check


Хорошая новость состоит в том, что язык EDL поддерживает передачу адреса указателя в ECALL или в OCALL без проверки границ и без копирования буфера данных. Если указать параметр user_check, программа Edger8r передает указатель без каких-либо дополнительных действий, исходя из того что разработчик сам позаботился о проверке границ адреса. Указывая user_check, вы, по сути, повышаете производительность за счет некоторого снижения безопасности.

У указателя с параметром user_check нет направления (in или out), поскольку не происходит копирование буфера. Если указать одновременно user_check и in или out, при компиляции возникнет ошибка. Также не указываются параметры count и size.

В приложении Tutorial Password Manager параметр user_check лучше всего использовать в функциях ECALL, загружающих и сохраняющих зашифрованное хранилище паролей. Наши ограничения проекта устанавливают предел самого хранилища, но в целом такие операции массового чтения и записи существенно выигрывают в скорости, если дать возможность анклаву работать напрямую с недоверенной памятью.

Исходный синтаксис EDL функций ve_load_vault() и ve_get_vault() выглядит так.

public int ve_load_vault ([in, count=len] unsigned char *edata, uint32_t len);
 
public int ve_get_vault ([out, count=len] unsigned char *edata, uint32_t len);

Если переписать их, указав user_check, получится следующее.

public int ve_load_vault ([user_check] unsigned char *edata);
 
public int ve_get_vault ([user_check] unsigned char *edata, uint32_t len);

Обратите внимание, что параметр len удален из ve_load_vault(). Если вспомнить четвертую часть этой серии статей, с этой функцией была проблема: несмотря на то что длина хранилища хранилась в виде переменной в анклаве, у прокси-функций не было доступа к ней. Чтобы прокси-функции ECALL могли копировать входящий буфер данных, нам нужно было указывать длину в EDL, чтобы программа Edger8r получила информацию о размере буфера. Если же использовать параметр user_check, то эта проблема исчезает, поскольку отсутствует операция копирования буфера. Анклав может читать данные непосредственно из недоверенной памяти и может использовать собственную внутреннюю переменную, чтобы определить, сколько байт необходимо прочесть.

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

Подведем итоги


В языке EDL предусмотрено три варианта передачи указателей в ECALL или OCALL: in, out и user_check. Они описаны в таблице 1.
Спецификатор/направление ECALL OCALL
in Буфер копируется из приложения в анклав. Изменения повлияют только на буфер внутри анклава. Буфер копируется из анклава в приложение. Изменения повлияют только на буфер вне анклава.
out Буфер будет выделен внутри анклава и инициализирован с нулевыми значениями. Он будет скопирован в исходный буфер при выходе ECALL. Буфер будет выделен вне анклава и инициализирован с нулевыми значениями. Этот недоверенный буфер будет скопирован в исходный буфер при выходе OCALL.
in, out Данные копируются туда и обратно. Данные копируются туда и обратно.
user_check Указатель не проверяется. Передается необработанный адрес. Указатель не проверяется. Передается необработанный адрес.
Таблица 1. Спецификаторы указателей и их значения в ECALL и OCALL.

Если использовать указатели направления, то буфер данных, на который ссылается указатель, будет скопирован, и необходимо также указать размер, чтобы программа Edger8r смогла определить, сколько байт находится в буфере. Если указать user_check, то в ECALL или в OCALL передается только необработанный указатель без каких-либо изменений.

Пример кода


Пример кода для этой статьи был обновлен: он рассчитан на сборку с Intel SGX SDK версии 1.7 с помощью Microsoft Visual Studio 2015. Этот код должен работать и с Intel SGX SDK версии 1.6 и Visual Studio 2013, но мы рекомендуем обновить Intel SGX SDK до последней версии.

В дальнейших выпусках


В восьмой части этой серии мы добавим поддержку событий электропитания. Следите за новостями!
Intel
177,00
Компания
Поделиться публикацией

Комментарии 0

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое