本页提供了在 Android 中添加或定义系统属性的规范方法,以及用于重构现有系统属性的指南。除非您遇到必须另行处理的严重兼容性问题,否则请务必在重构时使用这些指南。
第 1 步:定义系统属性
添加系统属性时,请确定该属性的名称,并将其与 SELinux 属性上下文相关联。如果没有合适的现有上下文,请创建一个新的上下文。该名称在访问属性时使用;属性上下文用于控制 SELinux 方面的可访问性。名称可以是任何字符串,但 AOSP 建议您遵循结构化格式,使其清晰明了。
属性名称
将此格式与 snake_case 大小写结合使用
[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}]
对于 prefix
元素,请使用 ""(省略)、ro
(对于仅设置一次的属性)或 persist
(对于跨重启持久存在的属性)。
注意事项
仅当您确定将来不需要 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
文件,并在可能的情况下使用现有的组名称,而不是创建新的组名称。下表提供了常用组名称的示例。
域 | 组(和子组) |
---|---|
蓝牙相关 | 蓝牙 |
来自内核 cmdline 的系统属性 | 启动 |
标识构建版本的系统属性 | 构建
|
电话相关 | 电话 |
音频相关 | 音频 |
图形相关 | 图形 |
vold 相关 | vold |
以下定义了先前 正则表达式示例 中 name
和 type
的用法。
[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}]
name
标识组内的系统属性。type
是一个可选元素,用于阐明系统属性的类型或意图。例如,与其将系统属性命名为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
的含义与先前 示例正则表达式 中定义的含义相同。例如,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 表示真,false 或 0 表示假 |
整数 | 带符号 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:添加到 system/sepolicy
当访问系统属性时,SELinux 控制进程的可访问性。在您确定所需的可访问性级别后,在 system/sepolicy
下定义属性上下文,以及关于进程允许(和不允许)读取或写入的附加 allow 和 neverallow 规则。
首先,在 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 规则绑定到特定域,则将其放置在 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 [可能值的列表...]
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 OS 变得模块化,这一点尤为重要。借助 Treble,系统、供应商和产品分区可以彼此独立地更新。借助 Mainline,OS 的某些部分被模块化为可更新模块(在 APEX 或 APK 中)。
如果系统属性用于跨可更新软件组件(例如跨系统分区和供应商分区),则它必须是稳定的。但是,如果它仅在特定 Mainline 模块内使用,例如,您可以更改其名称、类型或属性上下文,甚至删除它。
询问以下问题以确定系统属性的稳定性
- 此系统属性是否旨在由合作伙伴配置(或按设备进行不同配置)?如果是,则它必须是稳定的。
- 此 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:在运行时访问属性
可以在运行时读取和写入属性。
Init 脚本
Init 脚本文件(通常为 *.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,这些 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 底层属性函数和方法
在可能的情况下,即使您可以使用底层 C/C++ 或 Rust 函数或底层 Java 方法,也请使用 Sysprop 作为 API。
libc
、libbase
和 libcutils
提供 C++ 系统属性函数。libc
具有底层 API,而 libbase
和 libcutils
函数是包装器。如果可能,请使用 libbase
系统属性函数;它们是最方便的,并且主机二进制文件可以使用 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 中定义供应商面部识别属性。
在 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 是可写的属性,则无法重命名它。