此页面提供了在 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 |
来自内核命令行的 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)
其次,授予对属性上下文的读取和(或)写入访问权限。使用set_prop
和get_prop
宏在system/sepolicy/public/{domain}.te
或system/sepolicy/private/{domain}.te
文件中授予访问权限。尽可能使用private
; public
仅当set_prop
或get_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.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 的 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。
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 系统属性方法。
附录:添加特定于供应商的属性
合作伙伴(包括从事 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
供应商属性的限制
因为系统和产品分区不能依赖于供应商,所以绝不允许从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
文件中(在 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 是可写的,则不能重命名它。