Ajouter des propriétés système

Cette page fournit une méthode canonique pour ajouter ou définir des propriétés système dans Android, ainsi que des consignes pour refactoriser les propriétés système existantes. Veillez à respecter les consignes lors de la refactorisation, sauf si vous rencontrez un problème de compatibilité important qui en dise le contraire.

Étape 1: Définir la propriété système

Lorsque vous ajoutez une propriété système, choisissez un nom pour la propriété et associez-la à un contexte de propriété SELinux. S'il n'y a pas de contexte existant approprié, créez-en un. Le nom est utilisé lors de l'accès à la propriété. Le contexte de la propriété permet de contrôler l'accessibilité en termes de SELinux. Les noms peuvent être n'importe quelle chaîne, mais l'AOSP vous recommande de suivre un format structuré pour les rendre clairs.

Nom de la propriété

Utilisez ce format avec la casse snake_case:

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

Utilisez "" (omis), ro (pour les propriétés définies une seule fois) ou persist (pour les propriétés qui sont conservées lors des redémarrages) pour l'élément prefix.

Mises en garde

N'utilisez ro que lorsque vous êtes certain de ne pas avoir besoin de prefix pour être accessible en écriture à l'avenir. ** Ne spécifiez pas le préfixe ro.** Utilisez plutôt sepolicy pour que prefix soit en lecture seule (en d'autres termes, n'est accessible en écriture que par init).

N'utilisez persist que lorsque vous êtes certain que la valeur doit être conservée lors des redémarrages et que l'utilisation des propriétés système est votre seule option.

Google examine strictement les propriétés système qui possèdent des propriétés ro ou persist.

Le terme group est utilisé pour agréger des propriétés associées. Il doit s'agir d'un nom de sous-système semblable à celui utilisé pour audio ou telephony. N'utilisez pas de termes ambigus ou surchargés tels que sys, system, dev, default ou config.

Il est courant d'utiliser le nom du type de domaine d'un processus disposant d'un accès exclusif en lecture ou en écriture aux propriétés système. Par exemple, pour les propriétés système auxquelles le processus vold a un accès en écriture, il est courant d'utiliser vold (nom du type de domaine du processus) comme nom de groupe.

Si nécessaire, ajoutez subgroup pour mieux classer les propriétés, mais évitez les termes ambigus ou surchargés pour décrire cet élément. Vous pouvez également avoir plusieurs subgroup.

De nombreux noms de groupes ont déjà été définis. Vérifiez le fichier system/sepolicy/private/property_contexts et utilisez les noms de groupe existants si possible, au lieu d'en créer de nouveaux. Le tableau suivant fournit des exemples de noms de groupe fréquemment utilisés.

Domaine Groupe (et sous-groupe)
en lien avec le Bluetooth bluetooth
sysprops de la ligne de commande du noyau boot
sysprops qui identifient un build build
concernant la téléphonie telephony
audio audio
images graphics
Vold vold

Ce qui suit définit l'utilisation de name et type dans l'exemple d'expression régulière précédent.

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

  • name identifie une propriété système dans un groupe.

  • type est un élément facultatif qui clarifie le type ou l'intention de la propriété système. Par exemple, au lieu de nommer un sysprop audio.awesome_feature_enabled ou simplement audio.awesome_feature, renommez-le audio.awesome_feature.enabled pour refléter le type de propriété système et l'intent.

Il n'existe pas de règle spécifique concernant le type. Voici des recommandations d'utilisation:

  • enabled: à utiliser si le type est une propriété système booléenne utilisée pour activer ou désactiver une fonctionnalité.
  • config: permet de préciser que la propriété système ne représente pas un état dynamique du système, mais une valeur préconfigurée (par exemple, un élément en lecture seule).
  • List: à utiliser s'il s'agit d'une propriété système dont la valeur est une liste.
  • Timeoutmillis: à utiliser s'il s'agit d'une propriété système d'une valeur de délai avant expiration exprimée en unités de ms.

Exemples :

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

Contexte de la propriété

Le nouveau schéma de contexte des propriétés SELinux permet une plus grande précision et des noms plus descriptifs. Comme pour les noms de propriétés, AOSP recommande le format suivant:

{group}[_{subgroup}]*_prop

Ces termes sont définis comme suit:

group et subgroup ont la même signification que celle définie dans l'exemple d'expression régulière précédent. Par exemple, vold_config_prop correspond aux propriétés qui sont des configurations provenant d'un fournisseur et qui doivent être définies par vendor_init, tandis que vold_status_prop ou vold_prop désigne les propriétés qui doivent exposer l'état actuel de vold.

Lorsque vous nommez un contexte de propriété, choisissez des noms qui reflètent l'utilisation générale des propriétés. Évitez en particulier les types de termes suivants:

  • Termes qui semblent trop généraux et ambigus, tels que sys, system ou default.
  • Termes qui encodent directement l'accessibilité: exported, apponly, ro, public, private, par exemple.

Utilisez de préférence des utilisations de nom telles que vold_config_prop à exported_vold_prop ou vold_vendor_writable_prop.

Type

Un type de propriété peut être l'un des suivants, comme indiqué dans le tableau.

Type Définition
Booléen true ou 1 pour "true", false ou 0 pour "false".
Nombre entier entier signé de 64 bits
Entier non signé Entier de 64 bits non signé
Double virgule flottante à double précision
Chaîne Toute chaîne UTF-8 valide
enum Les valeurs peuvent être n'importe quelle chaîne UTF-8 valide sans espaces blancs
Liste des éléments ci-dessus Une virgule (,) est utilisée comme délimiteur
La liste d'entiers [1, 2, 3] est stockée sous la forme 1,2,3

En interne, toutes les propriétés sont stockées sous forme de chaînes. Vous pouvez appliquer ce type en le spécifiant en tant que fichier property_contexts. Pour en savoir plus, consultez property_contexts à l'étape 3.

Étape 2: Déterminez les niveaux d'accessibilité requis

Quatre macros d'assistance définissent une propriété.

Type d'accessibilité Signification
system_internal_prop Propriétés utilisées uniquement dans /system
system_restricted_prop Propriétés lues en dehors de /system, mais non écrites
system_vendor_config_prop Propriétés lues en dehors de /system et écrites uniquement par vendor_init
system_public_prop Propriétés lues et écrites en dehors de /system

Limitez l'accès aux propriétés système aussi précisément que possible. Par le passé, l'accès étendu a entraîné des défaillances d'applications et des failles de sécurité. Tenez compte des questions suivantes lors de la définition de la portée:

  • Cette propriété système doit-elle être conservée ? (si oui, pourquoi ?)
  • Quel processus devrait disposer d'un accès en lecture à cette propriété ?
  • Quel processus doit disposer d'un accès en écriture à cette propriété ?

Utilisez les questions précédentes et l'arbre de décision suivant comme outils pour déterminer un champ d'application approprié pour l'accès.

Arbre de décision pour déterminer l'étendue de l'accès

Figure 1 : Arbre de décision permettant de déterminer l'étendue de l'accès aux propriétés système

Étape 3: Ajouter à system/sepolicy

Lorsque vous accédez à sysprop, SELinux contrôle l'accessibilité des processus. Une fois que vous avez déterminé le niveau d'accessibilité requis, définissez des contextes de propriété sous system/sepolicy, ainsi que des règles supplémentaires allow et neverallow concernant les processus autorisés en lecture et en écriture.

Tout d'abord, définissez le contexte de la propriété dans le fichier system/sepolicy/public/property.te. Si la propriété est interne au système, définissez-la dans le fichier system/sepolicy/private/property.te. Utilisez l'une des macros system_[accessibility]_prop([context]) qui fournit l'accessibilité requise pour votre propriété système. Voici un exemple pour le fichier system/sepolicy/public/property.te:

system_public_prop(audio_foo_prop)
system_vendor_config_prop(audio_bar_prop)

Exemple à ajouter dans le fichier system/sepolicy/private/property.te:

system_internal_prop(audio_baz_prop)

Ensuite, accordez un accès en lecture et (ou) en écriture au contexte de la propriété. Utilisez les macros set_prop et get_prop pour accorder l'accès, dans le fichier system/sepolicy/public/{domain}.te ou system/sepolicy/private/{domain}.te. Utilisez private dans la mesure du possible. public ne convient que si la macro set_prop ou get_prop affecte des domaines extérieurs au domaine principal.

Exemple dans le fichier system/sepolicy/private/audio.te:

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

Exemple dans le fichier system/sepolicy/public/domain.te:

get_prop(domain, audio_bar_prop)

Troisièmement, ajoutez des règles de blocage afin de réduire davantage l'accessibilité limitée par la macro. Par exemple, supposons que vous ayez utilisé system_restricted_prop, car vos propriétés système doivent être lues par les processus des fournisseurs. Si l'accès en lecture n'est pas requis par tous les processus du fournisseur et qu'il n'est requis que par un certain ensemble de processus (tel que vendor_init), interdisez les processus du fournisseur qui n'ont pas besoin de l'accès en lecture.

Utilisez la syntaxe suivante pour restreindre l'accès en écriture et en lecture:

Pour restreindre l'accès en écriture:

neverallow [domain] [context]:property_service set;

Pour restreindre l'accès en lecture:

neverallow [domain] [context]:file no_rw_file_perms;

Placez les règles ne jamais autoriser dans le fichier system/sepolicy/private/{domain}.te si la règle de blocage est liée à un domaine spécifique. Pour des règles de blocage étendues, utilisez des domaines généraux tels que les suivants, le cas échéant:

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

Dans le fichier system/sepolicy/private/audio.te, placez le code suivant:

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

Dans le fichier system/sepolicy/private/property.te, placez le code suivant:

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

Notez que {domain -coredomain} capture tous les processus des fournisseurs. Ainsi, {domain -coredomain -vendor_init} signifie "tous les processus du fournisseur sauf vendor_init".

Enfin, associez une propriété système au contexte de la propriété. Cela garantit que l'accès accordé et les règles de blocage appliquées aux contextes de propriété sont appliqués aux propriétés réelles. Pour ce faire, ajoutez une entrée au fichier property_contexts, qui décrit le mappage entre les propriétés système et les contextes de propriété. Dans ce fichier, vous pouvez spécifier une seule propriété ou un préfixe pour les propriétés à mapper dans un contexte.

Voici la syntaxe permettant de mapper une seule propriété:

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

Voici la syntaxe pour mapper un préfixe:

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

Vous pouvez éventuellement spécifier le type de propriété, parmi les suivants:

  • bool
  • int
  • uint
  • double
  • enum [list of possible values...]
  • string (utilisez string pour les propriétés de liste)

Dans la mesure du possible, assurez-vous que chaque entrée possède son type désigné, car type est appliqué lorsque vous définissez property. L'exemple suivant montre comment écrire un mappage:

# 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

En cas de conflit entre une entrée exacte et une entrée de préfixe, l'entrée exacte prévaut. Pour plus d'exemples, consultez system/sepolicy/private/property_contexts.

Étape 4: Déterminez les exigences de stabilité

La stabilité est un autre aspect des propriétés système. Elle diffère de l'accessibilité. La stabilité consiste à déterminer si une propriété système peut être modifiée ou non (par exemple, renommée ou même supprimée) à l'avenir. Ce point est particulièrement important, car l'OS Android devient modulaire. Avec Treble, les partitions du système, du fournisseur et des produits peuvent être mises à jour indépendamment les unes des autres. Avec Mainline, certaines parties de l'OS sont modularisées en tant que modules pouvant être mis à jour (dans les APEX ou les APK).

Si une propriété système est destinée à être utilisée dans des éléments logiciels pouvant être mis à jour, par exemple dans des partitions système et de fournisseur, elle doit être stable. Toutefois, s'il n'est utilisé que dans un module principal spécifique, par exemple, vous pouvez modifier son nom, son type ou son contexte de propriété, et même le supprimer.

Posez les questions suivantes pour déterminer la stabilité d'une propriété système:

  • Cette propriété système est-elle destinée à être configurée par des partenaires (ou configurée différemment selon l'appareil) ? Si c'est le cas, elle doit être stable.
  • Cette propriété système définie par AOSP est-elle destinée à être écrite ou lue dans du code (et non un processus) existant dans des partitions non système telles que vendor.img ou product.img ? Si c'est le cas, elle doit être stable.
  • Cette propriété système est-elle accessible dans les modules Mainline ou sur un module Mainline, et dans la partie non mise à jour de la plate-forme ? Si c'est le cas, elle doit être stable.

Pour les propriétés système stables, définissez formellement chacune en tant qu'API et utilisez l'API pour accéder à la propriété système, comme expliqué à l'étape 6.

Étape 5: Définir les propriétés au moment de la compilation

Définissez les propriétés au moment de la compilation avec des variables makefile. Techniquement, les valeurs sont intégrées dans {partition}/build.prop. init lit ensuite {partition}/build.prop pour définir les propriétés. Il existe deux ensembles de variables de ce type: PRODUCT_{PARTITION}_PROPERTIES et TARGET_{PARTITION}_PROP.

PRODUCT_{PARTITION}_PROPERTIES contient une liste de valeurs de propriété. La syntaxe est {prop}={value} ou {prop}?={value}.

{prop}={value} est une attribution normale qui garantit que {prop} est défini sur {value}. Une seule attribution de ce type est possible par propriété.

{prop}?={value} est une attribution facultative. {prop} est défini sur {value} uniquement s'il n'y a pas d'attributions {prop}={value}. Si plusieurs attributions facultatives existent, la première l’emporte.

# 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 contient une liste de fichiers, qui est directement émise vers {partition}/build.prop. Chaque fichier contient une liste de paires {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

Pour en savoir plus, consultez build/make/core/sysprop.mk.

Étape 6: Accédez aux propriétés au moment de l'exécution

Les propriétés peuvent être lues et écrites au moment de l'exécution.

Scripts d'initialisation

Les fichiers de script d'initialisation (généralement des fichiers *.rc) peuvent lire une propriété par ${prop} ou ${prop:-default}, définir une action qui s'exécute chaque fois qu'une propriété devient une valeur spécifique et écrire les propriétés à l'aide de la commande 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}

Commandes shell getprop et setprop

Vous pouvez utiliser respectivement les commandes shell getprop ou setprop pour lire ou écrire les propriétés. Pour en savoir plus, appelez getprop --help ou setprop --help.

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

Sysprop comme API pour C++/Java/Rust

Avec sysprop comme API, vous pouvez définir des propriétés système et utiliser une API générée automatiquement, concrète et typée. Définir scope sur Public rend également les API générées disponibles pour les modules au-delà des limites et garantit la stabilité des API. Voici un exemple de fichier .sysprop, d'un module Android.bp et de code C++, Java et Rust utilisant ces éléments.

# 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);

Pour en savoir plus, consultez Implémenter des propriétés système en tant qu'API.

Fonctions et méthodes de propriétés de bas niveau C/C++, Java et Rust

Dans la mesure du possible, utilisez Sysprop comme API, même si des fonctions C/C++ ou Rust de bas niveau ou des méthodes Java de bas niveau sont à votre disposition.

libc, libbase et libcutils offrent des fonctions de propriétés système C++. libc dispose de l'API sous-jacente, tandis que les fonctions libbase et libcutils sont des wrappers. Si possible, utilisez les fonctions sysprop libbase. Ce sont les plus pratiques, et les binaires de l'hôte peuvent utiliser les fonctions libbase. Pour en savoir plus, consultez sys/system_properties.h (libc), android-base/properties.h (libbase) et cutils/properties.h (libcutils).

La classe android.os.SystemProperties propose des méthodes de propriétés système Java.

Le module rustutils::system_properties propose des fonctions et des types de propriétés système Rust.

Annexe: Ajouter des propriétés spécifiques au fournisseur

Les partenaires (y compris les Googleurs travaillant dans le contexte du développement de Pixel) souhaitent définir des propriétés système spécifiques au matériel (ou à un appareil). Les propriétés spécifiques aux fournisseurs sont des propriétés détenues par un partenaire et propres à leur propre matériel ou appareil, et non à la plate-forme. Comme elles dépendent du matériel ou de l'appareil, elles sont destinées à être utilisées dans les partitions /vendor ou /odm.

Depuis le projet Treble, les propriétés de la plate-forme et celles des fournisseurs ont été entièrement divisées afin d'éviter tout conflit. La section suivante explique comment définir les propriétés de fournisseur et indique celles qui doivent toujours être utilisées.

Espace de noms sur les noms de propriété et de contexte

Toutes les propriétés de fournisseur doivent commencer par l'un des préfixes suivants pour éviter toute collision entre elles et les propriétés d'autres partitions.

  • 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.

Notez que ro.hardware. est autorisé en tant que préfixe, mais uniquement pour des raisons de compatibilité. Ne l'utilisez pas pour les propriétés normales.

Les exemples suivants utilisent tous l'un des préfixes répertoriés ci-dessus:

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

Tous les contextes de propriété de fournisseur doivent commencer par vendor_. Cela permet également d'assurer la compatibilité. Voici des exemples:

  • vendor_radio_prop.
  • vendor_faceauth_prop.
  • vendor_usb_prop.

Il incombe au fournisseur de nommer et de gérer les propriétés. Par conséquent, suivez le format suggéré à l'étape 2, en plus des exigences du fournisseur concernant les espaces de noms.

Règles SEPolicy spécifiques au fournisseur et Property_contexts

Les propriétés de fournisseur peuvent être définies par la macro vendor_internal_prop. Placez les règles spécifiques au fournisseur que vous définissez dans le répertoire BOARD_VENDOR_SEPOLICY_DIRS. Par exemple, supposons que vous définissiez une propriété faceauth du fournisseur dans "corail".

Dans le fichier BoardConfig.mk (ou dans toute inclusion BoardConfig.mk), insérez ce qui suit:

BOARD_VENDOR_SEPOLICY_DIRS := device/google/coral-sepolicy

Dans le fichier device/google/coral-sepolicy/private/property.te, insérez le code suivant:

vendor_internal_prop(vendor_faceauth_prop)

Dans le fichier device/google/coral-sepolicy/private/property_contexts, insérez le code suivant:

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

Limites des propriétés de fournisseur

Étant donné que les partitions système et produit ne peuvent pas dépendre du fournisseur, n'autorisez jamais l'accès aux propriétés du fournisseur à partir des partitions system, system-ext ou product.

Annexe: Renommer les propriétés existantes

Lorsque vous devez abandonner une propriété et en déplacer une nouvelle, utilisez Sysprop en tant qu'API pour renommer vos propriétés existantes. Cela permet de maintenir la rétrocompatibilité en spécifiant à la fois l'ancien nom et le nouveau nom de la propriété. Plus précisément, vous pouvez définir l'ancien nom dans le champ legacy_prop_name du fichier .sysprop. L'API générée tente de lire prop_name et utilise legacy_prop_name si prop_name n'existe pas.

Par exemple, dans les étapes suivantes, awesome_feature_foo_enabled devient foo.awesome_feature.enabled.

Dans le fichier 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"
}

Dans le code 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);

Veuillez noter les mises en garde suivantes:

  • Tout d'abord, vous ne pouvez pas modifier le type de sysprop. Par exemple, vous ne pouvez pas transformer une propriété int en propriété string. Vous ne pouvez que modifier le nom.

  • Ensuite, seule l'API de lecture revient à l'ancien nom. L'API d'écriture ne fonctionne pas. Si sysprop est accessible en écriture, vous ne pouvez pas le renommer.