Validar o SELinux

O Android recomenda que os OEMs testem as implementações do SELinux com cuidado. À medida que os fabricantes implementam o SELinux, eles precisam aplicar a nova política primeiro a um pool de teste de dispositivos.

Depois de aplicar uma nova política, verifique se o SELinux está sendo executado no modo correto no dispositivo emitindo o comando getenforce.

Isso imprime o modo global do SELinux: "Enforcing" ou "Permissive". Para determinar o modo do SELinux de cada domínio, examine os arquivos correspondentes ou execute a versão mais recente de sepolicy-analyze com a flag apropriada (-p), presente em /platform/system/sepolicy/tools/.

Ler negações

Verifique se há erros, que são encaminhados como registros de eventos para dmesg e logcat e podem ser visualizados localmente no dispositivo. Os fabricantes precisam examinar a saída do SELinux para dmesg nesses dispositivos e refinar as configurações antes do lançamento público no modo permissivo e da eventual mudança para o modo de aplicação. As mensagens de registro do SELinux contêm avc: e podem ser encontradas facilmente com grep. É possível capturar os registros de negação em andamento executando cat /proc/kmsg ou capturar registros de negação da inicialização anterior executando cat /sys/fs/pstore/console-ramoops.

As mensagens de erro do SELinux são limitadas após a conclusão da inicialização para evitar o excesso de registros. Para garantir que você veja todas as mensagens relevantes, desative essa opção executando adb shell auditctl -r 0.

Com essa saída, os fabricantes podem identificar facilmente quando os usuários ou componentes do sistema violam a política do SELinux. Os fabricantes podem corrigir esse comportamento inadequado fazendo mudanças no software, na política do SELinux ou em ambos.

Especificamente, essas mensagens de registro indicam quais processos falhariam no modo de aplicação e por quê. Confira um exemplo:

avc: denied  { connectto } for  pid=2671 comm="ping" path="/dev/socket/dnsproxyd"
scontext=u:r:shell:s0 tcontext=u:r:netd:s0 tclass=unix_stream_socket

Interprete a saída assim:

  • O { connectto } acima representa a ação que está sendo realizada. Junto com o tclass no final (unix_stream_socket), ele informa aproximadamente o que estava sendo feito para o quê. Nesse caso, algo estava tentando se conectar a um soquete de fluxo do Unix.
  • O scontext (u:r:shell:s0) informa qual contexto iniciou a ação. Nesse caso, é algo que está sendo executado como o shell.
  • O tcontext (u:r:netd:s0) informa o contexto do destino da ação. Nesse caso, é um unix_stream_socket pertencente a netd.
  • O comm="ping" na parte de cima dá outra dica sobre o que estava sendo executado no momento em que a recusa foi gerada. Nesse caso, é uma dica muito boa.

Outro exemplo:

adb shell su root dmesg | grep 'avc: '

Saída:

<5> type=1400 audit: avc:  denied  { read write } for  pid=177
comm="rmt_storage" name="mem" dev="tmpfs" ino=6004 scontext=u:r:rmt:s0
tcontext=u:object_r:kmem_device:s0 tclass=chr_file

Estes são os principais elementos da recusa:

  • Ação: a ação tentada é destacada entre colchetes, read write ou setenforce.
  • Ator: a entrada scontext (contexto de origem) representa o ator, neste caso, o daemon rmt_storage.
  • Objeto: a entrada tcontext (contexto de destino) representa o objeto em que a ação está sendo realizada, neste caso, kmem.
  • Resultado: a entrada tclass (classe de destino) indica o tipo de objeto em que a ação está sendo realizada, neste caso, um chr_file (dispositivo de caractere).

Despejar pilhas de usuário e kernel

Em alguns casos, as informações contidas no registro de eventos não são suficientes para identificar a origem da recusa. Geralmente, é útil coletar a cadeia de chamadas, incluindo kernel e espaço do usuário, para entender melhor por que a negação ocorreu.

Os kernels recentes definem um tracepoint chamado avc:selinux_audited. Use o Android simpleperf para ativar esse ponto de rastreamento e capturar a cadeia de chamadas.

Configuração compatível

  • O kernel do Linux >= 5.10, em especial as ramificações do kernel comum do Android mainline e android12-5.10 são compatíveis. A ramificação android12-5.4 também é compatível. Use simpleperf para determinar se o ponto de rastreamento está definido no seu dispositivo: adb root && adb shell simpleperf list | grep avc:selinux_audited. Para outras versões do kernel, você pode escolher commits dd81662 e 30969bc.
  • É preciso ser possível reproduzir o evento que você está depurando. Eventos de tempo de inicialização não são compatíveis com o simpleperf. No entanto, talvez seja possível reiniciar o serviço para acionar o evento.

Capturar a cadeia de chamadas

A primeira etapa é registrar o evento usando simpleperf record:

adb shell -t "cd /data/local/tmp && su root simpleperf record -a -g -e avc:selinux_audited"

Em seguida, o evento que causou a recusa será acionado. Depois disso, a gravação será interrompida. Neste exemplo, ao usar Ctrl-c, a amostra deveria ter sido capturada:

^Csimpleperf I cmd_record.cpp:751] Samples recorded: 1. Samples lost: 0.

Por fim, simpleperf report pode ser usado para inspecionar o rastreamento de pilha capturado. Por exemplo:

adb shell -t "cd /data/local/tmp && su root simpleperf report -g --full-callgraph"
[...]
Children  Self     Command  Pid   Tid   Shared Object                                   Symbol
100.00%   0.00%    dmesg    3318  3318  /apex/com.android.runtime/lib64/bionic/libc.so  __libc_init
       |
       -- __libc_init
          |
           -- main
              toybox_main
              toy_exec_which
              dmesg_main
              klogctl
              entry_SYSCALL_64_after_hwframe
              do_syscall_64
              __x64_sys_syslog
              do_syslog
              selinux_syslog
              slow_avc_audit
              common_lsm_audit
              avc_audit_post_callback
              avc_audit_post_callback

A cadeia de chamadas acima é unificada para kernel e espaço do usuário. Ele oferece uma visão melhor do fluxo de código, começando o rastreamento do espaço do usuário até o kernel, onde a negação acontece. Para mais informações sobre simpleperf, consulte a Referência de comandos executáveis do Simpleperf

Mudar para permissiva

A aplicação do SELinux pode ser desativada com o adb em builds userdebug ou eng. Para isso, primeiro mude o ADB para root executando adb root. Em seguida, para desativar a aplicação do SELinux, execute:

adb shell setenforce 0

Ou na linha de comando do kernel (durante a inicialização do dispositivo):

androidboot.selinux=permissive
androidboot.selinux=enforcing

Ou pelo bootconfig no Android 12:

androidboot.selinux=permissive
androidboot.selinux=enforcing

Usar o audit2allow

A ferramenta audit2allow usa negações de dmesg e as converte em instruções de política do SELinux correspondentes. Assim, ele pode acelerar muito o desenvolvimento do SELinux.

Para usar, execute:

adb pull /sys/fs/selinux/policy
adb logcat -b events -d | audit2allow -p policy

No entanto, é preciso ter cuidado ao examinar cada possível adição para permissões excessivas. Por exemplo, ao inserir audit2allow na negação rmt_storage mostrada anteriormente, o resultado é a seguinte instrução de política do SELinux sugerida:

#============= shell ==============
allow shell kernel:security setenforce;
#============= rmt ==============
allow rmt kmem_device:chr_file { read write };

Isso concederia a rmt a capacidade de gravar memória do kernel, uma falha de segurança grave. Muitas vezes, as instruções audit2allow são apenas um ponto de partida. Depois de usar essas instruções, talvez seja necessário mudar o domínio de origem e o rótulo do destino, além de incorporar macros adequadas para chegar a uma boa política. Às vezes, a recusa em análise não resulta em mudanças na política, mas sim no app infrator.