目录
Android系统属性
1.属性在哪里?
2.属性长什么样?
3.如何读写属性:
4.属性的作用
属性文件生成过程
如何添加系统属性
1.添加系统属性到 /system/build.prop
2.添加系统属性到 /vendor/build.prop
3.添加系统属性到 /product/build.prop
Android系统属性
- 属性文件生成过程分析
- 如何添加系统属性
- 属性与 Selinux
- 属性系统整体框架与启动过程分析
- 属性读写过程源码分析
在 Android 系统中,为统一管理系统的属性,设计了一个统一的属性系统,每个属性都是一个 key-value 对。 我们可以通过 shell 命令,Native 函数接口,Java 函数接口的方式来读写这些 key-vaule 对。
1.属性在哪里?
init 进程在启动会去加载后缀为 .prop 的属性文件, 将属性文件中的属性加载到共享内存中, 这样系统就有了默认的一些属性。
属性文件都在哪里呢?
属性文件的后缀绝大部分都是 prop,我们可以在 Android 模拟器的 shell 环境下搜索:
find . -name "*.prop"
/default.prop
/data/local.prop
/system/build.prop
/system/product/build.prop
/vendor/build.prop
/vendor/odm/etc/build.prop
/vendor/default.prop
/data/property/
我们看看 /default.prop
属性文件的内容:
cat /default.prop
#
# ADDITIONAL_DEFAULT_PROPERTIES
#
ro.actionable_compatible_property.enabled=true
ro.postinstall.fstab.prefix=/system
ro.secure=0
ro.allow.mock.location=1
ro.debuggable=1
debug.atrace.tags.enableflags=0
dalvik.vm.image-dex2oat-Xms=64m
dalvik.vm.image-dex2oat-Xmx=64m
dalvik.vm.dex2oat-Xms=64m
dalvik.vm.dex2oat-Xmx=512m
dalvik.vm.usejit=true
dalvik.vm.usejitprofiles=true
dalvik.vm.dexopt.secondary=true
dalvik.vm.appimageformat=lz4
ro.dalvik.vm.native.bridge=0
pm.dexopt.first-boot=extract
pm.dexopt.boot=extract
pm.dexopt.install=speed-profile
pm.dexopt.bg-dexopt=speed-profile
pm.dexopt.ab-ota=speed-profile
pm.dexopt.inactive=verify
pm.dexopt.shared=speed
dalvik.vm.dex2oat-resolve-startup-strings=true
dalvik.vm.dex2oat-max-image-block-size=524288
dalvik.vm.minidebuginfo=true
dalvik.vm.dex2oat-minidebuginfo=true
ro.iorapd.enable=false
tombstoned.max_tombstone_count=50
persist.traced.enable=1
ro.com.google.locationfeatures=1
ro.setupwizard.mode=DISABLED
persist.sys.usb.config=adb
可以看出属性确实是一些 key-value 对。
init 进程会调用 property_load_boot_defaults
函数来加载属性文件:
void property_load_boot_defaults(bool load_debug_prop) {
// TODO(b/117892318): merge prop.default and build.prop files into one
// We read the properties and their values into a map, in order to always allow properties
// loaded in the later property files to override the properties in loaded in the earlier
// property files, regardless of if they are "ro." properties or not.
std::map<std::string, std::string> properties;
if (!load_properties_from_file("/system/etc/prop.default", nullptr, &properties)) {
// Try recovery path
if (!load_properties_from_file("/prop.default", nullptr, &properties)) {
// Try legacy path
load_properties_from_file("/default.prop", nullptr, &properties);
}
}
load_properties_from_file("/system/build.prop", nullptr, &properties);
load_properties_from_file("/vendor/default.prop", nullptr, &properties);
load_properties_from_file("/vendor/build.prop", nullptr, &properties);
if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_Q__) {
load_properties_from_file("/odm/etc/build.prop", nullptr, &properties);
} else {
load_properties_from_file("/odm/default.prop", nullptr, &properties);
load_properties_from_file("/odm/build.prop", nullptr, &properties);
}
load_properties_from_file("/product/build.prop", nullptr, &properties);
load_properties_from_file("/product_services/build.prop", nullptr, &properties);
load_properties_from_file("/factory/factory.prop", "ro.*", &properties);
if (load_debug_prop) {
LOG(INFO) << "Loading " << kDebugRamdiskProp;
load_properties_from_file(kDebugRamdiskProp, nullptr, &properties);
}
for (const auto& [name, value] : properties) {
std::string error;
if (PropertySet(name, value, &error) != PROP_SUCCESS) {
LOG(ERROR) << "Could not set '" << name << "' to '" << value
<< "' while loading .prop files" << error;
}
}
property_initialize_ro_product_props();
property_derive_build_fingerprint();
update_sys_usb_config();
}
从源码中我们也可以看到 init 进程加载了哪些属性文件以及加载的顺序。
2.属性长什么样?
每一个属性是一个 key-value 对:
ro.actionable_compatible_property.enabled=true
ro.postinstall.fstab.prefix=/system
ro.secure=0
ro.allow.mock.location=1
ro.debuggable=1
debug.atrace.tags.enableflags=0
dalvik.vm.image-dex2oat-Xms=64m
dalvik.vm.image-dex2oat-Xmx=64m
等号左边是属性的名字,等号右边是属性的值
属性的分类:
- 一般属性:普通的 key-value 对,没有其他功能,系统启动后,如果修改了某个属性值(仅修改了内存中的值,未写入到文件),再重启系统,修改的值不会被保存下来,读取到的仍是修改前的值
- 特殊属性
- 属性名称以
ro
开头,那么这个属性被视为只读属性。一旦设置,属性值不能改变。net
开头的属性,顾名思义,就是与网络相关的属性,net
属性中有一个特殊的属性:net.change
,它记录了每一次最新设置和更新的net
属性,也就是每次设置和更新net
,属性时则会自动的更新net.change
属性,net.change
属性的 value 就是这个被设置或者更新的net
属性的 name。例如我们更新了属性net.bt.name
的值,由于net
有属性发生了变化,那么属性服务就会自动更新net.change
,将其值设置为net.bt.name
。- 以
persist
为开头的属性值,当在系统中通过 setprop 命令设置这个属性时,就会在/data/property/
目录下会保存一个副本。这样在系统重启后,按照加载流程这些persist
属性的值就不会消失了。- 属性
ctrl.start
和ctrl.stop
是用来启动和停止服务。这里的服务是指定义在 rc 后缀文件中的服务。当我们向ctrl.start
属性写入一个值时,属性服务将使用该属性值作为服务名找到该服务,启动该服务。这项服务的启动结果将会放入init.svc.<服务名>
属性中,可以通过查询这个属性值,以确定服务是否已经启动。
3.如何读写属性:
命令行:
getprop "wlan.driver.status"
setprop "wlan.driver.status" "timeout"
Native 代码:
char buf[20]="qqqqqq";
char tempbuf[PROPERTY_VALUE_MAX];
property_set("type_value",buf);
property_get("type_value",tempbuf,"0");
Java 代码:
String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
SystemProperties.set("service.bootanim.exit", "0");
4.属性的作用
常见的属性文件的作用如下:
属性文件生成过程
运行的系统中有很多属性文件:
/default.prop
/data/local.prop
/system/build.prop
/system/product/build.prop
/vendor/build.prop
/vendor/odm/etc/build.prop
/vendor/default.prop
那么这些属性文件是从哪里来的呢?
我们先看一下 /system/build.prop
文件的开头:
# begin common build properties
# autogenerated by build/make/tools/buildinfo_common.sh
ro.system.build.date=Fri Oct 13 17:23:40 CST 2023
# ......
注释里面说,当前文件由 build/make/tools/buildinfo_common.sh
自动生成,我们去看一下这个 shell 脚本的具体内容:
cat build/make/tools/buildinfo_common.sh
#!/bin/bash
partition="$1"
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <partition>" 1>&2
exit 1
fi
echo "# begin common build properties"
echo "# autogenerated by $0"
echo "ro.${partition}.build.date=`$DATE`"
echo "ro.${partition}.build.date.utc=`$DATE +%s`"
echo "ro.${partition}.build.fingerprint=$BUILD_FINGERPRINT"
echo "ro.${partition}.build.id=$BUILD_ID"
echo "ro.${partition}.build.tags=$BUILD_VERSION_TAGS"
echo "ro.${partition}.build.type=$TARGET_BUILD_TYPE"
echo "ro.${partition}.build.version.incremental=$BUILD_NUMBER"
echo "ro.${partition}.build.version.release=$PLATFORM_VERSION"
echo "ro.${partition}.build.version.sdk=$PLATFORM_SDK_VERSION"
echo "ro.product.${partition}.brand=$PRODUCT_BRAND"
echo "ro.product.${partition}.device=$PRODUCT_DEVICE"
echo "ro.product.${partition}.manufacturer=$PRODUCT_MANUFACTURER"
echo "ro.product.${partition}.model=$PRODUCT_MODEL"
echo "ro.product.${partition}.name=$PRODUCT_NAME"
echo "# end common build properties"
上述脚本中,通过 echo 打印了许多信息。这些信息都进入了文件中,那么调用过程中一定做了重定向操作。
我们在 build/make
中搜索 buildinfo_common.sh
,看看哪里使用了这个脚本:
cd build/make
grep -R "buildinfo_common.sh" .
./core/Makefile:BUILDINFO_COMMON_SH := build/make/tools/buildinfo_common.sh
可以看到在 build/make/core/Makefile
文件中,脚本的路径赋值给了 BUILDINFO_COMMON_SH 变量。
我们接着搜这个变量:
# 还是在 build/make 目录下
grep -R "BUILDINFO_COMMON_SH" .
./core/Makefile:BUILDINFO_COMMON_SH := build/make/tools/buildinfo_common.sh
./core/Makefile: bash $(BUILDINFO_COMMON_SH) "$(1)" >> $(2)
./core/Makefile:$(INSTALLED_DEFAULT_PROP_TARGET): $(BUILDINFO_COMMON_SH) $(intermediate_system_build_prop)
./core/Makefile:$(intermediate_system_build_prop): $(BUILDINFO_SH) $(BUILDINFO_COMMON_SH) $(INTERNAL_BUILD_ID_MAKEFILE) $(BUILD_SYSTEM)/version_defaults.mk $(system_prop_file) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(API_FINGERPRINT)
./core/Makefile:$(INSTALLED_VENDOR_BUILD_PROP_TARGET): $(BUILDINFO_COMMON_SH) $(intermediate_system_build_prop)
./core/Makefile:$(INSTALLED_PRODUCT_BUILD_PROP_TARGET): $(BUILDINFO_COMMON_SH) $(product_prop_files)
./core/Makefile:$(INSTALLED_ODM_BUILD_PROP_TARGET): $(BUILDINFO_COMMON_SH)
./core/Makefile:$(INSTALLED_PRODUCT_SERVICES_BUILD_PROP_TARGET): $(BUILDINFO_COMMON_SH)
注意到第二行的输出 ./core/Makefile: bash $(BUILDINFO_COMMON_SH) "$(1)" >> $(2)
,这里调用了上面的 shell 脚本
打开文件,看到它是函数 generate-common-build-props-with-product-vars-set
的一部分:
define generate-common-build-props-with-product-vars-set
BUILD_FINGERPRINT="$(BUILD_FINGERPRINT_FROM_FILE)" \
BUILD_ID="$(BUILD_ID)" \
BUILD_NUMBER="$(BUILD_NUMBER_FROM_FILE)" \
BUILD_VERSION_TAGS="$(BUILD_VERSION_TAGS)" \
DATE="$(DATE_FROM_FILE)" \
PLATFORM_SDK_VERSION="$(PLATFORM_SDK_VERSION)" \
PLATFORM_VERSION="$(PLATFORM_VERSION)" \
TARGET_BUILD_TYPE="$(TARGET_BUILD_VARIANT)" \
bash $(BUILDINFO_COMMON_SH) "$(1)" >> $(2)
endef
这里用函数的参数做了从定向,我们接着搜,哪里使用这个函数:
grep -R "generate-common-build-props-with-product-vars-set" .
./core/Makefile: $(call generate-common-build-props-with-product-vars-set,$(1),$(2))
./core/Makefile:define generate-common-build-props-with-product-vars-set
./core/Makefile: $(call generate-common-build-props-with-product-vars-set,system,$@)
有两个地方调用了 generate-common-build-props-with-product-vars-set
函数:
我们主要看第二个地方:
# 定义 system_prop_file 变量的值
# system_prop_file 代表了一个 prop 属性文件
ifdef TARGET_SYSTEM_PROP
system_prop_file := $(TARGET_SYSTEM_PROP)
else
system_prop_file := $(wildcard $(TARGET_DEVICE_DIR)/system.prop)
endif
# 这里是一个 Makefile 规则
# intermediate_system_build_prop 的值类似于 out/target/product/xxx/obj/ETC/system_build_prop_intermediates/build.prop 是一个中间文件,用于生成最终的 /system/build.prop
$(intermediate_system_build_prop): $(BUILDINFO_SH) $(BUILDINFO_COMMON_SH) $(INTERNAL_BUILD_ID_MAKEFILE) $(BUILD_SYSTEM)/version_defaults.mk $(system_prop_file) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(API_FINGERPRINT)
@echo Target buildinfo: $@
@mkdir -p $(dir $@)
$(hide) echo > $@
ifneq ($(PRODUCT_OEM_PROPERTIES),)
$(hide) echo "#" >> $@; \
echo "# PRODUCT_OEM_PROPERTIES" >> $@; \
echo "#" >> $@;
$(hide) $(foreach prop,$(PRODUCT_OEM_PROPERTIES), \
echo "import /oem/oem.prop $(prop)" >> $@;)
endif
$(hide) PRODUCT_BRAND="$(PRODUCT_SYSTEM_BRAND)" \
PRODUCT_MANUFACTURER="$(PRODUCT_SYSTEM_MANUFACTURER)" \
PRODUCT_MODEL="$(PRODUCT_SYSTEM_MODEL)" \
PRODUCT_NAME="$(PRODUCT_SYSTEM_NAME)" \
PRODUCT_DEVICE="$(PRODUCT_SYSTEM_DEVICE)" \
# 在这里调用 generate-common-build-props-with-product-vars-set 生成中间文件
# out/target/product/xxx/obj/ETC/system_build_prop_intermediates/build.prop
$(call generate-common-build-props-with-product-vars-set,system,$@)
$(hide) TARGET_BUILD_TYPE="$(TARGET_BUILD_VARIANT)" \
TARGET_BUILD_FLAVOR="$(TARGET_BUILD_FLAVOR)" \
TARGET_DEVICE="$(TARGET_DEVICE)" \
PRODUCT_DEFAULT_LOCALE="$(call get-default-product-locale,$(PRODUCT_LOCALES))" \
PRODUCT_DEFAULT_WIFI_CHANNELS="$(PRODUCT_DEFAULT_WIFI_CHANNELS)" \
PRIVATE_BUILD_DESC="$(PRIVATE_BUILD_DESC)" \
BUILD_ID="$(BUILD_ID)" \
BUILD_DISPLAY_ID="$(BUILD_DISPLAY_ID)" \
DATE="$(DATE_FROM_FILE)" \
BUILD_USERNAME="$(BUILD_USERNAME)" \
BUILD_HOSTNAME="$(BUILD_HOSTNAME)" \
BUILD_NUMBER="$(BUILD_NUMBER_FROM_FILE)" \
BOARD_BUILD_SYSTEM_ROOT_IMAGE="$(BOARD_BUILD_SYSTEM_ROOT_IMAGE)" \
AB_OTA_UPDATER="$(AB_OTA_UPDATER)" \
PLATFORM_VERSION="$(PLATFORM_VERSION)" \
PLATFORM_SECURITY_PATCH="$(PLATFORM_SECURITY_PATCH)" \
PLATFORM_BASE_OS="$(PLATFORM_BASE_OS)" \
PLATFORM_SDK_VERSION="$(PLATFORM_SDK_VERSION)" \
PLATFORM_PREVIEW_SDK_VERSION="$(PLATFORM_PREVIEW_SDK_VERSION)" \
PLATFORM_PREVIEW_SDK_FINGERPRINT="$$(cat $(API_FINGERPRINT))" \
PLATFORM_VERSION_CODENAME="$(PLATFORM_VERSION_CODENAME)" \
PLATFORM_VERSION_ALL_CODENAMES="$(PLATFORM_VERSION_ALL_CODENAMES)" \
PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION="$(PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION)" \
BUILD_VERSION_TAGS="$(BUILD_VERSION_TAGS)" \
$(if $(OEM_THUMBPRINT_PROPERTIES),BUILD_THUMBPRINT="$(BUILD_THUMBPRINT_FROM_FILE)") \
TARGET_CPU_ABI_LIST="$(TARGET_CPU_ABI_LIST)" \
TARGET_CPU_ABI_LIST_32_BIT="$(TARGET_CPU_ABI_LIST_32_BIT)" \
TARGET_CPU_ABI_LIST_64_BIT="$(TARGET_CPU_ABI_LIST_64_BIT)" \
TARGET_CPU_ABI="$(TARGET_CPU_ABI)" \
TARGET_CPU_ABI2="$(TARGET_CPU_ABI2)" \
bash $(BUILDINFO_SH) >> $@
# 把 system_prop_file 中的内容写入中间文件
$(hide) $(foreach file,$(system_prop_file), \
if [ -f "$(file)" ]; then \
echo Target buildinfo from: "$(file)"; \
echo "" >> $@; \
echo "#" >> $@; \
echo "# from $(file)" >> $@; \
echo "#" >> $@; \
cat $(file) >> $@; \
echo "# end of $(file)" >> $@; \
fi;)
# FINAL_BUILD_PROPERTIES 的值来自 ADDITIONAL_BUILD_PROPERTIES
# ADDITIONAL_BUILD_PROPERTIES 主要是 PRODUCT_PROPERTY_OVERRIDES 这个变量赋值, 而这个变量就是我们开发时经常用于自定义属性的, 一般在产品配置目录下定义
# 这里把 FINAL_BUILD_PROPERTIES 中的属性写入中间文件
$(if $(FINAL_BUILD_PROPERTIES), \
$(hide) echo >> $@; \
echo "#" >> $@; \
echo "# ADDITIONAL_BUILD_PROPERTIES" >> $@; \
echo "#" >> $@; )
$(hide) $(foreach line,$(FINAL_BUILD_PROPERTIES), \
echo "$(line)" >> $@;)
$(hide) build/make/tools/post_process_props.py $@ $(PRODUCT_SYSTEM_PROPERTY_BLACKLIST)
build_desc :=
ifeq (,$(filter true, $(TARGET_NO_KERNEL) $(TARGET_NO_RECOVERY)))
INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img
else
INSTALLED_RECOVERYIMAGE_TARGET :=
endif
# INSTALLED_BUILD_PROP_TARGET 的值为 out/target/product/xxx/system/build.prop
# 也就是我们最终要生成的文件
$(INSTALLED_BUILD_PROP_TARGET): $(intermediate_system_build_prop) $(INSTALLED_RECOVERYIMAGE_TARGET)
@echo "Target build info: $@"
# 这里把中间文件写入到最终文件中,同时剔除 ro.product.first_api_level 相关的行
$(hide) grep -v 'ro.product.first_api_level' $(intermediate_system_build_prop) > $@
以上代码总结一下:
- 准备好一个中间文件
out/target/product/xxx/obj/ETC/system_build_prop_intermediates/build.prop
- 调用
buildinfo_common.sh
,向中间文件写入数据 - 把 system_prop_file 中的内容写入中间文件
- 这里把用户自定义的的属性(PRODUCT_PROPERTY_OVERRIDES)写入中间文件
- 这里把中间文件写入到最终文件(out/target/product/xxx/system/build.pro)中,同时剔除 ro.product.first_api_level 相关的行
以上就是 /system/build.prop
,其他属性文件我们都可以依葫芦画瓢进行分析了。
如何添加系统属性
常见的自定义系统属性有三类:
- 添加系统属性到 /system/build.prop
- 添加系统属性到 /vendor/build.prop
- 添加系统属性到 /product/build.prop
1.添加系统属性到 /system/build.prop
我们先看看生成 /system/build.prop
文件相关的部分源码:
# build/make/core/Makefile
ifdef TARGET_SYSTEM_PROP
system_prop_file := $(TARGET_SYSTEM_PROP)
else
system_prop_file := $(wildcard $(TARGET_DEVICE_DIR)/system.prop)
endif
在上文我们分析过 system_prop_file 文件的内容最终会写入到 /system/build.prop
中,/system/build.prop
的值又来自 TARGET_SYSTEM_PROP
,所以我们添加一个属性文件,再修改 TARGET_SYSTEM_PROP
的值即可。
在我们的自定义 Product device/jelly/rice14
目录下,添加一个 system.prop
文件,文件的内容如下:
ro.rice14.test=2.0
接着在 device/jelly/rice14/BoardConfig.mk
中添加:
TARGET_SYSTEM_PROP += device/jelly/rice14/system.prop
然后重新编译系统,启动模拟器:
source build/envsetup.sh
lunch rice14-eng
make -j16
emulator
启动虚拟机后,我们可以进入虚拟机终端查看属性值:
adb shell
rice14:/ # getprop ro.rice14.test
2.0# end of device/jelly/rice14/system.prop
这里查询到了我们刚添加的属性。
2.添加系统属性到 /vendor/build.prop
同样的,我们先看看生成 /vendor/build.prop
文件相关的部分源码:
ifdef property_overrides_split_enabled
FINAL_VENDOR_BUILD_PROPERTIES += \
$(call collapse-pairs, $(PRODUCT_PROPERTY_OVERRIDES))
FINAL_VENDOR_BUILD_PROPERTIES := $(call uniq-pairs-by-first-component, \
$(FINAL_VENDOR_BUILD_PROPERTIES),=)
endif # property_overrides_split_enabled
这里会把 PRODUCT_PROPERTY_OVERRIDES
变量中的值赋值写入到 FINAL_VENDOR_BUILD_PROPERTIES
,从变量名字就可以看出,这就是最终的 vendor 属性文件,所以我们通过修改 PRODUCT_PROPERTY_OVERRIDES 变量的值即可添加属性:
# device/jelly/rice14/rice14.mk
PRODUCT_PROPERTY_OVERRIDES += \
ro.vendor.xxx=xxx \
ro.vendor.yyy=yyy
3.添加系统属性到 /product/build.prop
同样的,我们先看看生成 /product/build.prop
文件相关的部分源码:
ifdef TARGET_PRODUCT_PROP
product_prop_files := $(TARGET_PRODUCT_PROP)
else
product_prop_files := $(wildcard $(TARGET_DEVICE_DIR)/product.prop)
endif
FINAL_PRODUCT_PROPERTIES += \
$(call collapse-pairs, $(PRODUCT_PRODUCT_PROPERTIES) $(ADDITIONAL_PRODUCT_PROPERTIES))
FINAL_PRODUCT_PROPERTIES := $(call uniq-pairs-by-first-component, \
$(FINAL_PRODUCT_PROPERTIES),=)
可以看到这里会把 product_prop_files
文件和 PRODUCT_PRODUCT_PROPERTIES
变量以及 ADDITIONAL_PRODUCT_PROPERTIES
变量中的值都会写入到最终的属性文件中。
所以上两节介绍的两种方式都可以,这里我们演示先通过添加变量值的方式添加属性:
# device/jelly/rice14/rice14.mk
PRODUCT_PRODUCT_PROPERTIES += \
ro.product.xxx=xxx \
ro.product.yyy=yyy
属性与 Selinux
Android O 以后,属性的 Selinux 的规则变得复杂起来了,今天我们先看一个简单的例子,做基本的了解。在Hal与硬件服务
的 treble 章节我们还会继续来学习属性相关的 Selinux 配置。
#读写属性的示例代码
首先我们在 device/jelly/rice14
目录下创建如下的目录:
PropTest/
├── Android.bp
└── prop_test.cpp
其中 prop_test.cpp
具体内容如下:
#include <string>
#include <cutils/properties.h>
#define LOG_TAG "prop_test"
#include <log/log.h>
#include <android-base/properties.h>
using namespace std;
int main(int argc, char *argv[])
{
// 打印版本信息
char android_version[PROPERTY_VALUE_MAX];
property_get("ro.build.version.release", android_version, "");
ALOGD("android version : %s", android_version);
// 写入自定义属性
property_set("vendor.my.prop.test", "xxx");
// 读自定义属性
char prop_test[PROPERTY_VALUE_MAX];
property_get("vendor.my.prop.test", prop_test, "");
ALOGD("prop test : %s", prop_test);
while(1) {
}
return 0;
}
配套的编译文件 Android.bp
如下:
至此,整个示例程序就完成了,最后修改我们自定义 Product 的配置文件 device/jelly/rice14/rice14.mk
:
PRODUCT_PACKAGES +=
# ......
prop_test \
#Selinux 的配置
接着需要配置 SeLinux:
在 device/jelly/rice14
目录下创建如下的文件与目录:
sepolicy/
├── file_contexts
├── property_contexts
├── property.te
└── prop_te
不要忘了配置自定义 sepolicy 目录,在 device/jelly/rice14/rice14.mk
中添加如下内容:
BOARD_SEPOLICY_DIRS += \
device/jelly/rice14/sepolicy
首先在 property.te
中定义属性类型
type vendor_mytest_prop, property_type;
接着在 property_contexts
中配置好我们自定义属性 vendor.my.prop.test
的安全上下文:
vendor.my.prop.test u:object_r:vendor_mytest_prop:s0
接着在 prop_test.te
中配置好可执行文件和对应进程的类型和域转换规则以及属性的读写权限:
# 可执行文件对应进程类型
type myprop_test_dt, domain;
# 可执行文件类型
type myprop_test_dt_exec, exec_type, vendor_file_type, file_type;
# 域转换规则
init_daemon_domain(myprop_test_dt)
domain_auto_trans(shell, myprop_test_dt_exec, myprop_test_dt)
# 属性读写规则
set_prop(myprop_test_dt, vendor_mytest_prop);
get_prop(myprop_test_dt, vendor_mytest_prop);
get_prop(myprop_test_dt, exported2_default_prop);
在属性读写规则中,我们添加了对 exported2_default_prop
类型属性的读取规则,exported2_default_prop
是源码中 ro.build.version.release
属性对应的 type,我是通过如下的搜索命令查找到的:
find . -name "property_contexts" | xargs grep ro.build.version.release
./system/sepolicy/public/property_contexts:ro.build.version.release u:object_r:exported2_default_prop:s0 exact string
./system/sepolicy/prebuilts/api/29.0/public/property_contexts:ro.build.version.release u:object_r:exported2_default_prop:s0 exact string
./system/sepolicy/prebuilts/api/28.0/public/property_contexts:ro.build.version.release u:object_r:exported2_default_prop:s0 exact string
最后我们需要再 file_contexts
中配置可执行文件的安全上下文:
/vendor/bin/prop_test u:object_r:myprop_test_dt_exec:s0
接着我们就可以执行在虚拟机的 shell 中执行 prop_test 可执行文件了:
# 重新编译源码
source build/envsetup.sh
lunch rice14-eng
make -j16
# 启动虚拟机
emulator
# 重开一个终端,进入虚拟机 shell 环境
adb shell
# root
su
# selinux 配置为 Permissive 模式
setenforce 0
# 退出 root
exit
prop_test
接着我们查看 log:
logcat | grep prop_test
10-23 10:25:42.420 2959 2959 W prop_test: type=1400 audit(0.0:27): avc: denied { read } for name="u:object_r:vendor_mytest_prop:s0" dev="tmpfs" ino=6750 scontext=u:r:shell:s0 tcontext=u:object_r:vendor_mytest_prop:s0 tclass=file permissive=0
10-23 10:25:42.424 2959 2959 D prop_test: android version : 10
10-23 10:25:42.428 2959 2959 D prop_test: prop test :
10-23 10:29:41.930 3704 3704 W prop_test: type=1400 audit(0.0:58): avc: denied { use } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:29:41.930 3704 3704 W prop_test: type=1400 audit(0.0:60): avc: denied { use } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:29:41.930 3704 3704 W prop_test: type=1400 audit(0.0:61): avc: denied { use } for path="/dev/goldfish_pipe" dev="tmpfs" ino=7279 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:29:41.930 3704 3704 W prop_test: type=1400 audit(0.0:62): avc: denied { use } for path="/dev/goldfish_pipe" dev="tmpfs" ino=7279 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:31:17.980 3919 3919 I prop_test: type=1400 audit(0.0:87): avc: denied { use } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=1
10-23 10:31:17.980 3919 3919 I prop_test: type=1400 audit(0.0:88): avc: denied { read write } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:object_r:devpts:s0 tclass=chr_file permissive=1
10-23 10:31:17.980 3919 3919 I prop_test: type=1400 audit(0.0:89): avc: denied { read write } for path="socket:[9765]" dev="sockfs" ino=9765 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=unix_stream_socket permissive=1
10-23 10:31:17.980 3919 3919 I prop_test: type=1400 audit(0.0:90): avc: denied { use } for path="/vendor/bin/prop_test" dev="dm-1" ino=158 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:shell:s0 tclass=fd permissive=1
10-23 10:31:18.000 3919 3919 D prop_test: android version : 10
10-23 10:31:18.002 3919 3919 D prop_test: prop test : xxx
发现我们的程序任然缺少一些权限,我们把权限相关的 log 拷贝下来,在源码根目录下新建一个 avc_log.txt 文件,把缺少权限相关的 log 拷贝进去:
# avc_log.txt
10-23 10:38:05.670 2910 2910 W prop_test: type=1400 audit(0.0:28): avc: denied { use } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:38:05.670 2910 2910 W prop_test: type=1400 audit(0.0:30): avc: denied { use } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:38:05.670 2910 2910 W prop_test: type=1400 audit(0.0:31): avc: denied { use } for path="socket:[10955]" dev="sockfs" ino=10955 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:38:05.670 2910 2910 W prop_test: type=1400 audit(0.0:32): avc: denied { use } for path="/dev/goldfish_pipe" dev="tmpfs" ino=7343 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:38:57.400 3034 3034 I prop_test: type=1400 audit(0.0:39): avc: denied { use } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=1
10-23 10:38:57.400 3034 3034 I prop_test: type=1400 audit(0.0:40): avc: denied { read write } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:object_r:devpts:s0 tclass=chr_file permissive=1
10-23 10:38:57.400 3034 3034 I prop_test: type=1400 audit(0.0:41): avc: denied { read write } for path="socket:[10955]" dev="sockfs" ino=10955 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=unix_stream_socket permissive=1
10-23 10:38:57.410 3034 3034 I prop_test: type=1400 audit(0.0:42): avc: denied { use } for path="/vendor/bin/prop_test" dev="dm-1" ino=158 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:shell:s0 tclass=fd permissive=1
接着执行命令 audit2allow -i avc_log.txt
生成缺失的权限配置:
allow myprop_test_dt adbd:fd use;
allow myprop_test_dt adbd:unix_stream_socket { read write };
allow myprop_test_dt devpts:chr_file { read write };
allow myprop_test_dt shell:fd use;
把这些权限配置加入到 prop_test.te
后,重新编译系统,启动虚拟机,程序即可正常运行。