Quando a CPU está no modo de usuário, ela não pode executar instruções privilegiadas e não pode acessar a memória do espaço do kernel.
E quando a CPU está no modo kernel, ela pode executar todas as instruções e acessar toda a memória.
Agora no Linux, um programa em modo de usuário pode acessar toda a memória (usando /dev/mem
) e pode executar as duas instruções privilegiadas IN
e OUT
(usando iopl()
eu acho).
Portanto, um programa no modo usuário no Linux pode fazer a maioria das coisas (eu acho a maioria das coisas) que podem ser feitas no modo kernel.
Não permitir que um programa no modo de usuário tenha toda essa energia anula o propósito de ter modos de CPU?
iopl
não permite todas as instruções privilegiadas, por isso ainda é útil para garantir que um programa de espaço de usuário com erros não seja executado acidentalmenteinvd
saltando por um ponteiro de função corrompido que aponta para a memória executável iniciada por0F 08
bytes. Adicionei uma resposta com alguns dos motivos que não são de segurança, pelos quais é útil fazer com que os processos no espaço do usuário elevem seus privilégios.Somente da mesma maneira que
modprobe
"derrota" a segurança carregando um novo código no kernel.Por várias razões, às vezes faz mais sentido ter código semi-privilegiado (como drivers gráficos dentro do servidor X) executando no espaço do usuário em vez de um thread do kernel.
kill
fazê - lo mais facilmente, a menos que bloqueie o HW.Não faz muito por segurança, mas há grandes vantagens em termos de confiabilidade e arquitetura de software.
A instalação de drivers gráficos no kernel pode reduzir as alternâncias de contexto entre os clientes X e o servidor X, como apenas um usuário-> kernel-> usuário, em vez de ter que colocar dados em outro processo de espaço de uso, mas os servidores X historicamente são muito grandes e com erros demais querer que eles estejam totalmente no kernel.
Sim, o código malicioso com esses privs pode assumir o controle do kernel, se ele quiser, usando
/dev/mem
para modificar o código do kernel.Ou no x86, por exemplo, execute uma
cli
instrução para desativar as interrupções nesse núcleo depois de fazer umaiopl
chamada do sistema para definir seu nível de privilégio de E / S para tocar 0.Mas mesmo o x86
iopl
"only" dá acesso a algumas instruções : in / out (e as versões das strings ins / outs) e cli / sti. Ele não permite que você userdmsr
ouwrmsr
leia ou escreva "registros específicos do modelo" (por exemplo,IA32_LSTAR
que define o endereço do ponto de entrada do kernel para asyscall
instrução x86-64 ) oulidt
substitua a tabela de descritores de interrupção (o que permitiria sobre a máquina a partir do kernel existente, pelo menos nesse núcleo.)Você não pode nem ler registros de controle (como o CR3, que contém o endereço físico do diretório de páginas de nível superior, que um processo de ataque pode achar útil como deslocamento
/dev/mem
para modificar suas próprias tabelas de páginas como uma alternativammap
a mais/dev/mem
. )invd
(invalidar todos os caches sem write-back !! ( caso de uso = BIOS anterior antes da RAM ser configurada)) é outro divertido que sempre exige CPL 0 total (nível de privilégio atual), não apenas IOPL. Evenwbinvd
é privilegiado porque é muito lento (e não pode ser interrompido) e precisa liberar todos os caches em todos os núcleos. (Consulte Existe uma maneira de liberar todo o cache da CPU relacionado a um programa? E uso de instruções WBINVD )Os erros que resultam em um salto para um endereço incorreto executando dados como código, portanto, não podem executar nenhuma dessas instruções por acidente em um servidor X no espaço do usuário.
O nível de privilégio atual (no modo protegido e longo) são os 2 bits baixos de
cs
(o seletor de segmento de código) .mov eax, cs
/and eax, 3
trabalha em qualquer modo para ler o nível de privilégio.Para escrever o nível de privilégio, você deve definir
jmp far
oucall far
definirCS:RIP
(mas a entrada GDT / LDT para o segmento de destino pode restringi-lo com base no antigo nível de privilégio, e é por isso que o espaço do usuário não pode fazer isso para se elevar). Ou você pode usarint
ousyscall
para alternar para tocar 0 no ponto de entrada do kernel.fonte