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, avec des instructions pour refactoriser les propriétés système existantes. Assurez-vous d'utiliser les directives lorsque vous refactorisez, sauf si vous rencontrez un problème de compatibilité important qui en impose autrement.

Étape 1 : Définition de 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’existe aucun contexte existant approprié, créez-en un nouveau. Le nom est utilisé lors de l'accès à la propriété ; le contexte de propriété est utilisé pour contrôler l'accessibilité en termes de SELinux. Les noms peuvent être n'importe quelle chaîne, mais AOSP vous recommande de suivre un format structuré pour les rendre clairs.

Nom de la propriété

Utilisez ce format avec le boîtier Snake_case :

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

Utilisez soit « » (omis), ro (pour les propriétés définies une seule fois) ou persist (pour les propriétés qui persistent après les redémarrages) pour le prefix d'élément.

Mises en garde

Utilisez ro uniquement lorsque vous êtes certain de ne plus avoir besoin prefix pour être accessible en écriture à l'avenir. ** Ne spécifiez pas le préfixe ro .** Au lieu de cela, comptez sur sepolicy pour rendre prefix lecture seule (en d'autres termes, accessible en écriture uniquement par init ).

Utilisez persist uniquement 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. (Pour plus de détails, reportez-vous à la section Préparation .)

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

Le terme group est utilisé pour regrouper les propriétés associées. Il s'agit d'un nom de sous-système similaire à celui 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 du système. Par exemple, pour les propriétés système auxquelles le processus vold a accès en écriture, il est courant d'utiliser vold (le nom du type de domaine du processus) comme nom de groupe.

Si nécessaire, ajoutez subgroup pour catégoriser davantage 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 groupes existants lorsque cela est possible, au lieu d'en créer de nouveaux. Le tableau suivant fournit des exemples de noms de groupes fréquemment utilisés.

Domaine Groupe (et sous-groupe)
lié au Bluetooth bluetooth
sysprops de la ligne de commande du noyau boot
sysprops qui identifient une build build
lié à la téléphonie telephony
lié à l'audio audio
graphiques liés graphics
lié au vol vold

Ce qui suit définit l'utilisation du 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 au sein d’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 comme audio.awesome_feature_enabled ou simplement audio.awesome_feature , renommez-le comme audio.awesome_feature.enabled pour refléter le type et l'intention de la propriété système.

Il n'y a pas de règle spécifique sur ce que doit être 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 : à utiliser si l'intention est de clarifier que la propriété système ne représente pas un état dynamique du système ; il représente 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 pour une valeur de délai d'attente en unités de ms.

Exemples:

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

Contexte de la propriété

Le nouveau schéma de contexte de propriété SELinux permet une granularité plus fine et des noms plus descriptifs. Semblable à celui utilisé pour les noms de propriété, AOSP recommande le format suivant :

{group}[_{subgroup}]*_prop

Les termes sont définis comme suit :

group et subgroup ont la même signification que celle définie pour l' exemple d'expression régulière précédent. Par exemple, vold_config_prop signifie des propriétés qui sont des configurations d'un fournisseur et destinées à être définies par vendor_init , tandis que vold_status_prop ou simplement vold_prop signifie des 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. En particulier, évitez les types de termes suivants :

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

Préférez les utilisations de noms telles que vold_config_prop à exported_vold_prop , ou vold_vendor_writable_prop , etc.

Taper

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

Taper Définition
Booléen true ou 1 pour vrai, false ou 0 pour faux
Entier entier signé de 64 bits
Entier non signé entier non signé de 64 bits
Double virgule flottante double précision
Chaîne toute chaîne UTF-8 valide
énumération les valeurs peuvent être n'importe quelle chaîne UTF-8 valide sans espaces
Liste 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 le type en le spécifiant en tant que fichier property_contexts . Pour plus d'informations, consultez property_contexts à l'étape 3 .

Étape 2 : Détermination des niveaux d’accessibilité requis

Il existe quatre macros d'assistance qui 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 /system , mais non écrites
system_vendor_config_prop Propriétés lues en dehors /system et écrites uniquement par vendor_init
system_public_prop Propriétés lues et écrites en dehors /system

Étendez l’accès aux propriétés du système aussi étroitement que possible. Dans le passé, un accès généralisé a entraîné des pannes 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 doit avoir un accès en lecture à cette propriété ?
  • Quel processus doit avoir 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 une portée d’accès appropriée.

Decision tree for determining the scope of access

Figure 1. Arbre de décision pour déterminer l'étendue de l'accès aux propriétés du système

Étape 3 : l'ajouter au système/sepolicy

Lors de l'accès à sysprop, SELinux contrôle l'accessibilité des processus. Après avoir déterminé le niveau d'accessibilité requis, définissez les contextes de propriété sous system/sepolicy , ainsi que des règles supplémentaires d'autorisation et de neverallow sur ce que les processus sont (et ne sont pas) autorisés à lire ou à écrire.

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. Ceci est 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)

Deuxièmement, 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 autant que possible ; public ne convient que si la macro set_prop ou get_prop affecte des domaines en dehors du 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 Neverallow pour réduire davantage l'accessibilité définie par la macro. Par exemple, supposons que vous ayez utilisé system_restricted_prop car les propriétés de votre système doivent être lues par les processus du fournisseur. Si l'accès en lecture n'est pas requis par tous les processus du fournisseur, mais uniquement par un certain ensemble de processus (tels 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 neverallow dans le fichier system/sepolicy/private/{domain}.te si la règle neverallow est liée à un domaine spécifique. Pour des règles Neverallow plus larges, utilisez des domaines généraux tels que ceux-ci lorsque cela est approprié :

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

Dans le fichier system/sepolicy/private/audio.te , placez ce qui suit :

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

Dans le fichier system/sepolicy/private/property.te , placez ce qui suit :

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 à l'exception vendor_init ».

Enfin, associez une propriété système au contexte de propriété. Cela garantit que l'accès accordé et les règles Neverallow 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 , un fichier qui décrit le mappage entre les propriétés système et les contextes de propriétés. Dans ce fichier, vous pouvez spécifier soit une propriété unique, soit un préfixe pour les propriétés à mapper dans un contexte.

Voici la syntaxe pour 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 la propriété, qui peut être l'un des suivants :

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

Assurez-vous que chaque entrée a son type désigné dans la mesure du possible, car type est appliqué lors de la définition 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 est prioritaire. Pour plus d'exemples, consultez system/sepolicy/private/property_contexts .

Étape 4 : Détermination des exigences de stabilité

La stabilité est un autre aspect des propriétés du système, et elle diffère de l'accessibilité. La stabilité concerne la question de savoir si une propriété système peut ou non être modifiée (par exemple renommée, voire supprimée) à l'avenir. Ceci est particulièrement important à mesure que le système d’exploitation Android devient modulaire. Avec Treble, les partitions système, fournisseur et produit peuvent être mises à jour indépendamment les unes des autres. Avec Mainline, certaines parties du système d'exploitation sont modularisées sous forme de modules pouvant être mis à jour (dans les APEX ou les APK).

Si une propriété système est destinée à être utilisée sur des logiciels pouvant être mis à jour, par exemple sur des partitions système et fournisseur, elle doit être stable. Cependant, s'il est utilisé uniquement dans, par exemple, un module Mainline spécifique, vous pouvez modifier son nom, son type ou ses contextes 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 les partenaires (ou configurée différemment par appareil) ? Si oui, il doit être stable.
  • Cette propriété système définie par AOSP est-elle destinée à être écrite ou lue à partir du code (et non du processus) qui existe dans des partitions non système comme vendor.img ou product.img ? Si oui, il doit être stable.
  • Cette propriété système est-elle accessible via les modules Mainline ou via un module Mainline et la partie non modifiable de la plate-forme ? Si oui, il doit être stable.

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

Étape 5 : Définition des propriétés au moment de la construction

Définissez les propriétés au moment de la construction avec les variables makefile. Techniquement, les valeurs sont intégrées à {partition}/build.prop . Ensuite, init lit {partition}/build.prop pour définir les propriétés. Il existe deux ensembles de ces variables : 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 affectation normale qui garantit que {prop} est défini sur {value} ; une seule affectation de ce type est possible par propriété.

{prop}?={value} est une affectation facultative ; {prop} est défini sur {value} uniquement s'il n'y a aucune affectation {prop}={value} . S'il existe plusieurs missions facultatives, 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 plus de détails, voir build/make/core/sysprop.mk .

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

Bien entendu, 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} , peuvent définir une action qui s'exécute chaque fois qu'une propriété devient une valeur spécifique et peuvent écrire les propriétés à l'aide de setprop commande.

# 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 plus de détails, 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 des API générées automatiquement qui sont concrètes et typées. La définition scope avec Public rend également les API générées disponibles pour les modules au-delà des frontières et garantit la stabilité des API. Voici un exemple d'un fichier .sysprop , d'un module Android.bp et du code C++, Java et Rust les utilisant.

# 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 plus d'informations, consultez Implémentation 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

Lorsque cela est 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 proposent des fonctions de propriétés système C++. libc possède l'API sous-jacente, tandis que les fonctions libbase et libcutils sont des wrappers. Si c'est possible, utilisez les fonctions sysprop libbase ; ce sont les plus pratiques et les binaires hôtes peuvent utiliser les fonctions libbase . Pour plus de détails, 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 : Ajout de 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 à l'appareil). Les propriétés spécifiques au fournisseur sont des propriétés appartenant au partenaire et propres à leur propre matériel ou appareil, et non à la plateforme. Comme ils dépendent du matériel ou du périphérique, ils sont destinés à être utilisés dans les partitions /vendor ou /odm .

Depuis Project Treble, les propriétés de la plateforme et celles des fournisseurs ont été complètement divisées pour éviter tout conflit. Ce qui suit décrit comment définir les propriétés du fournisseur et indique quelles propriétés du fournisseur doivent toujours être utilisées.

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

Toutes les propriétés du 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é comme préfixe, mais uniquement pour des raisons de compatibilité. Ne l'utilisez pas pour des 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és de fournisseur doivent commencer par vendor_ . C'est aussi pour des raisons de compatibilité. Voici des exemples :

  • vendor_radio_prop .
  • vendor_faceauth_prop .
  • vendor_usb_prop .

Il est de la responsabilité du fournisseur de nommer et de gérer les propriétés. Suivez donc le format suggéré à l'étape 2 , en plus des exigences en matière d'espaces de noms du fournisseur.

Règles SEPolicy et property_contexts spécifiques au fournisseur

Les propriétés du 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 de fournisseur dans Coral.

Dans le fichier BoardConfig.mk (ou dans n'importe quel BoardConfig.mk inclus), mettez ce qui suit :

BOARD_VENDOR_SEPOLICY_DIRS := device/google/coral-sepolicy

Dans le fichier device/google/coral-sepolicy/private/property.te , mettez ce qui suit :

vendor_internal_prop(vendor_faceauth_prop)

Dans le fichier device/google/coral-sepolicy/private/property_contexts , mettez ce qui suit :

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

Limites des propriétés du 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 des propriétés existantes

Lorsque vous devez rendre obsolète une propriété et passer à une nouvelle, utilisez Sysprop comme API pour renommer vos propriétés existantes. Cela maintient la compatibilité ascendante en spécifiant à la fois l’ancien nom et le nouveau nom de propriété. Plus précisément, vous pouvez définir le nom hérité via le champ legacy_prop_name dans le fichier .sysprop . L'API générée essaie de lire prop_name et utilise legacy_prop_name si prop_name n'existe pas.

Par exemple, les étapes suivantes renomment awesome_feature_foo_enabled en 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"
}

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

Notez les mises en garde suivantes :

  • Premièrement, vous ne pouvez pas modifier le type du sysprop. Par exemple, vous ne pouvez pas transformer un accessoire int en accessoire string . Vous ne pouvez changer que le nom.

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