本页将介绍用于在 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
文件并尽可能使用现有的群组名称,而不是创建新的群组名称。下表提供了常用群组名称的示例。
域 | 群组(和子群组) |
---|---|
蓝牙相关 | bluetooth |
来自内核 cmdline 的 sysprop | boot |
用于标识 build 的 sysprop | build
|
电话相关 | telephony |
音频相关 | audio |
图形相关 | graphics |
vold 相关 | vold |
下面定义了在前一正则表达式示例中 name
和 type
的用法。
[{prefix}.]{group}[.{subgroup}]*.{name}[.{type}]
name
用于标识群组中的系统属性。type
是一个可选元素,用于阐明系统属性的类型或 intent。例如,不要将 sysprop 命名为audio.awesome_feature_enabled
或仅命名为audio.awesome_feature
,而要将其重命名为audio.awesome_feature.enabled
,以反映系统属性类型和 intent。
对于类型并无具体的规则;以下是使用建议:
enabled
:如果类型是用于启用或停用功能的布尔值系统属性,请使用此类型。config
:如果 intent 是为了阐明系统属性不代表系统的动态状态,请使用此类型;它表示一个预配置的值(例如只读对象)。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 表示 true,false 或 0 表示 false |
整数 | 64 位有符号整数 |
无符号整数 | 64 位无符号整数 |
双精度 | 双精度浮点数 |
字符串 | 任何有效的 UTF-8 字符串 |
枚举 | 值可以是任何不含空格的有效 UTF-8 字符串 |
上述类型值的列表 | 使用英文逗号 (, ) 作为分隔符整数列表 [1, 2, 3] 被存储为 1,2,3 |
在内部,所有属性都存储为字符串。您可以通过在 property_contexts
文件中指定属性类型来强制执行属性类型。如需了解详情,请参阅第 3 步中的 property_contexts
。
第 2 步:确定所需的可访问性级别
以下是定义属性的 4 个辅助宏。
可访问性类型 | 含义 |
---|---|
system_internal_prop |
仅在 /system 中使用的属性 |
system_restricted_prop |
从 /system 外部读取而不写入的属性 |
system_vendor_config_prop |
从 /system 外部读取且仅由 vendor_init 写入的属性 |
system_public_prop |
从 /system 外部读取和写入的属性 |
请尽可能缩小对系统属性的访问范围。在过去,广泛访问会导致应用中断和安全漏洞。确定范围时,请考虑以下问题:
- 是否需要保留此系统属性?(如果是,原因是什么?)
- 哪些进程应具有该属性的读取权限?
- 哪些进程应具有该属性的写入权限?
以上述问题和如下决策树为工具,来确定适当的访问范围。
图 1. 用于确定系统属性访问范围的决策树
第 3 步:添加到 system/sepolicy
访问 sysprop 时,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 规则。如需获得更广泛的 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.img
或product.img
等非系统分区中的代码(而非进程)或从中读取数据?如果是,就必须保持稳定。 - 此系统属性是否跨多个或单个 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。使用 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
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
分区中使用。
从 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
prop 改为string
prop。您只能更改名称。其次,只有读取 API 才能回退到旧属性名称。写入 API 不会回退。如果 sysprop 是可写的,您无法对其重命名。