Em resumo: privilégios (recursos) do Linux

Uma tradução do artigo foi preparada especificamente para os alunos do curso Linux Administrator .


Os recursos estão sendo usados ​​cada vez mais graças em grande parte ao SystemD, Docker e orquestradores, como o Kubernetes. Mas, parece-me, a documentação é um pouco complicada de entender e algumas partes da implementação de privilégios para mim acabaram sendo um pouco confusas, então decidi compartilhar meu conhecimento atual neste breve artigo.



O link do privilégio mais importante é a página do manual features (7) . Mas ela não é muito adequada para um conhecido inicial.

Capacidades de processo


Os direitos dos usuários comuns são muito limitados, enquanto os direitos do usuário root são muito extensos. Embora os processos executados como "raiz" geralmente não exijam todos os privilégios de raiz.

Para reduzir os privilégios de root, as permissões POSIX fornecem uma maneira de limitar os grupos de operações privilegiadas do sistema que um processo e seus descendentes têm permissão para executar. Em essência, eles dividem todos os direitos "raiz" em um conjunto de privilégios separados. A idéia de recursos foi descrita em 1997 em um rascunho do POSIX 1003.1e.

No Linux, cada processo (tarefa) possui cinco números de 64 bits (conjuntos) contendo bits de permissão (antes do Linux 2.6.25 eram 32 bits), que podem ser visualizados em
  / proc / <pid> / status 
.

CapInh: 00000000000004c0 CapPrm: 00000000000004c0 CapEff: 00000000000004c0 CapBnd: 00000000000004c0 CapAmb: 0000000000000000 

Esses números (mostrados aqui na notação hexadecimal) são bitmaps nos quais os conjuntos de permissões são representados. Aqui estão os nomes completos:

  • Herdável - Permissões que os descendentes podem herdar
  • Permitido - Permissões que podem ser usadas pela tarefa.
  • Efetivo - permissões efetivas atuais
  • Limite - Antes do Linux 2.6.25, o conjunto delimitador era um atributo de todo o sistema comum a todos os encadeamentos, projetado para descrever um conjunto além do qual as permissões não podiam ser expandidas. Atualmente, este é um conjunto para cada tarefa e é apenas parte da lógica executada, detalhes abaixo.
  • Ambiente (externo desde o Linux 4.3) - adicionado para facilitar o fornecimento de permissões não raiz ao usuário, sem o uso de permissões setuid ou de arquivo (mais sobre isso posteriormente).

Se a tarefa solicitar uma operação privilegiada (por exemplo, ligação a portas <1024), o kernel verificará o conjunto de limites atual para CAP_NET_BIND_SERVICE . Se estiver instalado, a operação continuará. Caso contrário, a operação será rejeitada com EPERM (operação não permitida). Esses CAP_ no código-fonte do kernel e numerados sequencialmente, portanto CAP_NET_BIND_SERVICE , igual a 10, significa bit 1 << 10 = 0x400 (esse é o dígito hexadecimal “4” no meu exemplo anterior).

Uma lista completa de privilégios legíveis por humanos atualmente definidos pode ser encontrada na página de manual recursos atuais (7) (a lista aqui é apenas para referência).

Além disso, existe uma biblioteca libcap para simplificar as verificações de gerenciamento e autorização. Além da API da biblioteca , o pacote inclui o utilitário capsh , que, entre outras coisas, permite mostrar suas credenciais.

 # capsh --print Current: = cap_setgid,cap_setuid,cap_net_bind_service+eip Bounding set = cap_setgid,cap_setuid,cap_net_bind_service Ambient set = Securebits: 00/0x0/1'b0 secure-noroot: no (unlocked) secure-no-suid-fixup: no (unlocked) secure-keep-caps: no (unlocked) secure-no-ambient-raise: no (unlocked) uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video) 

Existem alguns pontos confusos aqui:

  • Atual - exibe os privilégios efetivos, herdados e disponíveis do processo capsh no formato cap_to_text (3) . Nesse formato, os direitos são listados como grupos de permissão “capability[,capability…]+(e|i|p)” , onde “e” significa efetivo, “i” herdado e “p” disponíveis. A lista não é separada pelo símbolo “,” , como você deve ter adivinhado (cap_setgid+eip, cap_setuid+eip) . Uma vírgula divide as permissões em um grupo de ação. A lista real de grupos de ação é então separada por espaços. Outro exemplo com dois grupos de ação seria “= cap_sys_chroot+ep cap_net_bind_service+eip” . E também os dois grupos de ações a seguir “= cap_net_bind_service+e cap_net_bind_service+ip” codificarão o mesmo valor que um “cap_net_bind_service+eip” .
  • Conjunto delimitador / conjunto ambiente . Para confundir ainda mais, essas duas linhas contêm apenas uma lista de permissões definidas nesses conjuntos, separadas por espaços. O formato cap_to_text não é usado aqui, porque não contém conjuntos de permissões disponíveis, efetivas e herdadas, mas apenas um conjunto (delimitador / ambiente).
  • Securebits : exibe os sinalizadores securebits da tarefa no formato decimal / hexadecimal / Verilog (sim, todos esperam isso aqui, e isso é perfeitamente claro a partir do ponto em que cada administrador do sistema programa seus próprios FPGA e ASIC ). A seguir está o estado dos securebits. Os sinalizadores reais são definidos como SECBIT_* em securebits.h e também são descritos nos recursos (7) .
  • Este utilitário carece da exibição das informações “NoNewPrivs” , que podem ser visualizadas em
      / proc / <pid> / status 
    . É mencionado apenas no prctl (2), embora afete diretamente os direitos quando usado em conjunto com as permissões de arquivo (em mais detalhes abaixo). NoNewPrivs é descrito da seguinte maneira: “Com no_new_privs como 1, o execve (2) promete não conceder privilégios ao que não poderia ter sido feito sem chamar o execve (2) (por exemplo, processando os bits de set-user-ID set-group-ID e desativando o processamento de permissões de arquivo) . Após a instalação, o atributo no_new_privs não pode ser redefinido. O valor desse atributo é herdado pelos descendentes criados pelo fork (2) e pelo clone (2) e armazenados pelo execve (2). ” O Kubernetes define esse sinalizador como 1 quando allowPrivilegeEscalation é falso no pod securityContext.


Ao iniciar um novo processo por meio do execve (2), as permissões para o processo filho são convertidas usando a fórmula especificada nos recursos (7) :

 P'(ambient) = (file is privileged) ? 0 : P(ambient) P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding)) | P'(ambient) P'(effective) = F(effective) ? P'(permitted) : P'(ambient) P'(inheritable) = P(inheritable) [ie, unchanged] P'(bounding) = P(bounding) [ie, unchanged] where: P() denotes the value of a thread capability set before the execve(2) -      execve(2) P'() denotes the value of a thread capability set after the execve(2) -      execve(2) F() denotes a file capability set -   


Essas regras descrevem as ações executadas para cada bit em todos os conjuntos de permissões (ambiente / permitido / efetivo / herdável / limitado). A sintaxe C padrão é usada (& - para AND lógico, | - para OR lógico). P 'é um processo filho. P é o processo atual que chama o execve (2). F são as chamadas "permissões de arquivo" de um arquivo lançado através do execve.

Além disso, um processo pode alterar programaticamente seus conjuntos herdados, acessíveis e eficientes com libcap a qualquer momento, de acordo com as seguintes regras:

  • Se o chamador não tiver CAP_SETPCAP , o novo conjunto herdado deverá ser um subconjunto de P (herdado) e P (disponível)
  • (com Linux 2.6.25) O novo conjunto herdado deve ser um subconjunto de P (herdado) e P (limitador)
  • O novo conjunto disponível deve ser um subconjunto de P (disponível)
  • O novo conjunto eficiente deve ser um subconjunto de P (efetivo)


Permissões de arquivo


Às vezes, um usuário com um conjunto limitado de direitos precisa executar um arquivo que requer mais privilégios. Isso foi conseguido anteriormente, definindo o bit setuid ( chmod + s ./executable ) em um arquivo binário. Esse arquivo, se pertencer ao root, terá direitos de root completos quando executado por qualquer usuário.

Mas esse mecanismo concede muitos privilégios a um arquivo, portanto, as permissões POSIX implementaram um conceito chamado "permissões de arquivo". Eles são armazenados como um atributo de arquivo estendido chamado “security.capability”, portanto, você precisa de um sistema de arquivos com suporte para atributos estendidos (ext *, XFS, Raiserfs, Brtfs, overlay2, ...). Para alterar esse atributo, é CAP_SETFCAP permissão CAP_SETFCAP (no conjunto disponível de permissões do processo).

 $ getfattr -m - -d `which ping` # file: usr/bin/ping security.capability=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA= $ getcap `which ping` /usr/bin/ping = cap_net_raw+ep 


Casos e comentários especiais


Obviamente, na realidade, nem tudo é tão simples, e há vários casos especiais descritos na página do manual features (7) . Provavelmente os mais importantes são:

  • As permissões de bit e arquivo setuid serão ignoradas se NoNewPrivs estiver instalado ou o sistema de arquivos estiver montado com nosuid ou o processo que chama o execve for rastreado pelo ptrace. As permissões de arquivo também são ignoradas quando o kernel é inicializado com a opção no_file_caps .
  • Um arquivo "burro" (capacidade-burro) é um arquivo binário convertido de um arquivo setuid para um arquivo com permissões de arquivo, mas sem alterar seu código-fonte. Esses arquivos geralmente são obtidos configurando permissões + ep neles, por exemplo, “setcap cap_net_bind_service+ep ./binary” . A parte importante é "e" - eficaz. Após a execução, essas permissões serão adicionadas às disponíveis e às existentes, para que o executável esteja pronto para usar a operação privilegiada. Por outro lado, um arquivo "com capacidade inteligente" que usa libcap ou funcionalidade semelhante pode usar cap_set_proc (3) (ou capset ) para definir os bits "efetivos" ou "herdados" a qualquer momento, se essa permissão já estiver em " kit acessível. Portanto, “ setcap cap_net_bind_service+p ./binary” será suficiente para um arquivo “inteligente”, pois ele poderá definir as permissões necessárias em um conjunto efetivo antes de chamar uma operação privilegiada. Veja o código de exemplo .
  • Os arquivos com setuid-root continuam funcionando, concedendo todos os privilégios de root quando o usuário inicia como não root. Mas se eles tiverem permissões de arquivo definidas, somente elas serão concedidas. Você também pode criar um arquivo setuid com um conjunto vazio de permissões, o que fará com que seja executado como um usuário com UID 0 sem nenhuma permissão. Existem casos especiais para o usuário root ao executar um arquivo com setuid-root e configurar vários sinalizadores de securebits (consulte man).
  • Um conjunto delimitador mascara as permissões disponíveis, mas não as herdadas. Lembre-se de P '(disponível) = F (disponível) e P (limitando). Se um fluxo tiver uma permissão em seu conjunto herdado que não esteja em seu conjunto delimitador, ainda poderá obtê-la em seu conjunto disponível executando um arquivo que tenha permissão em seu conjunto herdado - P '(disponível) = P ( herdado) & F (herdado).
  • A execução de um programa que altera o UID ou o GID por meio dos bits set-user-ID, set-group-ID ou a execução de um programa para o qual todas as permissões de arquivo estão definidas, limpará o ambiente . As permissões são adicionadas ao conjunto circundante usando PR_CAP_AMBIENT prctl . Essas permissões já devem estar presentes nos conjuntos de processos acessíveis e herdados .
  • Se um processo com um UID diferente de 0 executar execve (2) , todos os direitos em seus conjuntos disponíveis e ativos serão excluídos.
  • Se SECBIT_KEEP_CAPS (ou o SECBIT_NO_SETUID_FIXUP mais SECBIT_NO_SETUID_FIXUP ) não estiver definido, e alterar o UID de 0 para diferente de zero removerá todas as permissões dos conjuntos herdados, acessíveis e eficazes .


Então ...


Se o contêiner nginx oficial, ingress-nginx ou o seu próprio parar ou reiniciar com um erro:

bind() to 0.0.0.0:80 failed (13: Permission denied)

... isso significa que houve uma tentativa de escutar na porta 80 como um usuário não privilegiado (não 0) e não havia CAP_NET_BIND_SERVICE no CAP_NET_BIND_SERVICE permissões atual. Para obter esses direitos, você deve usar xattr e set (using setcap ) para obter a permissão de arquivo nginx pelo menos cap_net_bind_service+ie . Essa permissão de arquivo será combinada com o conjunto herdado (especificado com o conjunto delimitador do pod SecurityContext / resource / add / NET_BIND_SERVICE) e também será colocado no conjunto de permissões disponíveis. O resultado é cap_net_bind_service+pie .

Isso funciona desde que securityContext / allowPrivilegeEscalation esteja definido como true e o driver de armazenamento docker / rkt (consulte a documentação do docker) suporte xattrs.

Se o nginx fosse inteligente em relação às permissões, então cap_net_bind_service+i seria suficiente. Então ele poderia usar a libcap para expandir os direitos do conjunto disponível para efetivo. Tendo recebido como resultado cap_net_bind_service+pie .

Além de usar o xattr, a única maneira de obter cap_net_bind_service em um contêiner não raiz é permitir que o Docker defina seus recursos externos (recursos ambientais). Mas a partir de abril de 2019, isso ainda não foi implementado .

Exemplos de código


Aqui está um código de amostra usando o libcap para adicionar CAP_NET_BIND_SERVICE a um conjunto de permissões eficiente. Requer a permissão CAP_BIND_SERVICE+p para o arquivo binário.

Referências (eng.):

Source: https://habr.com/ru/post/pt471802/


All Articles