Não permitir que um programa no modo usuário acesse a memória do espaço do kernel e execute as instruções IN e OUT anula o propósito de ter modos da CPU?

19

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 INe 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?

user341099
fonte

Respostas:

23

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.

Bem, nem todos os programas em modo de usuário podem, apenas aqueles com os privilégios apropriados. E isso é determinado pelo kernel.

/dev/memé protegido pelas permissões usuais de acesso ao sistema de arquivos e pelo CAP_SYS_RAWIOrecurso. iopl()e ioperm()também são restritos pela mesma capacidade.

/dev/memtambém pode ser compilado completamente do kernel ( CONFIG_DEVMEM).

Não permitir que um programa no modo de usuário tenha toda essa energia anula o propósito de ter modos de CPU?

Bem, talvez. Depende do que você deseja que processos privilegiados de espaço do usuário possam fazer. Os processos de espaço do usuário também podem descartar todo o disco rígido se eles tiverem acesso /dev/sda(ou equivalente), mesmo que isso anule o propósito de ter um driver de sistema de arquivos para lidar com o acesso ao armazenamento.

(Há também o fato de que iopl()funciona utilizando os modos de privilégio da CPU no i386, portanto, não se pode dizer que isso derrota seu objetivo.)

ilkkachu
fonte
2
Mesmo ioplnã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 acidentalmente invdsaltando por um ponteiro de função corrompido que aponta para a memória executável iniciada por 0F 08bytes. 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.
Peter Cordes
16

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.

  • Ser capaz de killfazê - lo mais facilmente, a menos que bloqueie o HW.
  • Para que ele exija a página de código / dados dos arquivos no sistema de arquivos. (A memória do kernel não é paginável)
  • Dando a ele seu próprio espaço de endereço virtual, onde os erros no servidor X podem travar o servidor X, sem derrubar o kernel.

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/mempara modificar o código do kernel.

Ou no x86, por exemplo, execute uma cliinstrução para desativar as interrupções nesse núcleo depois de fazer uma ioplchamada 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ê use rdmsrou wrmsrleia ou escreva "registros específicos do modelo" (por exemplo, IA32_LSTARque define o endereço do ponto de entrada do kernel para a syscallinstrução x86-64 ) ou lidtsubstitua 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/mempara modificar suas próprias tabelas de páginas como uma alternativa mmapa 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. Even wbinvdé 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, 3trabalha em qualquer modo para ler o nível de privilégio.

Para escrever o nível de privilégio, você deve definir jmp farou call fardefinir CS: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 usar intou syscallpara alternar para tocar 0 no ponto de entrada do kernel.

Peter Cordes
fonte
Na verdade, tenho certeza de que é apenas o "seletor de código" na Intel parlace. Era um segmento no 8086/8088, possivelmente no 80186, mas no 80286 era chamado de seletor, e acho que eles não mudaram oficialmente essa terminologia desde então.
um CVn