本頁面提供了在 Android 中新增或定義系統屬性的規範方法,以及重構現有系統屬性的指南。確保在重構時使用這些準則,除非您有強烈的相容性問題,否則需要這樣做。
第 1 步:定義系統屬性
新增系統屬性時,確定該屬性的名稱,並將其與 SELinux 屬性上下文關聯。如果沒有合適的現有上下文,請建立一個新上下文。存取該屬性時使用該名稱;屬性上下文用於控制 SELinux 的可存取性。名稱可以是任何字串,但 AOSP 建議您遵循結構化格式以使它們清晰。
物業名稱
使用帶有 Snake_case 大小寫的格式:
[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}]
使用「」(省略)、 ro
(對於僅設定一次的屬性)或persist
(對於在重新啟動後仍然存在的屬性)作為元素prefix
。
注意事項
僅當您確定將來不需要prefix
時才使用ro
。 ** 不要指定ro
前綴。** 相反,依靠 sepolicy 使prefix
只讀(換句話說,只能由init
寫入)。
只有當您確定該值必須在重新啟動後保留,並且使用系統屬性是您唯一的選擇時,才使用persist
。 (詳情請參閱準備部分。)
Google 嚴格審查具有ro
或persist
屬性的系統屬性。
術語group
用於聚合相關屬性。它旨在成為一個類似於audio
或telephony
子系統名稱。不要使用不明確或過多的術語,例如sys
、 system
、 dev
、 default
或config
。
通常的做法是使用對系統屬性具有獨佔讀取或寫入存取權限的程序的網域類型名稱。例如,對於vold
進程具有寫入存取權限的系統屬性,通常使用vold
(進程的域類型的名稱)作為群組名稱。
如果需要,請新增subgroup
以進一步對屬性進行分類,但避免使用不明確或過多的術語來描述此元素。 (您也可以有多個子subgroup
。)
許多群組名稱已經定義。檢查system/sepolicy/private/property_contexts
檔案並盡可能使用現有群組名稱,而不是建立新名稱。下表提供了常用群組名稱的範例。
領域 | 組(和子組) |
---|---|
藍牙相關 | bluetooth |
來自核心命令列的 sysprops | boot |
標識建置的 sysprops | build |
電話相關 | telephony |
音訊相關 | audio |
圖形相關 | graphics |
卷相關 | vold |
下面定義了前面的正規表示式範例中name
和type
的使用。
[{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
術語定義如下:
group
和subgroup
意思與前面的範例 regex的定義相同。例如, vold_config_prop
表示來自供應商的配置並由vendor_init
設定的屬性,而vold_status_prop
或只是vold_prop
表示要公開vold
當前狀態的屬性。
命名屬性上下文時,選擇反映屬性一般用法的名稱。特別要避免使用以下類型的術語:
- 看起來過於籠統和模糊的術語,例如
sys
、system
、default
。 - 直接編碼可訪問性的術語:例如
exported
、apponly
、ro
、public
、private
。
優先使用vold_config_prop
等名稱用法,而不是exported_vold_prop
或vold_vendor_writable_prop
等。
類型
屬性類型可以是表中列出的下列類型之一。
類型 | 定義 |
---|---|
布林值 | true 或1 表示 true, false 或0 表示 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 外部讀寫的屬性 |
盡可能縮小對系統屬性的存取範圍。過去,廣泛的存取導致了應用程式損壞和安全漏洞。確定範圍時請考慮以下問題:
- 這個系統屬性需要保留嗎? (如果是這樣,為什麼?)
- 哪個程序應該具有對此屬性的讀取權限?
- 哪個進程應該具有對此屬性的寫入存取權限?
使用前面的問題和下面的決策樹作為確定適當存取範圍的工具。
圖 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)
其次,授予對屬性上下文的讀取和(或)寫入存取權限。在system/sepolicy/public/{domain}.te
或system/sepolicy/private/{domain}.te
檔案中使用set_prop
和get_prop
巨集授予存取權限。盡可能使用private
;只有當set_prop
或get_prop
巨集影響核心域以外的任何網域時, public
才適用。
例如,在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 中)。
如果系統屬性用於跨可更新的軟體片段(例如跨系統和供應商分區),則它必須穩定。但是,如果它僅在特定的主線模組中使用,您可以變更其名稱、類型或屬性上下文,甚至將其刪除。
詢問以下問題以確定係統屬性的穩定性:
- 此系統屬性是否打算由合作夥伴配置(或每個設備進行不同的配置)?如果是的話,應該是穩定的。
- 這個 AOSP 定義的系統屬性是否旨在寫入或讀取存在於非系統分區(如
vendor.img
或product.img
中的程式碼(而非進程)?如果是的話,應該是穩定的。 - 是否可以跨 Mainline 模組或跨 Mainline 模組和平台的不可更新部分存取此系統屬性?如果是的話,應該是穩定的。
對於穩定的系統屬性,將每個屬性正式定義為 API 並使用 API 存取系統屬性,如步驟 6中所述。
第 5 步:在建置時設定屬性
在建置時使用 makefile 變數設定屬性。從技術上講,這些值被烘焙到{partition}/build.prop
。然後init
讀取{partition}/build.prop
來設定屬性。有兩組此類變數: PRODUCT_{PARTITION}_PROPERTIES
和TARGET_{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 指令
您可以分別使用getprop
或setprop
shell 指令來讀取或寫入屬性。有關更多詳細信息,請調用getprop --help
或setprop --help
。
$ adb shell getprop ro.vndk.version
$
$ adb shell setprop security.perf_harden 0
Sysprop 作為 C++/Java/Rust 的 API
使用 sysprop 作為 API,您可以定義系統屬性並使用自動產生的具體且類型化的 API。使用Public
設定scope
還可以使產生的 API 可供跨邊界的模組使用,並確保 API 的穩定性。以下是.sysprop
檔案、 Android.bp
模組以及使用它們的 C++、Java 和 Rust 程式碼的範例。
# 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);
有關更多信息,請參閱將系統屬性實作為 API 。
C/C++、Java 和 Rust 低階屬性函式和方法
如果可能,請使用 Sysprop 作為 API,即使您可以使用低階 C/C++ 或 Rust 函數或低階 Java 方法。
libc
、 libbase
和libcutils
提供 C++ 系統屬性函式。 libc
具有底層 API,而libbase
和libcutils
函數是包裝器。如果可能的話,使用libbase
sysprop 函數;它們是最方便的,主機二進位檔案可以使用libbase
函數。有關更多詳細信息,請參閱sys/system_properties.h
( libc
)、 android-base/properties.h
( libbase
) 和cutils/properties.h
( libcutils
)。
android.os.SystemProperties
類別提供 Java 系統屬性方法。
rustutils::system_properties
模組提供 Rust 系統屬性函數和類型。
附錄:新增特定於供應商的屬性
合作夥伴(包括從事 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
宏定義。將您定義的供應商特定規則放入BOARD_VENDOR_SEPOLICY_DIRS
目錄中。例如,假設您正在 Coral 中定義供應商 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
供應商屬性的限制
由於系統和產品分區不能依賴供應商,因此切勿允許從system
、 system-ext
或product
分區存取供應商屬性。
附錄:重命名現有屬性
當您必須棄用某個屬性並移至新屬性時,請使用Sysprop 作為 API來重新命名現有屬性。這透過指定舊名稱和新屬性名稱來保持向後相容性。具體來說,您可以透過.sysprop
檔案中的legacy_prop_name
欄位設定舊名稱。產生的 API 嘗試讀取prop_name
,如果prop_name
不存在,則使用legacy_prop_name
。
例如,以下步驟將awesome_feature_foo_enabled
重新命名為foo.awesome_feature.enabled
。
在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"
}
在 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 是可寫的,則無法重新命名它。