Scrivere un criterio SELinux

Android Open Source Project (AOSP) fornisce criteri di base solidi per le app e i servizi comuni a tutti i dispositivi Android. I collaboratori di AOSP perfezionano regolarmente queste norme. Si prevede che il criterio principale rappresenterà circa il 90-95% del criterio on-device finale, con personalizzazioni specifiche per dispositivo il restante 5-10%. Questo articolo si concentra su queste personalizzazioni specifiche del dispositivo, su come scrivere norme specifiche per il dispositivo e su alcuni dei problemi da evitare.

Messa in servizio del dispositivo

Quando scrivi le norme specifiche per il dispositivo, segui questi passaggi.

Eseguire in modalità permissiva

Quando un dispositivo è in modalità permissiva, i rifiuti vengono registrati, ma non applicati. La modalità permissiva è importante per due motivi:

  • La modalità permissiva garantisce che l'inizializzazione dei criteri non ritardi altre attività di inizializzazione iniziale del dispositivo.
  • Un rifiuto forzato potrebbe mascherare altri rifiuti. Ad esempio, l'accesso ai file solitamente comporta una ricerca nella directory, l'apertura del file e la lettura del file. In modalità di applicazione, si verifica solo il rifiuto della ricerca nella directory. La modalità permissiva garantisce che tutti i rifiuti vengano visualizzati.

Il modo più semplice per mettere un dispositivo in modalità permissiva è utilizzare la riga di comando del kernel. Questo può essere aggiunto al file BoardConfig.mk del dispositivo: platform/device/<vendor>/<target>/BoardConfig.mk. Dopo aver modificato la riga di comando, premi make clean, quindi make bootimage e esegui il flashing della nuova immagine di avvio.

Dopodiché, conferma la modalità permissiva con:

adb shell getenforce

Due settimane sono un periodo di tempo ragionevole per utilizzare la modalità permissiva globale. Dopo aver risolto la maggior parte dei rifiuti, torna alla modalità di applicazione e risolvi i bug man mano che si presentano. I domini che continuano a produrre rifiuti o servizi ancora in fase di sviluppo intensivo possono essere temporaneamente impostati in modalità permissiva, ma devono essere riportati in modalità di applicazione il prima possibile.

Applicare in anticipo

In modalità di applicazione, i rifiuti vengono registrati e applicati. È buona pratica attivare la modalità di applicazione il prima possibile sul dispositivo. Se aspetti di creare e applicare criteri specifici per i dispositivi, spesso il risultato è un prodotto con bug e un'esperienza utente negativa. Inizia abbastanza presto per partecipare alla versione sperimentale e garantire la copertura completa dei test delle funzionalità nell'uso reale. Se inizi frühzeitig, puoi assicurarti che i problemi di sicurezza influenzino le decisioni di progettazione. Al contrario, la concessione di autorizzazioni basata esclusivamente sui rifiuti osservati è un approccio non sicuro. Sfrutta questo momento per eseguire un controllo di sicurezza del dispositivo e segnalare bug relativi a comportamenti che non devono essere consentiti.

Rimuovere o eliminare i criteri esistenti

Esistono diversi buoni motivi per creare da zero norme specifiche per un dispositivo su un nuovo dispositivo, tra cui:

Risolvere i problemi di rifiuto dei servizi principali

I rifiuti generati dai servizi di base vengono in genere risolti con l'etichettatura dei file. Ad esempio:

avc: denied { open } for pid=1003 comm=”mediaserver” path="/dev/kgsl-3d0”
dev="tmpfs" scontext=u:r:mediaserver:s0 tcontext=u:object_r:device:s0
tclass=chr_file permissive=1
avc: denied { read write } for pid=1003 name="kgsl-3d0" dev="tmpfs"
scontext=u:r:mediaserver:s0
tcontext=u:object_r:device:s0 tclass=chr_file permissive=1

viene completamente risolto etichettando correttamente /dev/kgsl-3d0. In questo esempio, tcontext è device. Questo rappresenta un contesto predefinito in cui tutti gli elementi in /dev ricevono l'etichetta " device", a meno che non venga assegnata un'etichetta più specifica. Se accetti semplicemente l'output di audit2allow, otterrai una regola errata e eccessivamente permissiva.

Per risolvere questo tipo di problema, assegna al file un'etichetta più specifica, che in questo caso è gpu_device. Non sono necessarie altre autorizzazioni perché il mediaserver ha già le autorizzazioni necessarie nel criterio principale per accedere al dispositivo GPU.

Altri file specifici del dispositivo che devono essere etichettati con i tipi predefiniti nel criterio principale:

In generale, la concessione delle autorizzazioni alle etichette predefinite è sbagliata. Molte di queste autorizzazioni non sono consentite dalle regole neverallow, ma anche se non sono esplicitamente vietate, la best practice è fornire un'etichetta specifica.

Etichettare i nuovi servizi e gestire i rifiuti

I servizi avviati da init devono essere eseguiti nei propri domini SELinux. L'esempio seguente inserisce il servizio "foo" nel proprio dominio SELinux e gli concede le autorizzazioni.

Il servizio è stato avviato nel file init.device.rc del nostro dispositivo come:

service foo /system/bin/foo
    class core
  1. Crea un nuovo dominio "foo"

    Crea il file device/manufacturer/device-name/sepolicy/foo.te con il seguente contenuto:

    # foo service
    type foo, domain;
    type foo_exec, exec_type, file_type;
    
    init_daemon_domain(foo)
    

    Questo è il modello iniziale per il dominio SELinux foo, a cui puoi aggiungere regole in base alle operazioni specifiche eseguite dall'eseguibile.

  2. Etichetta /system/bin/foo

    Aggiungi quanto segue a device/manufacturer/device-name/sepolicy/file_contexts:

    /system/bin/foo   u:object_r:foo_exec:s0
    

    In questo modo, l'eseguibile viene etichettato correttamente in modo che SELinux esegua il servizio nel dominio corretto.

  3. Crea e esegui il flashing delle immagini di avvio e di sistema.
  4. Perfeziona le regole SELinux per il dominio.

    Utilizza i rifiuti per determinare le autorizzazioni richieste. Lo strumento audit2allow fornisce buone linee guida, ma utilizzalo solo per informare la scrittura delle norme. Non limitarti a copiare l'output.

Tornare alla modalità di applicazione

Puoi risolvere i problemi in modalità permissiva, ma torna alla modalità di applicazione il prima possibile e cerca di mantenerla.

Errori comuni

Di seguito sono riportate alcune soluzioni per gli errori comuni che si verificano durante la scrittura di criteri specifici per i dispositivi.

Uso eccessivo della negazione

La seguente regola di esempio è come chiudere la porta di casa, ma lasciare aperte le finestre:

allow { domain -untrusted_app } scary_debug_device:chr_file rw_file_perms

Lo scopo è chiaro: tutti, tranne le app di terze parti, potrebbero avere accesso al dispositivo di debug.

La regola presenta alcuni difetti. È facile aggirare l'esclusione di untrusted_app perché, facoltativamente, tutte le app possono eseguire servizi nel dominio isolated_app. Analogamente, se vengono aggiunti nuovi domini per le app di terze parti ad AOSP, anche questi hanno accesso a scary_debug_device. La regola è eccessivamente permissiva. La maggior parte dei domini non trarrà vantaggio dall'accesso a questo strumento di debug. La regola avrebbe dovuto essere scritta in modo da consentire solo i domini che richiedono l'accesso.

Funzionalità di debug in produzione

Le funzionalità di debug non devono essere presenti nelle build di produzione né le relative norme.

L'alternativa più semplice è consentire la funzionalità di debug solo quando SELinux è disabilitato nelle build eng/userdebug, ad esempio adb root e adb shell setenforce 0.

Un'altra alternativa sicura è racchiudere le autorizzazioni di debug in un'istruzione userdebug_or_eng.

Esplosione delle dimensioni dei criteri

Characterizing SEAndroid Policies in the Wild descrive una preoccupante tendenza nella crescita delle personalizzazioni dei criteri dei dispositivi. I criteri specifici del dispositivo devono rappresentare il 5-10% dei criteri complessivi in esecuzione su un dispositivo. Le personalizzazioni nell'intervallo superiore al 20%contengono quasi certamente domini con privilegi eccessivi e norme non più valide.

Criterio eccessivamente grande:

  • Riceve un doppio hit sulla memoria perché il criterio si trova nel ramdisk e viene anche caricato nella memoria del kernel.
  • Spreca spazio su disco perché richiede un file bootimage più grande.
  • Influisce sui tempi di ricerca dei criteri di runtime.

L'esempio seguente mostra due dispositivi in cui le norme specifiche del produttore costituivano il 50% e il 40% dei criteri sul dispositivo. La riscrittura del criterio ha consentito notevoli miglioramenti della sicurezza senza perdita di funzionalità, come mostrato di seguito. I dispositivi AOSP Shamu e Flounder sono inclusi per il confronto.

Figura 1: confronto tra dimensioni di criteri specifici per dispositivo dopo il controllo di sicurezza.

Figura 1. Confronto delle dimensioni delle norme specifiche per il dispositivo dopo il controllo di sicurezza.

In entrambi i casi, il criterio è stato notevolmente ridotto sia in termini di dimensioni che di numero di autorizzazioni. Il calo delle dimensioni del criterio è dovuto quasi interamente alla rimozione di autorizzazioni non necessarie, molte delle quali erano probabilmente regole generate da audit2allow e aggiunte indiscriminatamente al criterio. Anche i domini non attivi sono stati un problema per entrambi i dispositivi.

Concedi la funzionalità dac_override

Un rifiuto dac_override indica che il processo in violazione sta tentando di accedere a un file con le autorizzazioni utente/gruppo/tutti errata di Unix. La soluzione corretta non è quasi mai concedere l'autorizzazione dac_override. Modifica invece le autorizzazioni Unix sul file o sul processo. Alcuni domini come init, vold e installd hanno davvero bisogno della possibilità di ignorare le autorizzazioni dei file Unix per accedere ai file di altri processi. Per una spiegazione più approfondita, consulta il blog di Dan Walsh.