聊一聊 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掉

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章