添加系統屬性

此頁面提供了在 Android 中添加或定義系統屬性的規範方法,以及重構現有系統屬性的指南。確保您在重構時使用這些指南,除非您有一個強烈的兼容性問題,否則會另行規定。

背景

系統屬性用於各種目的,因為它們易於使用。儘管它們類似於編程語言中的全局變量,但在創建它們時沒有一組正式的流程要求或命名約定供您遵循。因此,系統屬性可能難以理解和維護。此頁面上的步驟定義系統屬性,並提供添加和維護它們的步驟。

準備:考慮替代方案

確定係統屬性是否真的是您想要的,以及它是否是您唯一的選擇。系統屬性是提供某些優勢的系統範圍的資源。它們易於使用,並且它們的非動態屬性具有相對較低的性能開銷。使用系統屬性時,即使系統屬性在多個進程之間共享,也不需要使用進程間通信 (IPC)。但是,它們在濫用時也可能是有害的,類似於全局變量。濫用系統屬性導致用戶無法訪問應用程序以及引入安全漏洞等問題。

考慮以下系統屬性的許多替代方案,以評估其中一個是否可以為您提供所需的解決方案。

共享偏好

對於應用程序本地的持久配置,請使用Shared Preferences界面,而不是系統屬性。

哈爾

當配置的真實來源來自設備上的硬件組件時,HAL 必須提供該組件的信息。不要將 HAL 配置為寫入系統屬性以讓其他進程讀取它(反之亦然)。相反,定義一個新的 HAL 方法來訪問配置。換句話說,不要將系統屬性用作 HAL 的側信道通信機制。

請注意,對於此用例,您不需要全新的 HAL,因為這是一個昂貴的選擇。僅當您有現有的 HAL 來抽象硬件組件時才使用此建議。

配置文件

當配置數據是靜態但複雜(即結構化)時,考慮使用 XML 或其他此類格式的配置數據。確保文件架構保持穩定。對於 XML 文件,您可以使用xsd_config來保持模式穩定,並利用自動生成的 XML 解析器。

活頁夾服務

系統屬性通常用於與多個進程共享子系統的動態狀態。這樣的狀態可以在 binder 服務(例如對象字段)中實現,其他進程可以使用對服務的 binder 調用來讀取(甚至修改)狀態。這是首選的解決方案,因為將狀態設為系統屬性並授予對它的直接訪問權限需要一個類似於此的過程:

  1. 對狀態執行健全性檢查。
  2. 對狀態執行後處理以使其可使用。
  3. 執行細粒度的訪問控制功能。
  4. 保持狀態結構化。

當您將狀態實現為系統屬性時,成功完成此過程要么是不可能的,要么充其量是非常困難的。因此,當該州的讀者已經可以訪問活頁夾服務時,請使用活頁夾服務選項。

步驟 1:定義系統屬性

添加系統屬性時,請確定屬性的名稱,並將其與 SELinux 屬性上下文相關聯。如果沒有合適的現有上下文,請創建一個新上下文。訪問屬性時使用該名稱;屬性上下文用於控制 SELinux 的可訪問性。名稱可以是任何字符串,但 AOSP 建議您遵循結構化格式以使其清晰。

屬性名稱

將此格式與 snake_case 大小寫一起使用:

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

對元素prefix使用“”(省略)、 ro (用於僅設置一次的屬性)或persist (用於在重新啟動後持續存在的屬性)。

注意事項

僅當您確定將來不需要prefix可寫時才使用ro 。 ** 不要指定ro前綴。** 相反,依靠 sepolicy 使prefix只讀(換句話說,只能由init寫入)。

僅當您確定必須在重新啟動後保留該值並且使用系統屬性是您唯一的選擇時才使用persist 。 (有關詳細信息,請參閱準備部分。)

Google 嚴格審查具有ropersist屬性的系統屬性。

術語group用於聚合相關屬性。它的目的是作為一個子系統名稱,在使用中類似於audiotelephony不要使用模棱兩可或重載的術語,例如syssystemdevdefaultconfig

通常的做法是使用對系統屬性具有獨占讀取或寫入訪問權限的進程的域類型名稱。例如,對於vold進程具有寫入權限的系統屬性,通常使用vold (進程的域類型名稱)作為組名。

如果需要,添加subgroup以進一步對屬性進行分類,但避免使用模棱兩可或重載的術語來描述此元素。 (您也可以有多subgroup 。)

許多組名已被定義。檢查system/sepolicy/private/property_contexts文件並儘可能使用現有組名,而不是創建新組名。下表提供了常用組名的示例。

領域組(和子組)
藍牙相關bluetooth
來自內核命令行的 sysprops boot
標識構建的 sysprops build
電話相關telephony
音頻相關audio
圖形相關graphics
卷相關vold

下面定義了前面正則表達式示例nametype的使用。

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

  • name標識組內的系統屬性。

  • type是一個可選元素,用於闡明系統屬性的類型或意圖。例如,不要將 sysprop 命名為audio.awesome_feature_enabled或僅命名為audio.awesome_feature ,而是將其重命名為audio.awesome_feature.enabled以反映系統屬性類型和意圖。

關於類型必須是什麼沒有具體規則。這些是使用建議:

  • enabled :如果類型是用於打開或關閉功能的布爾系統屬性,則使用。
  • config :如果目的是澄清系統屬性不代表系統的動態狀態,則使用;它代表一個預配置的值(例如,一個只讀的東西)。
  • List :如果它是一個值為列表的系統屬性,則使用它。
  • Timeoutmillis :如果它是超時值的系統屬性,則以毫秒為單位使用。

例子:

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

屬性上下文

新的 SELinux 屬性上下文方案允許更精細的粒度和更具描述性的名稱。與用於屬性名稱的內容類似,AOSP 建議使用以下格式:

{group}[_{subgroup}]*_prop

術語定義如下:

groupsubgroup具有與前面示例 regex定義的含義相同的含義。例如, vold_config_prop表示來自供應商的配置並由vendor_init設置的屬性,而vold_status_prop或只是vold_prop表示將公開vold的當前狀態的屬性。

命名屬性上下文時,選擇反映屬性一般用途的名稱。尤其要避免以下類型的術語:

  • 看起來過於籠統和模棱兩可的術語,例如syssystemdefault
  • 直接編碼可訪問性的術語:例如exportedapponlyropublicprivate

喜歡使用諸如vold_config_prop exported_vold_prop vold_vendor_writable_prop等。

類型

屬性類型可以是表中列出的以下之一。

類型定義
布爾值true1表示 true, false0表示 false
整數有符號 64 位整數
無符號整數無符號 64 位整數
雙倍的雙精度浮點
細繩任何有效的 UTF-8 字符串
枚舉值可以是任何沒有空格的有效 UTF-8 字符串
以上列表逗號 ( , ) 用作分隔符
整數列表[1, 2, 3]存儲為1,2,3

在內部,所有屬性都存儲為字符串。您可以通過將類型指定為property_contexts文件來強制執行該類型。有關更多信息,請參閱步驟 3中的property_contexts

第 2 步:確定所需的可訪問性級別

有四個定義屬性的輔助宏。

輔助功能類型意義
system_internal_prop僅在/system中使用的屬性
system_restricted_prop/system外部讀取但未寫入的屬性
system_vendor_config_prop/system外部讀取並且僅由vendor_init寫入的屬性
system_public_prop/system外部讀寫的屬性

盡可能縮小對系統屬性的訪問範圍。過去,廣泛的訪問會導致應用程序損壞和安全漏洞。在確定範圍時考慮以下問題:

  • 這個系統屬性是否需要持久化? (如果是,為什麼?)
  • 哪個進程應該對該屬性具有讀取權限?
  • 哪個進程應該對此屬性有寫訪問權?

使用前面的問題和下面的決策樹作為確定適當訪問範圍的工具。

Decision tree for determining the scope of access

圖 1.確定係統屬性訪問範圍的決策樹

第 3 步:將其添加到系統/sepolicy

訪問 sysprop 時,SELinux 控制進程的可訪問性。在確定需要什麼級別的可訪問性之後,在system/sepolicy下定義屬性上下文,以及關於允許(和不允許)哪些進程讀取或寫入的附加允許禁止規則。

首先,在system/sepolicy/public/property.te文件中定義屬性上下文。如果屬性是系統內部的,請在system/sepolicy/private/property.te文件中定義它。使用提供系統屬性所需的可訪問性的system_[accessibility]_prop([context])宏之一。這是system/sepolicy/public/property.te文件的示例:

system_public_prop(audio_foo_prop)
system_vendor_config_prop(audio_bar_prop)

system/sepolicy/private/property.te文件中添加的示例:

system_internal_prop(audio_baz_prop)

其次,授予對屬性上下文的讀取和(或)寫入訪問權限。使用set_propget_prop宏在system/sepolicy/public/{domain}.tesystem/sepolicy/private/{domain}.te文件中授予訪問權限。盡可能使用privatepublic僅當set_propget_prop宏影響核心域之外的任何域時才適用。

例如,在system/sepolicy/private/audio.te文件中:

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

例如,在system/sepolicy/public/domain.te文件中:

get_prop(domain, audio_bar_prop)

第三,添加一些 neverallow 規則以進一步減少宏範圍內的可訪問性。例如,假設您使用了system_restricted_prop ,因為您的系統屬性必須由供應商進程讀取。如果不是所有供應商進程都需要讀取訪問權限,並且僅特定一組進程(例如vendor_init )需要,則禁止不需要讀取訪問權限的供應商進程。

使用以下語法來限制寫入和讀取訪問:

限制寫訪問:

neverallow [domain] [context]:property_service set;

要限制讀取訪問:

neverallow [domain] [context]:file no_rw_file_perms;

如果 neverallow 規則綁定到特定域,則將 neverallow 規則放在system/sepolicy/private/{domain}.te文件中。對於更廣泛的 neverallow 規則,請在適當的地方使用諸如這些的通用域:

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

system/sepolicy/private/audio.te文件中,放置以下內容:

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

system/sepolicy/private/property.te文件中,放置以下內容:

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

請注意, {domain -coredomain}捕獲所有供應商進程。所以{domain -coredomain -vendor_init}表示“vendor_init之外的所有供應商進程”。

最後,將系統屬性與屬性上下文相關聯。這可確保將授予的訪問權限和應用於屬性上下文的 neverallow 規則應用於實際屬性。為此,向property_contexts文件添加一個條目,該文件描述系統屬性和屬性上下文之間的映射。在此文件中,您可以指定單個屬性或要映射到上下文的屬性的前綴。

這是映射單個屬性的語法:

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

這是映射前綴的語法:

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

您可以選擇指定屬性的類型,可以是以下之一:

  • bool
  • int
  • uint
  • double
  • enum [list of possible values...]
  • string (將string用於列表屬性。)

盡可能確保每個條目都有其指定的類型,因為在設置property時強制執行type 。以下示例顯示瞭如何編寫映射:

# 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

當精確條目和前綴條目衝突時,精確條目優先。有關更多示例,請參閱system/sepolicy/private/property_contexts

第 4 步:確定穩定性要求

穩定性是系統屬性的另一個方面,它不同於可訪問性。穩定性是關於將來是否可以更改系統屬性(例如重命名,甚至刪除)。隨著 Android 操作系統變得模塊化,這一點尤其重要。使用 Treble,系統、供應商和產品分區可以相互獨立地更新。使用 Mainline,操作系統的某些部分被模塊化為可更新模塊(在 APEX 或 APK 中)。

如果系統屬性用於跨可更新的軟件片段,例如跨系統和供應商分區,它必須是穩定的。但是,如果它僅在特定 Mainline 模塊中使用,您可以更改其名稱、類型或屬性上下文,甚至將其刪除。

詢問以下問題以確定係統屬性的穩定性:

  • 此系統屬性是否打算由合作夥伴配置(或每個設備的配置不同)?如果是,它必須是穩定的。
  • 此 AOSP 定義的系統屬性是否旨在寫入或讀取存在於vendor.imgproduct.img等非系統分區中的代碼(而非進程)?如果是,它必須是穩定的。
  • 此系統屬性是跨 Mainline 模塊還是跨 Mainline 模塊和平台的不可更新部分訪問?如果是,它必須是穩定的。

對於穩定的系統屬性,將每個屬性正式定義為 API,並使用 API 訪問系統屬性,如步驟 6中所述。

第 5 步:在構建時設置屬性

在構建時使用 makefile 變量設置屬性。從技術上講,這些值被烘焙到{partition}/build.prop 。然後init讀取{partition}/build.prop來設置屬性。有兩組這樣的變量: PRODUCT_{PARTITION}_PROPERTIESTARGET_{PARTITION}_PROP

PRODUCT_{PARTITION}_PROPERTIES包含屬性值列表。語法是{prop}={value}{prop}?={value}

{prop}={value}是確保{prop}設置為{value}的正常分配;每個屬性只能有一個這樣的分配。

{prop}?={value}是一個可選的賦值;僅當沒有任何{prop}={value}分配時,{ {prop}才設置為{value} }。如果存在多個可選分配,則第一個獲勝。

# 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包含文件列表,直接發送到{partition}/build.prop 。每個文件都包含一個{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

有關更多詳細信息,請參閱build/make/core/sysprop.mk

第 6 步:在運行時訪問屬性

當然,可以在運行時讀取和寫入屬性。

初始化腳本

初始化腳本文件(通常是 *.rc 文件)可以通過${prop}${prop:-default}讀取屬性,可以設置在屬性變為特定值時運行的操作,並且可以使用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}

getprop 和 setprop shell 命令

您可以分別使用getpropsetprop shell 命令來讀取或寫入屬性。有關更多詳細信息,請調用getprop --helpsetprop --help

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

Sysprop 作為 C++/Java 的 API

使用 sysprop 作為 API,您可以定義系統屬性並使用自動生成的具體和類型化的 API。使用Public設置scope還可以使生成的 API 跨邊界供模塊使用,並確保 API 穩定性。這是.sysprop文件、 Android.bp模塊以及使用它們的 C++ 和 Java 代碼的示例。

# 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",
}

// Both java and cc module can link against sysprop_library
java_library {
    static_libs: ["AudioProps"],
    …
}

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

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

有關更多信息,請參閱將系統屬性實現為 API

C/C++ 和 Java 低級屬性函數和方法

使用 Sysprop 作為 API,即使您可以使用低級 C/C++ 函數或低級 Java 方法。盡可能優先使用 Sysprop。

libclibbaselibcutils提供 C++ 系統屬性函數。 libc具有底層 API,而libbaselibcutils函數是包裝器。如果可能,請使用libbase sysprop 函數;它們是最方便的,宿主二進製文件可以使用libbase函數。有關更多詳細信息,請參閱sys/system_properties.h ( libc )、 android-base/properties.h ( libbase ) 和cutils/properties.h ( libcutils )。

android.os.SystemProperties類提供 Java 系統屬性方法。

附錄:添加特定於供應商的屬性

合作夥伴(包括從事 Pixel 開發工作的 Google 員工)希望定義特定於硬件(或特定於設備)的系統屬性。供應商特定屬性是合作夥伴擁有的屬性,它們是他們自己的硬件或設備獨有的,而不是平台獨有的。由於這些依賴於硬件或設備,因此它們應在/vendor/odm分區中使用。

自 Project Treble 以來,平台屬性和供應商屬性已完全分離,以防止它們發生衝突。下面介紹如何定義供應商屬性,並說明必須始終使用哪些供應商屬性。

屬性和上下文名稱的命名空間

所有供應商屬性必須以以下前綴之一開頭,以防止它們與其他分區的屬性發生衝突。

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

注意ro.hardware.允許作為前綴,但僅用於兼容性。不要將其用於普通屬性。

以下示例都使用前面列出的前綴之一:

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

所有供應商屬性上下文必須以vendor_ 。這也是為了兼容性。以下是示例:

  • vendor_radio_prop
  • vendor_faceauth_prop
  • vendor_usb_prop

命名和維護屬性是供應商的責任,因此除了供應商命名空間要求外,還應遵循步驟 2中建議的格式。

供應商特定的 SEPolicy 規則和 property_contexts

與平台屬性類似,供應商屬性可以由以下宏之一定義。

輔助功能類型意義
vendor_internal_prop僅在/vendor中使用的屬性
vendor_restricted_prop/vendor外部讀取但未寫入的屬性
vendor_public_prop/vendor外部讀寫的屬性

將您定義的供應商特定規則放在BOARD_VENDOR_SEPOLICY_DIRS目錄中。例如,假設您正在珊瑚中定義供應商 faceauth 屬性。

BoardConfig.mk文件(或任何BoardConfig.mk包含)中,輸入以下內容:

BOARD_VENDOR_SEPOLICY_DIRS := device/google/coral-sepolicy

device/google/coral-sepolicy/private/property.te文件中,輸入以下內容:

vendor_internal_prop(vendor_faceauth_prop)

device/google/coral-sepolicy/private/property_contexts文件中,輸入以下內容:

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

供應商屬性的限制

因為系統和產品分區不能依賴於供應商,所以絕不允許從systemsystem-extproduct分區訪問供應商屬性。

附錄:重命名現有屬性

當您必須棄用某個屬性並遷移到新屬性時,請使用Sysprop 作為 API來重命名現有屬性。這通過同時指定舊名稱和新屬性名稱來保持向後兼容性。具體來說,您可以通過.sysprop文件中的legacy_prop_name字段設置舊名稱。生成的 API 嘗試讀取prop_name ,如果prop_name不存在,則使用legacy_prop_name

例如,以下步驟將awesome_feature_foo_enabled重命名為foo.awesome_feature.enabled

foo.sysprop文件中(在 Java 中)

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"
}

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

請注意以下警告:

  • 首先,您不能更改 sysprop 的類型。例如,您不能將int道具轉換為string道具。您只能更改名稱。

  • 其次,只有讀取 API 回退到舊名稱。寫入 API 不會回退。如果 sysprop 是可寫的,則不能重命名它。