11. Режим Безопасных Вычислений¶
В этом дополнении будет кратко описан особый режим компиляции и исполнения программ на C/C++, называемый Режимом Безопасных Вычислений (РБВ).
11.1. Назначение и общий принцип работы РБВ¶
Идея использования РБВ заключается во введении дополнительного контроля поведения программ на С/С++, отсутствующего в обычных реализациях этих языков. Контроль опирается на специализированные аппаратные функции платформы Эльбрус. Его ключевые особенности:
контролируются обращения за границами объектов,
контролируется использование неинициализированных переменных,
контролируется создание новых указателей и конверсия указателей.
Дополнительный контроль позволяет обнаруживать сложные ошибки исполнения программ, связанные с переполнением буфера, использованием случайных данных. Более того, в РБВ становится невозможной эксплуатация многих программных уязвимостей, которые не были обнаружены до начала эксплуатации.
Для работы программы в РБВ вводятся дополнительные требования к её исходным текстам. Они более жёсткие, чем общие стандарты языков C/C++.
РБВ основан на аппаратной поддержке контроля ссылок/указателей, типов данных и контекста исполнения, с точностью до модуля. Аппаратный контроль (технология безопасных вычислений, ТБВ) является более глубоким и общим свойством архитектуры Эльбрус, и может быть задействован в реализации разных языков программирования. На данный момент времени он используется только для языков C/C++, и в руководстве мы ограничимся его описанием в данной части.
11.2. Устройство указателя в РБВ¶
В режиме РБВ указатели расширяются до дескрипторов. В отличие от обычного указателя C/C++, который совместим с целочисленным типом, дескриптор РБВ содержит в себе дополнительную информацию:
указатель на начало выделенной области памяти/объекта (64 бита);
размер выделенной памяти/объекта (32 бита);
смещение относительно начала (32 бита).
Дескриптор в ТБВ имеет размер 128 бит, выровнен по размеру и подтверждён аппаратными тэгами. В рамках ТБВ его можно получить ограниченным числом способов:
получить от ядра операционной системы с помощью системного вызова (malloc);
получить указатель на процедурный фрейм пользовательского стека;
сконверировать из другого указателя, с уменьшением области памяти (переход от объекта к подобъекту);
скопировать из указателя на область глобалов модуля.
11.3. Тэги¶
Аппаратные тэги - добавочные биты информации, которые хранятся отдельно от основных данных и используются для разметки дополнительных свойств ТБВ. Для размещения тэгов «рядом» с данными в оперативной памяти используется часть битов ECC.
Тэги позволяют сохранять в процессе исполнения информацию о типах данных и в дальнейшем контролировать их аппаратно. Для РБВ используются типы:
числовые данные;
указатель (дескриптор);
неинизиализированные данные.
Наличие тэгов учитывается семантикой всех операций из системы команд, но в руководстве мы ограничимся лишь самыми общими правилами:
целостность дескриптора сохраняется лишь в том случае, когда дескриптор записывается целиком (атомарно), в пару соседних регистров (квадрорегистр) либо в выровненные 16 байт памяти;
операции чтения и записи проверяют целостность тэгов аргумента-дескриптора, и вырабатывают исключение/диагностиику, если целостность тэга нарушена.
Таким образом, с помощью тэгов гарантируется одно из главных свойств РБВ:
работоспособный дескриптор нельзя получить из частей разных дескрипторов и/или числовых данных, ни случайно, ни специально.
11.4. Дополнительные ограничения на исходные коды¶
Для компиляции в РБВ программа на C/C++:
не должна содержать преобразование из целого в указатель;
не должна закладываться на sizeof(void*) == sizeof(long);
не должна закладываться на sizeof(void*) == sizeof(double);
не может содержать отдельных объектов размером более 2^32 - это ограничение является временным для текущей реализации.
В процессе исполнения в РБВ программа получит исключительную ситуацию:
при обращении по указателю за пределами, указанными в дескрипторе [base, base+size];
при использовани данных, прочтенных из неинициализированной памяти;
при попытке разыменования некорректного дескриптора (попытка неявного преобразования целого в указатель через память).
Полный список ограничений достаточно велик и выходит за рамки документа. Заметим, что довольно часто нарушения дополнительных ограничений происходит в собственных менеджерах памяти программ.
11.5. Частичные аналоги РБВ¶
Идеи контроля границ объектов были реализованы в отечественной и мировой практике как программными средствами (valgrind, address sanitizer = asan), так и на уровне аппаратуры (cheri). В некоторых ситуациях РБВ работает точнее своих аналогов, обнаруживая нарушения границ на любое смещение, в отличие от, например, asan.
11.6. Пример сборки и исполнения теста¶
#include <stdlib.h>
#define N 1000
int *gp;
int main()
{
gp = (int*)malloc(N*sizeof(int));
gp[N/2] = N/2;
gp[-1] = 14; // ошибка
return 0;
}
$ /opt/mcst/bin/lcc ./t.c -o t
$ ./t
$ /opt/mcst/bin/lcc -mptr128 ./t.c -o t_128
$ ./t_128
Segmentation fault
Как видно из примера, версия теста в РБВ упала на исполнении с ошибкой сегментации, в то время как в обычном режиме тест отработал без ошибки, несмотря на запись за пределами выделенного объекта.
11.7. Ассемблер теста в РБВ¶
Посмотрим на ассемблер функции main теста из предыдущего раздела.
main:
{
nop 1
setwd wsz = 0x13, nfx = 0x1, dbl = 0x0
setbn rsz = 0x7, rbs = 0xb, rcur = 0x0
setbp psz = 0x0
getsap,0 _f32s,_lts1 0xffffffe0, %r18
}
{
nop 1
stapb,2 %r18, 0x0, %r18
}
{
disp %ctpr1, malloc; ipd 2
movtq,0,sm %r18, %b[0]
addd,2 0x0, _f16s,_lts0lo 0xfa0, %b[2]
adds,3 0x0, _f16s,_lts0hi 0x1f4, %r1
adds,4 0xe, 0x0, %r2
}
{
nop 3
aptoapb,0 %r18, _f16s,_lts0lo 0x20, %b[0]
}
{
call %ctpr1, wbs = 0xb
}
{
return %ctpr3; ipd 2
stgdq,2,sm 0x0, [ _f32s,_lts0 gp ], %b[0]
adds,3 0x0, 0x0, %r0
}
{
stapw,5 %b[0], _f16s,_lts0lo 0x7d0, %r1
}
{
nop 2
ldgdq,0 0x0, [ _f32s,_lts0 gp ], %r20
}
{
stapw,2 %r20, _f16s,_lts0lo 0xfffc, %r2
}
{
ct %ctpr3
}
Отличия от обычного режима исполнения:
получение указателя на фрейм стека:
getsap
вместоgetsp
; результатом является 128-битный дескриптор.команда
stapw
вместоstw
: обращение в память по дескриптору. В качестве аргумента должен быть 128-битный регистр, реализованный в качестве пары соседних 64-битных регистров, в котором находится дескриптор.
movtq
копирует 128-битный дескриптор с сохранением тэгов.команда преобразования указателя
aptoapb
: возвращает дескриптор, полученный из дескриптора %r18-%r19 в первом аргументе, до размера 0x20, указанного во втором аргументе.команды
stgd
иldgd
нужны для обращения к глобальным объектам. В качестве дескриптора области глобальных объектов выступает регистр %gd.