Há, no entanto, um ponto sobre o qual gostaria de falar mais detalhadamente.
No artigo anterior, escrevemos que, como parte da implementação do suporte SGX, foi necessário ensinar o serviço Nova a gerar um arquivo XML com as configurações necessárias para o domínio convidado. Este problema se mostrou complexo e interessante: durante o trabalho de sua solução, tivemos que entender em detalhes, usando o exemplo da libvirt, como os programas em geral interagem com conjuntos de instruções em processadores x86. Existem muito poucos materiais detalhados e, o mais importante - claramente escritos sobre este tópico. Esperamos que nossa experiência seja útil para todos os envolvidos na virtualização. No entanto, as primeiras coisas primeiro.
Primeiras tentativas
Vamos repetir a formulação da tarefa mais uma vez: precisamos passar os parâmetros de suporte SGX para o arquivo de configuração XML da máquina virtual. Quando começamos a resolver este problema, não havia suporte SGX no OpenStack e libvirt, respectivamente, era impossível transferi-los para o XML da máquina virtual nativamente.
Tentamos primeiro resolver esse problema adicionando um bloco de linha de comando Qemu ao script para se conectar ao hipervisor via libvirt, conforme descrito no guia do desenvolvedor Intel:
<qemu:commandline>
<qemu:arg value='-cpu'/>
<qemu:arg value='host,+sgx,+sgxlc'/>
<qemu:arg value='-object'/>
<qemu:arg value='memory-backend-epc,id=mem1,size=''' + epc + '''M,prealloc'/>
<qemu:arg value='-sgx-epc'/>
<qemu:arg value='id=epc1,memdev=mem1'/>
</qemu:commandline>
Mas depois disso, uma segunda opção de processador foi adicionada à máquina virtual:
[root@compute-sgx ~] cat /proc/$PID/cmdline |xargs -0 printf "%s\n" |awk '/cpu/ { getline x; print $0 RS x; }'
-cpu
Skylake-Client-IBRS
-cpu
host,+sgx,+sgxlc
A primeira opção foi definida normalmente e a segunda foi adicionada diretamente por nós no bloco de linha de comando do Qemu . Isso causou um inconveniente ao escolher um modelo de emulação de processador: qualquer modelo de processador que substituíssemos em cpu_model no arquivo de configuração do nó de computação Nova, víamos a exibição do processador host na máquina virtual.
Como resolver este problema?
Em busca de uma resposta, primeiro tentamos experimentar a linha < qemu: arg value = 'host, + sgx, + sgxlc'/> e tente transferir o modelo do processador para ele, mas isso não cancelou a duplicação desta opção depois que a VM foi iniciada. Em seguida, decidiu-se usar libvirt para atribuir sinalizadores de CPU e controlá-los por meio do arquivo de configuração Nov'y do nó computacional usando o parâmetro cpu_model_extra_flags .
A tarefa acabou sendo mais difícil do que esperávamos: precisávamos estudar a Intel IA-32 - instrução CPUID, bem como encontrar informações sobre os registros e bits necessários na documentação da Intel sobre SGX.
Pesquisa adicional: aprofundando a libvirt
A documentação do desenvolvedor para o serviço Nova afirma que o mapeamento do sinalizador da CPU deve ser suportado pelo próprio libvirt.
Encontramos um arquivo que descreve todos os sinalizadores de CPU - este é x86_features.xml (relevante desde libvirt 4.7.0). Depois de revisar esse arquivo, presumimos (como mais tarde descobrimos, erroneamente) que só precisamos obter os endereços hexadecimais dos registros necessários na 7ª folha usando o utilitário cpuid. Na documentação da Intel, aprendemos em quais registros as instruções de que precisamos são chamadas: sgx está no registro EBX e sgxlc está no ECX.
[root@compute-sgx ~] cpuid -l 7 -1 |grep SGX
SGX: Software Guard Extensions supported = true
SGX_LC: SGX launch config supported = true
[root@compute-sgx ~] cpuid -l 7 -1 -r
CPU:
0x00000007 0x00: eax=0x00000000 ebx=0x029c6fbf ecx=0x40000000 edx=0xbc000600
Depois de adicionar as sinalizações sgx e sgxlc com os valores obtidos usando o utilitário cpuid, recebemos a seguinte mensagem de erro:
error : x86Compute:1952 : out of memory
A mensagem, para ser franco, não é muito informativa. Para entender de alguma forma qual é o problema, abrimos um problema no gitlab libvirt. Os desenvolvedores do libvirt notaram que um erro incorreto foi exibido e o corrigiram, indicando que o libvirt não conseguiu encontrar a instrução correta que estávamos chamando e sugeriu onde podemos estar errados. Mas para entender o que exatamente precisávamos indicar para que não houvesse erro, não tivemos sucesso.
Tive que pesquisar as fontes e estudar, demorou muito. Foi possível descobrir isso somente depois de estudar o código em um Qemu modificado da Intel:
[FEAT_7_0_EBX] = {
.type = CPUID_FEATURE_WORD,
.feat_names = {
"fsgsbase", "tsc-adjust", "sgx", "bmi1",
"hle", "avx2", NULL, "smep",
"bmi2", "erms", "invpcid", "rtm",
NULL, NULL, "mpx", NULL,
"avx512f", "avx512dq", "rdseed", "adx",
"smap", "avx512ifma", "pcommit", "clflushopt",
"clwb", "intel-pt", "avx512pf", "avx512er",
"avx512cd", "sha-ni", "avx512bw", "avx512vl",
},
.cpuid = {
.eax = 7,
.needs_ecx = true, .ecx = 0,
.reg = R_EBX,
},
.tcg_features = TCG_7_0_EBX_FEATURES,
},
[FEAT_7_0_ECX] = {
.type = CPUID_FEATURE_WORD,
.feat_names = {
NULL, "avx512vbmi", "umip", "pku",
NULL /* ospke */, "waitpkg", "avx512vbmi2", NULL,
"gfni", "vaes", "vpclmulqdq", "avx512vnni",
"avx512bitalg", NULL, "avx512-vpopcntdq", NULL,
"la57", NULL, NULL, NULL,
NULL, NULL, "rdpid", NULL,
NULL, "cldemote", NULL, "movdiri",
"movdir64b", NULL, "sgxlc", NULL,
},
.cpuid = {
.eax = 7,
.needs_ecx = true, .ecx = 0,
.reg = R_ECX,
},
A partir da lista acima, você pode ver que nos blocos .feat_names , as instruções dos registros EBX / ECX da 7ª folha são listadas bit a bit (de 0 a 31); se a instrução não for suportada pelo Qemu ou este bit estiver reservado, ele será preenchido com um valor NULL . Graças a esse exemplo, fizemos a seguinte suposição: talvez precisemos especificar não o endereço hexadecimal do registro necessário em libvirt, mas especificamente o bit desta instrução. É mais fácil entender isso lendo a tabela da Wikipedia . À esquerda está um bit e três registros. Encontramos nossa instrução nele - sgx. Na tabela, está indicado no segundo bit do registro EBX:
A seguir, verificamos a localização desta instrução no código Qemu. Como podemos ver, ela é a terceira na lista de feat_names, mas isso ocorre porque a numeração de bits começa em 0:
[FEAT_7_0_EBX] = {
.type = CPUID_FEATURE_WORD,
.feat_names = {
"fsgsbase", "tsc-adjust", "sgx", "bmi1",
Você pode consultar outras instruções nesta tabela e certificar-se, ao contar a partir de 0, de que estão sob seu próprio bit na lista fornecida. Por exemplo: fsgsbase vai para o bit 0 do registro EBX e é listado primeiro.
Na documentação da Intel, encontramos a confirmação disso e nos certificamos de que o conjunto de instruções necessárias pode ser chamado usando cpuid, passando o bit correto ao acessar o registro da folha desejada e, em alguns casos, a sublista.
Começamos a entender mais detalhadamente a arquitetura dos processadores de 32 bits e vimos que tais processadores possuem folhas que contêm os 4 principais registradores: EAX, EBX, ECX, EDX. Cada um desses registros contém 32 bits reservados para um conjunto específico de instruções da CPU. Um bit é uma potência de dois e, na maioria das vezes, pode ser passado para um programa em formato hexadecimal, como é feito no libvirt.
Para uma melhor compreensão, considere outro exemplo com o sinalizador de virtualização VMX aninhado do arquivo x86_features.xml usado por libvirt:
<feature name = 'vmx ' >
<cpuid eax_in = ' 0x01 ' ecx = ' 0x00000020 '/> # 2 5 = 32 10 = 20 16
</ feature>
A referência a esta instrução é realizada na 1ª folha para o registrador ECX no bit 5 e você pode verificar isso olhando a tabela de informações de recursos na Wikipedia.
Tendo lidado com isso e formado uma compreensão de como os sinalizadores são eventualmente adicionados ao libvirt, decidimos adicionar outros sinalizadores SGX (além dos principais: sgx e sgxlc) que estavam presentes no Qemu modificado:
[root@compute-sgx ~] /usr/libexec/qemu-kvm -cpu help |xargs printf '%s\n' |grep sgx
sgx
sgx-debug
sgx-exinfo
sgx-kss
sgx-mode64
sgx-provisionkey
sgx-tokenkey
sgx1
sgx2
sgxlc
Alguns desses sinalizadores não são mais instruções, mas atributos da Estrutura de Controle de Dados do Enclave (SECS); você pode ler mais sobre isso na documentação da Intel. Nele, descobrimos que o conjunto de atributos SGX de que precisamos está na folha 0x12 na sublista 1:
[root@compute-sgx ~] cpuid -l 0x12 -s 1 -1 CPU: SGX attributes (0x12/1): ECREATE SECS.ATTRIBUTES valid bit mask = 0x000000000000001f0000000000000036
Na captura de tela da Tabela 38-3, você pode encontrar os bits de atributo de que precisamos, que especificaremos posteriormente como sinalizadores em libvirt: sgx-debug, sgx-mode64, sgx-provisionkey, sgx-tokenkey. Eles estão localizados nos bits 1, 2, 4 e 5.
Também entendemos a partir da resposta em nosso problema : libvirt tem uma macro para verificar os flags para seu suporte diretamente pelo processador do nó computacional. Isso significa que não é suficiente especificar as folhas, bits e registros necessários no arquivo x86_features.xml se a própria libvirt não suportar uma folha de conjunto de instruções. Mas, felizmente para nós, descobrimos que o código libvirt tem a capacidade de trabalhar com esta planilha:
/* Leaf 0x12: SGX capability enumeration
*
* Sub leaves 0 and 1 is supported if ebx[2] from leaf 0x7 (SGX) is set.
* Sub leaves n >= 2 are valid as long as eax[3:0] != 0.
*/
static int
cpuidSetLeaf12(virCPUDataPtr data,
virCPUx86DataItemPtr subLeaf0)
{
virCPUx86DataItem item = CPUID(.eax_in = 0x7);
virCPUx86CPUIDPtr cpuid = &item.data.cpuid;
virCPUx86DataItemPtr leaf7;
if (!(leaf7 = virCPUx86DataGet(&data->data.x86, &item)) ||
!(leaf7->data.cpuid.ebx & (1 << 2)))
return 0;
if (virCPUx86DataAdd(data, subLeaf0) < 0)
return -1;
cpuid->eax_in = 0x12;
cpuid->ecx_in = 1;
cpuidCall(cpuid);
if (virCPUx86DataAdd(data, &item) < 0)
return -1;
cpuid->ecx_in = 2;
cpuidCall(cpuid);
while (cpuid->eax & 0xf) {
if (virCPUx86DataAdd(data, &item) < 0)
return -1;
cpuid->ecx_in++;
cpuidCall(cpuid);
}
return 0;
}
A partir desta lista, você pode ver que ao acessar o segundo bit EBX do registrador de 7ª folha (ou seja, a instrução SGX), a libvirt pode usar a folha 0x12 para verificar os atributos disponíveis nas sublistas 0, 1 e 2.
Conclusão
Após a pesquisa feita, descobrimos como adicionar corretamente o arquivo x86_features.xml. Convertemos os bits necessários para o formato hexadecimal - e isto é o que obtivemos:
<!-- SGX features -->
<feature name='sgx'>
<cpuid eax_in='0x07' ecx_in='0x00' ebx='0x00000004'/>
</feature>
<feature name='sgxlc'>
<cpuid eax_in='0x07' ecx_in='0x00' ecx='0x40000000'/>
</feature>
<feature name='sgx1'>
<cpuid eax_in='0x12' ecx_in='0x00' eax='0x00000001'/>
</feature>
<feature name='sgx-debug'>
<cpuid eax_in='0x12' ecx_in='0x01' eax='0x00000002'/>
</feature>
<feature name='sgx-mode64'>
<cpuid eax_in='0x12' ecx_in='0x01' eax='0x00000004'/>
</feature>
<feature name='sgx-provisionkey'>
<cpuid eax_in='0x12' ecx_in='0x01' eax='0x00000010'/>
</feature>
<feature name='sgx-tokenkey'>
<cpuid eax_in='0x12' ecx_in='0x01' eax='0x00000020'/>
</feature>
Agora, para passar esses sinalizadores para a máquina virtual, podemos especificá-los no arquivo de configuração do Nova usando cpu_model_extra_flags :
[root@compute-sgx nova] grep cpu_mode nova.conf
cpu_mode = custom
cpu_model = Skylake-Client-IBRS
cpu_model_extra_flags = sgx,sgxlc,sgx1,sgx-provisionkey,sgx-tokenkey,sgx-debug,sgx-mode64
[root@compute-sgx ~] cat /proc/$PID/cmdline |xargs -0 printf "%s\n" |awk '/cpu/ { getline x; print $0 RS x; }'
-cpu
Skylake-Client-IBRS,sgx=on,sgx-mode64=on,sgx-provisionkey=on,sgx-tokenkey=on,sgx1=on,sgxlc=on
Tendo ido da maneira mais difícil, aprendemos como adicionar suporte para sinalizadores SGX ao libvirt. Isso nos ajudou a resolver o problema de duplicação das opções do processador no arquivo XML da máquina virtual. Usaremos a experiência adquirida em nosso trabalho futuro: se um novo conjunto de instruções aparecer nos processadores Intel ou AMD, podemos adicioná-los ao libvirt da mesma maneira. A familiaridade com a instrução CPUID também será útil para nós ao escrevermos nossas próprias soluções.
Se você tiver alguma dúvida - bem-vindo aos comentários, tentaremos responder. E se você tem algo a acrescentar - ainda mais, escreva, ficaremos muito gratos.