Aggiungi proprietà di sistema

Questa pagina fornisce un metodo canonico per aggiungere o definire proprietà di sistema in Android, con linee guida per il refactoring delle proprietà di sistema esistenti. Assicurati di utilizzare le linee guida quando esegui il refactoring, a meno che tu non abbia un forte problema di compatibilità che comporti diversamente.

Passaggio 1: definisci la proprietà di sistema

Quando aggiungi una proprietà di sistema, scegli un nome per la proprietà e associala a un contesto della proprietà SELinux. Se non esiste un contesto appropriato, creane uno nuovo. Il nome viene utilizzato per accedere alla proprietà; il contesto della proprietà viene utilizzato per controllare l'accessibilità in termini di SELinux. I nomi possono essere qualsiasi stringa, ma AOSP consiglia di seguire un formato strutturato per renderli chiari.

Nome proprietà

Utilizza questo formato con il casing snake_case:

[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}]

Utilizza "" (omesso), ro (per le proprietà impostate una sola volta) o persist (per le proprietà che rimangono invariate dopo i riavvii) per l'elemento prefix.

Precisazioni

Utilizza ro solo se hai la certezza di non dover scrivere in prefix in futuro. ** Non specificare il prefisso ro.** Utilizza invece sepolicy per impostare prefix come di sola lettura (in altre parole, scrivibile solo da init).

Utilizza persist solo se hai la certezza che il valore debba essere mantenuto dopo i riavvii e che l'utilizzo delle proprietà di sistema sia la tua unica opzione.

Google esamina rigorosamente le proprietà di sistema che hanno proprietà ro o persist.

Il termine group viene utilizzato per aggregare le proprietà correlate. È destinato a essere un nome di sottosistema simile in uso a audio o telephony. Non utilizzare termini ambigui o sovraccaricati come sys, system, dev, default o config.

È buona norma utilizzare il nome del tipo di dominio di un processo che ha accesso esclusivo in lettura o scrittura alle proprietà di sistema. Ad esempio, per le proprietà di sistema a cui il processo vold ha accesso in scrittura, è comune utilizzare vold (il nome del tipo di dominio per il processo) come nome del gruppo.

Se necessario, aggiungi subgroup per classificare ulteriormente le proprietà, ma evita di usare termini ambigui o sovraccaricati per descrivere questo elemento. Puoi anche avere più di un subgroup.

Molti nomi di gruppi sono già stati definiti. Controlla il file system/sepolicy/private/property_contexts e, se possibile, utilizza i nomi di gruppo esistenti anziché crearne di nuovi. La tabella seguente fornisce esempi di nomi di gruppi utilizzati di frequente.

Dominio Gruppo (e sottogruppo)
correlato al bluetooth bluetooth
sysprops dalla cmdline del kernel boot
sysprops che identificano una build build
correlata alla telefonia telephony
correlati all'audio audio
correlata alla grafica graphics
correlato a vold vold

Di seguito viene definito l'utilizzo di name e type nell'esempio di expressioni regolari precedente.

[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}]

  • name identifica una proprietà di sistema all'interno di un gruppo.

  • type è un elemento facoltativo che chiarisce il tipo o lo scopo della proprietà di sistema. Ad esempio, anziché assegnare a una proprietà di sistema il nome audio.awesome_feature_enabled o semplicemente audio.awesome_feature, rinominala come audio.awesome_feature.enabled per riflettere il tipo e lo scopo della proprietà di sistema.

Non esiste una regola specifica sul tipo che deve essere utilizzato. Di seguito sono riportati alcuni consigli di utilizzo:

  • enabled: da utilizzare se il tipo è una proprietà di sistema booleana utilizzata per attivare o disattivare una funzionalità.
  • config: da utilizzare se l'intento è chiarire che la proprietà di sistema non rappresenta uno stato dinamico del sistema; rappresenta un valore preconfigurato (ad esempio un elemento di sola lettura).
  • List: da utilizzare se si tratta di una proprietà di sistema il cui valore è un elenco.
  • Timeoutmillis: da utilizzare se si tratta di una proprietà di sistema per un valore di timeout in unità di ms.

Esempi:

  • persist.radio.multisim.config
  • drm.service.enabled

Contesto della proprietà

Il nuovo schema del contesto della proprietà SELinux consente una granularità più fine e nomi più descrittivi. Analogamente a quanto utilizzato per i nomi delle proprietà, AOSP consiglia il seguente formato:

{group}[_{subgroup}]*_prop

I termini sono definiti come segue:

group e subgroup hanno lo stesso significato definito per la precedente expressione regolare di esempio. Ad esempio, vold_config_prop indica proprietà che sono configurazioni di un fornitore e che devono essere impostate da vendor_init, mentre vold_status_prop o semplicemente vold_prop indicano proprietà che devono mostrare lo stato corrente di vold.

Quando assegni un nome a un contesto della proprietà, scegli nomi che riflettano l'utilizzo generale delle proprietà. In particolare, evita i seguenti tipi di termini:

  • Termini troppo generici e ambigui, ad esempio sys, system e default.
  • Termini che codificano direttamente l'accessibilità, ad esempio exported, apponly, ro, public, private.

Preferisci l'utilizzo di nomi come vold_config_prop rispetto a exported_vold_prop o vold_vendor_writable_prop.

Digitazione

Un tipo di proprietà può essere uno dei seguenti, come indicato nella tabella.

Digitazione Definizione
Booleano true o 1 per true, false o 0 per false
Numero intero Intero a 64 bit con segno
Numero intero senza segno numero intero a 64 bit senza segno
Doppio a virgola mobile a precisione doppia
Stringa Qualsiasi stringa UTF-8 valida
enum I valori possono essere qualsiasi stringa UTF-8 valida senza spazi
Elenco di quanto sopra Una virgola (,) viene utilizzata come delimitatore
L'elenco interi [1, 2, 3] viene memorizzato come 1,2,3

All'interno, tutte le proprietà vengono memorizzate come stringhe. Puoi applicare il tipo specificandolo come file property_contexts. Per ulteriori informazioni, consulta property_contexts nel passaggio 3.

Passaggio 2: determina i livelli di accessibilità richiesti

Esistono quattro macro di supporto che definiscono una proprietà.

Tipo di accessibilità Significato
system_internal_prop Proprietà utilizzate solo in /system
system_restricted_prop Proprietà lette al di fuori di /system, ma non scritte
system_vendor_config_prop Proprietà lette al di fuori di /system e scritte solo da vendor_init
system_public_prop Proprietà lette e scritte all'esterno di /system

Limita l'accesso alle proprietà di sistema il più possibile. In passato, l'accesso ampio ha causato malfunzionamenti delle app e vulnerabilità di sicurezza. Quando definisci l'ambito, poniti le seguenti domande:

  • Questa proprietà di sistema deve essere persistente? (Se la risposta è sì, perché?)
  • Quale processo deve avere accesso in lettura a questa proprietà?
  • Quale processo deve avere accesso in scrittura a questa proprietà?

Utilizza le domande precedenti e la seguente struttura decisionale come strumenti per determinare un ambito appropriato per l'accesso.

Albero decisionale per determinare l'ambito di accesso

Figura 1. Albero decisionale per determinare l'ambito di accesso alle proprietà di sistema

Passaggio 3: aggiungi a system/sepolicy

Quando si accede a sysprop, SELinux controlla l'accessibilità dei processi. Dopo aver determinato il livello di accessibilità richiesto, definisci i contesti della proprietà in system/sepolicy, insieme a regole allow e neverallow aggiuntive su cosa i processi possono (e non possono) leggere o scrivere.

Innanzitutto, definisci il contesto della proprietà nel file system/sepolicy/public/property.te. Se la proprietà è interna al sistema, definiscila nel file system/sepolicy/private/property.te. Utilizza una delle macrosystem_[accessibility]_prop([context]) che fornisce l'accessibilità richiesta per la proprietà di sistema. Ecco un esempio per il file system/sepolicy/public/property.te:

system_public_prop(audio_foo_prop)
system_vendor_config_prop(audio_bar_prop)

Esempio da aggiungere al file system/sepolicy/private/property.te:

system_internal_prop(audio_baz_prop)

In secondo luogo, concedi l'accesso in lettura e/o scrittura al contesto della proprietà. Utilizza le macro set_prop e get_prop per concedere l'accesso, nel file system/sepolicy/public/{domain}.te o system/sepolicy/private/{domain}.te. Utilizza private, se possibile. public è adatto solo se la macro set_prop o get_prop interessa domini esterni al dominio principale.

Esempio, nel file system/sepolicy/private/audio.te:

set_prop(audio, audio_foo_prop)
set_prop(audio, audio_bar_prop)

Esempio, nel file system/sepolicy/public/domain.te:

get_prop(domain, audio_bar_prop)

In terzo luogo, aggiungi alcune regole che non consentono l'accesso per ridurre ulteriormente l'accessibilità nell'ambito della macro. Ad esempio, supponiamo che tu abbia utilizzato system_restricted_prop perché le proprietà di sistema devono essere lette dai processi del fornitore. Se l'accesso in lettura non è richiesto da tutti i processi del fornitore ed è richiesto solo da un determinato insieme di processi (ad esempio, vendor_init), proibisci i processi del fornitore che non richiedono l'accesso in lettura.

Utilizza la seguente sintassi per limitare l'accesso in scrittura e in lettura:

Per limitare l'accesso in scrittura:

neverallow [domain] [context]:property_service set;

Per limitare l'accesso in lettura:

neverallow [domain] [context]:file no_rw_file_perms;

Inserisci le regole neverallow nel file system/sepolicy/private/{domain}.te se la regola neverallow è associata a un dominio specifico. Per regole neverallow più ampie, utilizza domini generali come questi, ove opportuno:

  • system/sepolicy/private/property.te
  • system/sepolicy/private/coredomain.te
  • system/sepolicy/private/domain.te

Nel file system/sepolicy/private/audio.te, inserisci quanto segue:

neverallow {
    domain -init -audio
} {audio_foo_prop audio_bar_prop}:property_service set;

Nel file system/sepolicy/private/property.te, inserisci quanto segue:

neverallow {
    domain -coredomain -vendor_init
} audio_prop:file no_rw_file_perms;

Tieni presente che {domain -coredomain} acquisisce tutte le procedure del fornitore. Pertanto, {domain -coredomain -vendor_init} significa "tutti i processi del fornitore tranne vendor_init".

Infine, associa una proprietà di sistema al contesto della proprietà. In questo modo, l'accesso concesso e le regole neverallow applicate ai contesti delle proprietà vengono applicati alle proprietà effettive. A tale scopo, aggiungi una voce al file property_contexts, un file che descrive la mappatura tra le proprietà del sistema e i relativi contesti. In questo file, puoi specificare una singola proprietà o un prefisso per le proprietà da mappare in un contesto.

Questa è la sintassi per mappare una singola proprietà:

[property_name] u:object_r:[context_name]:s0 exact [type]

Questa è la sintassi per mappare un prefisso:

[property_name_prefix] u:object_r:[context_name]:s0 prefix [type]

Se vuoi, puoi specificare il tipo di proprietà, che può essere uno dei seguenti:

  • bool
  • int
  • uint
  • double
  • enum [list of possible values...]
  • string (utilizza string per le proprietà di elenco).

Assicurati che ogni voce abbia il tipo designato, se possibile, poiché type viene applicato quando viene impostato property. L'esempio seguente mostra come scrivere una mappatura:

# binds a boolean property "ro.audio.status.enabled"
# to the context "audio_foo_prop"
ro.audio.status.enabled u:object_r:audio_foo_prop:s0 exact bool

# binds a boolean property "vold.decrypt.status"
# to the context "vold_foo_prop"
# The property can only be set to one of these: on, off, unknown
vold.decrypt.status u:object_r:vold_foo_prop:s0 exact enum on off unknown

# binds any properties starting with "ro.audio.status."
# to the context "audio_bar_prop", such as
# "ro.audio.status.foo", or "ro.audio.status.bar.baz", and so on.
ro.audio.status. u:object_r:audio_bar_prop:s0 prefix

Quando una voce esatta e una voce con prefisso sono in conflitto, la voce esatta ha la precedenza. Per altri esempi, vedi system/sepolicy/private/property_contexts.

Passaggio 4: determina i requisiti di stabilità

La stabilità è un altro aspetto delle proprietà di sistema e si differenzia dall'accessibilità. La stabilità riguarda se una proprietà di sistema può essere modificata (ad esempio rinominata o addirittura rimossa) in futuro. Questo è particolarmente importante man mano che il sistema operativo Android diventa modulare. Con Treble, le partizioni di sistema, fornitore e prodotto possono essere aggiornate in modo indipendente. Con Mainline, alcune parti del sistema operativo sono modularizzate in moduli aggiornabili (in APEX o APK).

Se una proprietà di sistema è destinata all'utilizzo in componenti software aggiornabili, ad esempio nelle partizioni di sistema e del fornitore, deve essere stabile. Tuttavia, se viene utilizzato, ad esempio, solo all'interno di un modulo Mainline specifico, puoi modificarne il nome, il tipo o i contesti delle proprietà e persino rimuoverlo.

Per determinare la stabilità di una proprietà di sistema, poni le seguenti domande:

  • Questa proprietà di sistema è destinata a essere configurata dai partner (o configurata diversamente in base al dispositivo)? Se sì, deve essere stabile.
  • Questa proprietà di sistema definita da AOSP è destinata a essere scritta o letta da codice (non processo) esistente in partizioni non di sistema come vendor.img o product.img? Se sì, deve essere stabile.
  • È possibile accedere a questa proprietà di sistema tramite i moduli Mainline o tramite un modulo Mainline e la parte non aggiornabile della piattaforma? Se sì, deve essere stabile.

Per le proprietà di sistema stabili, definisci formalmente ciascuna come API e utilizza l'API per accedere alla proprietà di sistema, come spiegato nel passaggio 6.

Passaggio 5: imposta le proprietà in fase di compilazione

Imposta le proprietà al momento della creazione con le variabili makefile. Tecnicamente, i valori sono integrati in {partition}/build.prop. Quindi init legge {partition}/build.prop per impostare le proprietà. Esistono due insiemi di queste variabili: PRODUCT_{PARTITION}_PROPERTIES e TARGET_{PARTITION}_PROP.

PRODUCT_{PARTITION}_PROPERTIES contiene un elenco di valori delle proprietà. La sintassi è {prop}={value} o {prop}?={value}.

{prop}={value} è un'assegnazione normale che garantisce che {prop} sia impostato su {value}; è possibile una sola assegnazione di questo tipo per una singola proprietà.

{prop}?={value} è un compito facoltativo; {prop} viene impostato su {value} solo se non sono presenti assegnazioni {prop}={value}. Se esistono più compiti facoltativi, viene scelto il primo.

# sets persist.traced.enable to 1 with system/build.prop
PRODUCT_SYSTEM_PROPERTIES += persist.traced.enable=1

# sets ro.zygote to zygote32 with system/build.prop
# but only when there are no other assignments to ro.zygote
# optional are useful when giving a default value to a property
PRODUCT_SYSTEM_PROPERTIES += ro.zygote?=zygote32

# sets ro.config.low_ram to true with vendor/build.prop
PRODUCT_VENDOR_PROPERTIES += ro.config.low_ram=true

TARGET_{PARTITION}_PROP contiene un elenco di file, che viene emesso direttamente in {partition}/build.prop. Ogni file contiene un elenco di coppie {prop}={value}.

# example.prop

ro.cp_system_other_odex=0
ro.adb.secure=0
ro.control_privapp_permissions=disable

# emits example.prop to system/build.prop
TARGET_SYSTEM_PROP += example.prop

Per maggiori dettagli, consulta build/make/core/sysprop.mk.

Passaggio 6: accedi alle proprietà in fase di runtime

Le proprietà possono essere lette e scritte in fase di runtime.

Script di inizializzazione

I file di script di inizializzazione (in genere file *.rc) possono leggere una proprietà tramite ${prop} o ${prop:-default}, possono impostare un'azione che viene eseguita ogni volta che una proprietà diventa un valore specifico e possono scrivere le proprietà utilizzando il comando setprop.

# when persist.device_config.global_settings.sys_traced becomes 1,
# set persist.traced.enable to 1
on property:persist.device_config.global_settings.sys_traced=1
    setprop persist.traced.enable 1

# when security.perf_harden becomes 0,
# write /proc/sys/kernel/sample_rate to the value of
# debug.sample_rate. If it's empty, write -100000 instead
on property:security.perf_harden=0
    write /proc/sys/kernel/sample_rate ${debug.sample_rate:-100000}

i comandi della shell getprop e setprop

Puoi utilizzare, rispettivamente, i comandi della shell getprop o setprop per leggere o scrivere le proprietà. Per ulteriori dettagli, invoca getprop --help o setprop --help.

$ adb shell getprop ro.vndk.version
$
$ adb shell setprop security.perf_harden 0

Sysprop come API per C++/Java/Rust

Con sysprop come API, puoi definire proprietà di sistema e utilizzare API generate automaticamente, che sono concrete e con tipi. L'impostazione di scope con Public rende inoltre disponibili le API generate per i moduli oltre i confini e garantisce la stabilità delle API. Ecco un esempio di file .sysprop, modulo Android.bp e codice C++, Java e Rust che li utilizzano.

# AudioProps.sysprop
# module becomes static class (Java) / namespace (C++) for serving API
module: "android.sysprop.AudioProps"
# owner can be Platform or Vendor or Odm
owner: Platform
# one prop defines one property
prop {
    prop_name: "ro.audio.volume.level"
    type: Integer
    scope: Public
    access: ReadWrite
    api_name: "volume_level"
}
…
// Android.bp
sysprop_library {
    name: "AudioProps",
    srcs: ["android/sysprop/AudioProps.sysprop"],
    property_owner: "Platform",
}

// Rust, Java and C++ modules can link against the sysprop_library
rust_binary {
    rustlibs: ["libaudioprops_rust"],
    …
}

java_library {
    static_libs: ["AudioProps"],
    …
}

cc_binary {
    static_libs: ["libAudioProps"],
    …
}
// Rust code accessing generated API.
// Get volume. Use 50 as the default value.
let vol = audioprops::volume_level()?.unwrap_or_else(50);
// Java codes accessing generated API
// get volume. use 50 as the default value.
int vol = android.sysprop.AudioProps.volume_level().orElse(50);
// add 10 to the volume level.
android.sysprop.AudioProps.volume_level(vol + 10);
// C++ codes accessing generated API
// get volume. use 50 as the default value.
int vol = android::sysprop::AudioProps::volume_level().value_or(50);
// add 10 to the volume level.
android::sysprop::AudioProps::volume_level(vol + 10);

Per ulteriori informazioni, consulta Implementare le proprietà di sistema come API.

Funzioni e metodi delle proprietà di basso livello C/C++, Java e Rust

Se possibile, utilizza Sysprop come API anche se hai a disposizione funzioni C/C++ o Rust di basso livello o metodi Java di basso livello.

libc, libbase e libcutils offrono funzioni di proprietà di sistema C++. libc ha l'API sottostante, mentre le funzioni libbase e libcutils sono wrapper. Se è possibile, utilizza le funzioni sysprop libbase. Sono le più comode e i binari dell'host possono utilizzare le funzioni libbase. Per maggiori dettagli, consulta sys/system_properties.h (libc), android-base/properties.h (libbase) e cutils/properties.h (libcutils).

La classe android.os.SystemProperties offre metodi per le proprietà di sistema Java.

Il modulo rustutils::system_properties offre funzioni e tipi di proprietà di sistema Rust.

Appendice: aggiungi proprietà specifiche del fornitore

I partner (inclusi i Googler che lavorano nel contesto dello sviluppo di Pixel) vogliono definire proprietà di sistema specifiche per hardware (o dispositivo). Le proprietà specifiche del fornitore sono proprietà di proprietà del partner che sono univoche per il proprio hardware o dispositivo, non per la piattaforma. Poiché dipendono dall'hardware o dal dispositivo, devono essere utilizzati all'interno delle partizioni /vendor o /odm.

A partire dal progetto Treble, le proprietà della piattaforma e quelle del fornitore sono state completamente suddivise per evitare conflitti. Di seguito viene descritto come definire le proprietà del fornitore e quali devono essere sempre utilizzate.

Spazio dei nomi nei nomi delle proprietà e dei contesti

Per evitare collisioni tra le proprietà del fornitore e quelle di altre partizioni, tutte le proprietà del fornitore devono iniziare con uno dei seguenti prefissi.

  • ctl.odm.
  • ctl.vendor.
  • ctl.start$odm.
  • ctl.start$vendor.
  • ctl.stop$odm.
  • ctl.stop$vendor.
  • init.svc.odm.
  • init.svc.vendor.
  • ro.odm.
  • ro.vendor.
  • odm.
  • persist.odm.
  • persist.vendor.
  • vendor.

Tieni presente che ro.hardware. è consentito come prefisso, ma solo per motivi di compatibilità. Non utilizzarlo per le normali proprietà.

Gli esempi riportati di seguito utilizzano tutti uno dei prefissi elencati sopra:

  • vendor.display.primary_red
  • persist.vendor.faceauth.use_disk_cache
  • ro.odm.hardware.platform

Tutti i contesti delle proprietà del fornitore devono iniziare con vendor_. Questo vale anche per la compatibilità. Ecco alcuni esempi:

  • vendor_radio_prop.
  • vendor_faceauth_prop.
  • vendor_usb_prop.

È responsabilità del fornitore assegnare un nome e gestire le proprietà, quindi segui il formato suggerito nel passaggio 2, oltre ai requisiti degli spazi dei nomi del fornitore.

Regole SEPolicy e property_contexts specifiche del fornitore

Le proprietà del fornitore possono essere definite dalla macro vendor_internal_prop. Inserisci le regole specifiche del fornitore che definisci nella directory BOARD_VENDOR_SEPOLICY_DIRS. Ad esempio, supponiamo che tu stia definendo una proprietà faceauth del fornitore in coral.

Nel file BoardConfig.mk (o in qualsiasi include BoardConfig.mk), inserisci quanto segue:

BOARD_VENDOR_SEPOLICY_DIRS := device/google/coral-sepolicy

Nel file device/google/coral-sepolicy/private/property.te, inserisci quanto segue:

vendor_internal_prop(vendor_faceauth_prop)

Nel file device/google/coral-sepolicy/private/property_contexts, inserisci quanto segue:

vendor.faceauth.trace u:object_r:vendor_faceauth_prop:s0 exact bool

Limitazioni delle proprietà del fornitore

Poiché le partizioni di sistema e di prodotto non possono dipendere dal fornitore, non consentire mai l'accesso alle proprietà del fornitore dalle partizioni system, system-ext o product.

Appendice: rinominare le proprietà esistenti

Quando devi ritirare una proprietà e spostarla a una nuova, utilizza Sysprop come API per rinominare le proprietà esistenti. In questo modo viene mantenuta la compatibilità con le versioni precedenti specificando sia il nome precedente sia il nome della nuova proprietà. In particolare, puoi impostare il nome precedente in base al campo legacy_prop_name nel file .sysprop. L'API generata tenta di leggere prop_name e utilizza legacy_prop_name se prop_name non esiste.

Ad esempio, i passaggi che seguono rinominano awesome_feature_foo_enabled in foo.awesome_feature.enabled.

Nel file foo.sysprop

module: "android.sysprop.foo"
owner: Platform
prop {
    api_name: "is_awesome_feature_enabled"
    type: Boolean
    scope: Public
    access: Readonly
    prop_name: "foo.awesome_feature.enabled"
    legacy_prop_name: "awesome_feature_foo_enabled"
}

Nel codice C++

// is_awesome_feature_enabled() reads "foo.awesome_feature.enabled".
// If it doesn't exist, reads "awesome_feature_foo_enabled" instead
using android::sysprop::foo;

bool enabled = foo::is_awesome_feature_enabled().value_or(false);

Tieni presente i seguenti aspetti:

  • Innanzitutto, non puoi modificare il tipo di sysprop. Ad esempio, non puoi trasformare una proprietà int in una proprietà string. Puoi solo modificarne il nome.

  • In secondo luogo, solo l'API di lettura utilizza il nome precedente. L'API di scrittura non supporta il fallback. Se la proprietà sysprop è scrivibile, non puoi rinominarla.