聊一聊 android properties


前言:关于properties 之前也有关注看过一些东西,比较零零散散,最近又碰到运营商项目有一些文档涉及到这部分的知识需要重新回顾梳理一下,因此先从android O之后property的相关改动说起,在理一下property相关的原理,然后重新理解下运营商需要文档的一个相关的处理

1 Property contexts

Property_contexts:用于声明属性的安全上下文,plat前缀的文件用于声明system属性,nonplat前缀的文件用于
声明vendor属性。

Android 8.0 开始 property_contexts被拆分为两部分 : plat_property_contexts 和 nonplat_property_contexts两部分

1.1 plat_property_contexts

Android platform property_context that has no device-specific labels.
意思是说Android平台的属性一般没有去定义设备特定的标签,但是注意他们应该是有标签的

Must reside in system partition at
/system/etc/selinux/plat_property_contexts and be loaded by init at the start along with the non-platform property_contexts .这些属性属于system properties,位于system 分区,在手机的system/etc/selinux/目录下的plat_property_contexts中,这些属性与非平台属性也就是非system properties一起在init进程中被加载

1.2 nonplat_property_contexts

Device-specific property_context built by combining property_contexts
found in the directories pointed to by BOARD_SEPOLICY_DIRS in device’s
Boardconfig.mk files.
例如:BOARD_SEPOLICY_DIRS += device/oppa/X600/sepolicy/non_plat
那么在android\device\oppa\X600\sepolicy\non_plat目录下找到property_contexts,其中定义了非system properties

Must reside in vendor partition at
/vendor/etc/selinux/nonplat_property_contexts and be loaded by init
at the start along with the platform property_context
非system properties位于vendor分区,也可以称为vendor properties

2 property的访问约束

2.1 非system分区的访问约束限制

vendor-init-settable
○ Property files and init rc files in non-system partitions can only override the whitelisted-settable-init-props
and vendor (or odm) properties.
vendor-init-readable
○ Init rc files in non-system partitions can only read those system properties and vendor (or odm)
properties.
vendor-init-actionable
○ Init rc files in non-system partitions can only use those system properties and vendor (or odm) properties
as a trigger
public-readable
○ Binaries in non-system partitions can only read those RO properties and vendor (or odm) properties.
○ Binaries in non-system partitions can only write vendor (or odm) properties. They cannot write platform
properties.

2.2 system分区的访问约束限制

Processes running in the system shouldn’t read vendor (or odm)properties, but it’s not enforced in P (might
be enforced in the future).
Processes running in the system cannot write vendor (or odm) properties (see the neverallow)

System processes can only read and write to system properties that are used only within the system
partition. This includes AOSP-defined system properties as well as partner-defined system properties. Both
are never accessible outside of the system partition.

3 Property Set Permission

3.1 开始基于SELinuxpolicy

SEAndroid是一种基于安全策略的MAC安全机制。SEAndroid安全机制中的安全策略就是在安全上下文的基础上进行描述的,也就是说,它通过主体和客体的安全上下文,定义主体是否有权限访问客体。

我之前的sepolicy相关的总结中有提到:

基于 Android 4.3(宽容模式)和 Android 4.4(部分强制模式)的 Android 5.0 版本,开始全面强制执行 SELinux。
通过此项变更,Android 已从对有限的一组关键域(installd、netd、vold 和 zygote)强制执行 SELinux 转为对所有
域(超过 60 个域)强制执行 SELinux。具体而言:
在 Android 5.x 及更高版本中,所有域均处于强制模式。init 以外的任何进程都不应在 init 域中运行。

也就是从Android 5.0 (L)开始Property Set Permission开始基于SELinuxpolicy,而不再基于UID/GID

我们可以看到Android K中system/core/init/property_service.c中定义了一个array property_perms[],用于check property set的权限是否合法

/* White list of permissions for setting property services. */
60struct {
61    const char *prefix;
62    unsigned int uid;
63    unsigned int gid;
64} property_perms[] = {
65    { "net.rmnet0.",      AID_RADIO,    0 },
66    { "net.gprs.",        AID_RADIO,    0 },
67    { "net.ppp",          AID_RADIO,    0 },
68    { "net.qmi",          AID_RADIO,    0 },
69    { "net.lte",          AID_RADIO,    0 },
70    { "net.cdma",         AID_RADIO,    0 },
71    { "ril.",             AID_RADIO,    0 },
72    { "gsm.",             AID_RADIO,    0 },
73    { "persist.radio",    AID_RADIO,    0 },
74    { "net.dns",          AID_RADIO,    0 },
75    { "sys.usb.config",   AID_RADIO,    0 },
76    { "net.",             AID_SYSTEM,   0 },
77    { "dev.",             AID_SYSTEM,   0 },
78    { "runtime.",         AID_SYSTEM,   0 },
79    { "hw.",              AID_SYSTEM,   0 },
80    { "sys.",             AID_SYSTEM,   0 },
81    { "sys.powerctl",     AID_SHELL,    0 },
82    { "service.",         AID_SYSTEM,   0 },
83    { "wlan.",            AID_SYSTEM,   0 },
84    { "bluetooth.",       AID_BLUETOOTH,   0 },
85    { "dhcp.",            AID_SYSTEM,   0 },
86    { "dhcp.",            AID_DHCP,     0 },
87    { "debug.",           AID_SYSTEM,   0 },
88    { "debug.",           AID_SHELL,    0 },
89    { "log.",             AID_SHELL,    0 },
90    { "service.adb.root", AID_SHELL,    0 },
91    { "service.adb.tcp.port", AID_SHELL,    0 },
92    { "persist.sys.",     AID_SYSTEM,   0 },
93    { "persist.service.", AID_SYSTEM,   0 },
94    { "persist.security.", AID_SYSTEM,   0 },
95    { "persist.service.bdroid.", AID_BLUETOOTH,   0 },
96    { "selinux."         , AID_SYSTEM,   0 },
97    { NULL, 0, 0 }
98};

Android L开始所有properties的read write的权限都必须符合SEAndroid的规则,这在Android O以后变得尤其严格。
而且可以预想Android Q可能会更加严格,以达到Google的 管控目的

3.2 对于APK set system property

更好的具有可扩展性的解决方案是:

Implement one service, provide the AIDL and share with SYS UID, and the AIDL API can be setSysProperty.

Define the permission to control the AIDL API, and the permission protection type must be system or privileged.

All clients must bind to service to invoke related API to set the property.

For future requirements, new service APKs can be implemented and ensure it is run in the expected domain.

The client can be as least privileged, and the AIDL be protected with permission, so that the client can bind to different services for setting different properties

4 Property原理

4.1 属性使用方法

java应用里设置属性

import android.os.SystemProperties;
......
        SystemProperties.set("ro.debuggable", "1");

在java里取得属性:

long longFrameTime_ms = Integer.parseInt(SystemProperties.get("debug.longframe_ms", "16"));

也可以用SystemProperties.getBoolean,getInt等

SystemProperties.getBoolean("debug.crash_sysui", false)

native C中设置属性

#include <cutils/properties.h>
property_set("debug.sf.hwc_service_name", "mock");

在C中取得属性:

  char encrypted_state[32];
  property_get("ro.crypto.state", encrypted_state, "");

最后一个参数是默认值。

property在 Init rc files中的使用
Set a property
/vendor/etc/init/hw/init.taimen.rc

setprop vold.post_fs_data_done 1
setprop wifi.interface wlan0

Read a property
/vendor/etc/init/init.taimen.diag.rc

setprop sys.usb.state ${sys.usb.config}
write /config/usb_gadget/g1/UDC ${sys.usb.controller}

Action Triggers
/vendor/etc/init/hw/init.taimen.rc

on property:sys.user.0.ce_available=true
on property:sys.boot_completed=1

系统启动时以下面的次序加载预先设定属性:
/system/etc/prop.default
/product/build.prop
/odm/default.prop
/vendor/default.prop

/system/build.prop
/system/default.prop
/data/local.prop
/data/property/*

后加载的如果有重名的则覆盖前面的。

有两种属性值需要注意:

persist.* : 以persist开始的属性会在/data/property存一个副本。也就是说,如果程序调property_set设了一个以persist为前缀的属性,系统会在/data/property/*里加一个文件记录这个属性,重启以后这个属性还有。如果property_set其它属性,因为属性是在内存里存,所以重启后这个属性就没有了。

ro.* :以ro为前缀的属性不能修改。

4.2 property初始化

属性初始化的入口点是property_init
system/core/init/init.cpp

int main(int argc, char** argv) {
    ......
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
    if (is_first_stage) {
        boot_clock::time_point start_time = boot_clock::now();
        ......
        // /mnt/vendor is used to mount vendor-specific partitions that can not be
        // part of the vendor partition, e.g. because they are mounted read-write.
        mkdir("/mnt/vendor", 0755);

        InitKernelLogging(argv);
        ......
        // Set up SELinux, loading the SELinux policy.
        SelinuxSetupKernelLogging();
        SelinuxInitialize();
        ......
    }
    ......
    property_init();    //属性初始化
    ......
    // Now set up SELinux for second stage.
    SelinuxSetupKernelLogging();
    SelabelInitialize();
    SelinuxRestoreContext();
    ......
    epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    if (epoll_fd == -1) {
        PLOG(FATAL) << "epoll_create1 failed";
    }
    ......
    //property_init初始化完property以后,加载prop的属性
    property_load_boot_defaults();
    export_oem_lock_status();
    //启动property service
    start_property_service();
    ......
     
}

system/core/init/property_service.cpp中定义

void property_init() {
104    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
105    CreateSerializedPropertyInfo();
106    if (__system_property_area_init()) {
107        LOG(FATAL) << "Failed to initialize property area";
108    }
109    if (!property_info_area.LoadDefaultPath()) {
110        LOG(FATAL) << "Failed to load serialized property info file";
111    }
112}

//property_init初始化完property以后,加载prop的属性
680void property_load_boot_defaults() {
681    if (!load_properties_from_file("/system/etc/prop.default", NULL)) {
682        // Try recovery path
683        if (!load_properties_from_file("/prop.default", NULL)) {
684            // Try legacy path
685            load_properties_from_file("/default.prop", NULL);
686        }
687    }
688    load_properties_from_file("/product/build.prop", NULL);
689    load_properties_from_file("/odm/default.prop", NULL);
690    load_properties_from_file("/vendor/default.prop", NULL);
691
692    update_sys_usb_config();
693}

先看下CreateSerializedPropertyInfo

void CreateSerializedPropertyInfo() {
800    auto property_infos = std::vector<PropertyInfoEntry>();
801    if (access("/system/etc/selinux/plat_property_contexts", R_OK) != -1) {
802        if (!LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts",
803                                      &property_infos)) {
804            return;
805        }
806        // Don't check for failure here, so we always have a sane list of properties.
807        // E.g. In case of recovery, the vendor partition will not have mounted and we
808        // still need the system / platform properties to function.
809        if (!LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
810                                      &property_infos)) {
811            // Fallback to nonplat_* if vendor_* doesn't exist.
812            LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts",
813                                     &property_infos);
814        }
815    } else {
816        if (!LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) {
817            return;
818        }
819        if (!LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
820            // Fallback to nonplat_* if vendor_* doesn't exist.
821            LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos);
822        }
823    }
       ......

property_init初始化property,包括plat_property_contexts和nonplat_property_contexts中properties的定义以后,加载prop的属性值,在property_load_boot_defaults() 中可以看到这些属性的加载先后顺序
其它的系统属性(build.prop, local.prop,…)在start_property_service中加载

4.2 启动property service

接着启动property service
system/core/init/property_service.cpp

840void start_property_service() {
841    selinux_callback cb;
842    cb.func_audit = SelinuxAuditCallback;
843    selinux_set_callback(SELINUX_CB_AUDIT, cb);
844
845    property_set("ro.property_service.version", "2");
846
847    property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
848                                   false, 0666, 0, 0, nullptr);
849    if (property_set_fd == -1) {
850        PLOG(FATAL) << "start_property_service socket creation failed";
851    }
852
853    listen(property_set_fd, 8);
854
855    register_epoll_handler(property_set_fd, handle_property_set_fd);
856}
857

init进程调用property_init函数在共享内存区域中创建并初始化属性域。而后通过执行进程所提供的API访问属性域中的设置值。但更改属性域时,要预先向init进程提交值变更申请,然后init进程处理该申请,并修改属性值(用propert_set()和property_get()来处理),为此init进程生成“/dev/socket/property_service”套接字,以接收其它进程提交的申请

init.cpp的main函数通过epoll_create1用来创建轮询监听子进程终止和属性变更请求的文件描述符,监听子进程终止套接字fd和属性变更请求套接字fd通过epoll_ctl注册到epoll_fd中,然后通过epoll_wait监听轮询这两个fd

/system/core/init/init.cpp

133 void register_epoll_handler(int fd, void (*fn)()) {
134    epoll_event ev;
135    ev.events = EPOLLIN;
136    ev.data.ptr = reinterpret_cast<void*>(fn);
137    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
138        PLOG(ERROR) << "epoll_ctl failed";
139    }
140}

init启动属性服务时创建一个socket的文件描述符和其他进程通信(设置或读取属性),通过轮询机制如果有请求到来,则调用handle_property_set_fd来处理这个请求,在这个函数里,首先检查请求者的uid/gid看看是否有权限,如果有权限则调property_service.cpp中的property_set函数。

register_epoll_handler函数主要的作用是注册属性socket文件描述符到轮询描述符epoll_fd,当property_set_fd可读时,会调用上面register_epoll_handler函数的第二个参数(处理函数handle_property_set_fd())

在Propertyset函数中,它先查找就没有这个属性,如果找到,更改属性。如果找不到,则添加新属性。更改时还会判断是不是“ro”属性,如果是,则不能更改。如果是persist的话还会写到/data/property/中。

//system/core/init/property_service.cpp
static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {

133    if (!IsLegalPropertyName(name)) {
134        *error = "Illegal property name";
135        return PROP_ERROR_INVALID_NAME;
136    }
137
138    if (valuelen >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) {
139        *error = "Property value too long";
140        return PROP_ERROR_INVALID_VALUE;
141    }
142
143    if (mbstowcs(nullptr, value.data(), 0) == static_cast<std::size_t>(-1)) {
144        *error = "Value is not a UTF8 encoded string";
145        return PROP_ERROR_INVALID_VALUE;
146    }
147
148    prop_info* pi = (prop_info*) __system_property_find(name.c_str());
149    if (pi != nullptr) {
150        // ro.* properties are actually "write-once".
151        if (StartsWith(name, "ro.")) {
152            *error = "Read-only property was already set";
153            return PROP_ERROR_READ_ONLY_PROPERTY;
154        }
155
156        __system_property_update(pi, value.c_str(), valuelen);
157    } else {
158        int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
159        if (rc < 0) {
160            *error = "__system_property_add failed";
161            return PROP_ERROR_SET_FAILED;
162        }
163    }
164
165    // Don't write properties to disk until after we have read all default
166    // properties to prevent them from being overwritten by default values.
167    if (persistent_properties_loaded && StartsWith(name, "persist.")) {
168        WritePersistentProperty(name, value);
169    }
170    property_changed(name, value);
171    return PROP_SUCCESS;
172}
}

最后它会调property_changed,把事件挂到队列里,如果有人注册这个属性的话(比如init.rc中on property:ro.kernel.qemu=1),最终会调它的会调函数。
system/core/init/init.cpp

void property_changed(const std::string& name, const std::string& value) {
    if (name == "sys.powerctl") {
173        // Despite the above comment, we can't call HandlePowerctlMessage() in this function,
174        // because it modifies the contents of the action queue, which can cause the action queue
175        // to get into a bad state if this function is called from a command being executed by the
176        // action queue.  Instead we set this flag and ensure that shutdown happens before the next
177        // command is run in the main init loop.
178        // TODO: once property service is removed from init, this will never happen from a builtin,
179        // but rather from a callback from the property service socket, in which case this hack can
180        // go away.
181        shutdown_command = value;
182        do_shutdown = true;
183    }
184
185    if (property_triggers_enabled) ActionManager::GetInstance().QueuePropertyChange(name, value);
186
187    if (waiting_for_prop) {
188        if (wait_prop_name == name && wait_prop_value == value) {
189            LOG(INFO) << "Wait for property took " << *waiting_for_prop;
190            ResetWaitForProp();
191        }
192    }
}

5 运营商property要求疑问

最近遇到运营商项目中关于Property Permission章节的文档填写,要求:disclosed the change of property_perms[], a array defined in system/core/init/Property_service.c,查看了一下是没有的,有点不理解,还以为是要列出所有改动的properties,查看到android 4.4的源码才搜到property_perms[],原来是要列出我们添加或改变的property permission部分,是否有改动property_perms[],因为4,4对property的权限是基于UID/GID

这样一来肯定是运营商给的文档中此项描述还未及时更新,那或者需要我们提供property 权限的添加或更改部分,又或者此要求的部分过于陈旧,直接remove掉


Guys,oppo内推啦,欢迎发简历给我
https://blog.csdn.net/ch853199769/article/details/114842209

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安德路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值