一. 属性内存区域的创建和初始化
属性内存区域是由init进程在启动的过程中创建和初始化。创建和初始化完成之后,其它进程可以将这块属性内存区域以只读的方式映射到自己的地址空间去,这样其它进程就可以直接从自己的地址空间读出属性值。另一方面,如果其它进程需要增加或者修改属性的值,那么就必须要通过init进程来进行。Init进程在启动的时候,会创建一个属性管理服务。这个属性管理服务会创建一个Server端Socket,用来接收其它进程发送过来的增加或者修改属性的请求。
查看system/core/init/init.c中的main函数:
int main(int argc, char **argv)
{
........
property_init();
........
property_load_boot_defaults();
........
queue_builtin_action(property_service_init_action,"property_service_init");
........
queue_builtin_action(queue_property_triggers_action,"queue_property_triggers");
for(;;){
........
execute_one_command();
........
if (!property_set_fd_init && get_property_set_fd() > 0){
ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
........
nr = poll(ufds, fd_count, timeout);
........
for (i = 0; i < fd_count; i++) {
if (ufds[i].revents & POLLIN) {
if (ufds[i].fd == get_property_set_fd())
handle_property_set_fd();
else if (ufds[i].fd == get_keychord_fd())
handle_keychord();
else if (ufds[i].fd == get_signal_fd())
handle_signal();
}
}
}
return0;
}
在main函数中,首先调用函数property_init来创建属性内存区域。然后调用函数property_load_boot_defaults初始化一些默认的属性。接下来是将函数property_service_init_action和queue_property_triggers_action加入到内部的一个命令队列中去,以便在接下来的for循环中可以通过函数execute_one_command来执行。函数property_service_init_action的作用是启动属性管理服务。
在main函数最后的for循环中,除了调用函数execute_one_command执行命令队列中的命令之外,还做其它事情。其中的一件事情就是监听其它进程通过Socket发送过来的增加或者修改属性的请求。用来接收请求的Server端Socket是在函数property_service_init_action的调用过程中创建的,并且可以通过函数get_property_set_fd来获得它的文件描述符。
有了这个Server端Socket的文件描述符之后,就可以通过函数poll来监听其它进程发送过来的请求了。也就是说,一旦其它进程发送请求过来,那么Init进程就会从函数poll返回,并且获得一个类型为POLLIN的事件。如果这个POLLIN事件对应的文件描述符就是用来接收增加或者修改属性请求的Server端Socket的文件描述符,那么就调用另外一个函数handle_property_set_fd来处理请求。
查看整个过程中的一些重要的函数调用实现:
- property_init (system/core/init/property_service.c)
void property_init(void)
{
init_property_area();
}
它通过调用另外一个函数init_property_area来创建和初始化一块属性内存区域。查看init_property_area():
static int init_property_area(void)
{
if(property_area_inited)
return -1;
if(__system_property_area_init())
return -1;
if(init_workspace(&pa_workspace, 0))
return -1;
fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);
property_area_inited = 1;
return0;
}
函数init_property_area首先是检查全局变量property_area_inited和函数__system_property_area_init()的值。如果不等于0,那么就说明属性内存区域已经创建过了,因此就直接返回。否则,接下来就调用函数init_workspace来创建一块大小等于PA_SIZE的属性内存区域,并且将创建出来的属性内存区域的地址保存在全局变量pa_workspace所指向的一个workspace结构体的成员变量data中。
查看init_workspace函数的实现:
static int init_workspace(workspace *w, size_t size)
{
void*data;
int fd =open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW);
if (fd <0)
return -1;
w->size =size;
w->fd =fd;
return0;
}
其中PROP_FILENAME定义在bionic/libc/include/sys/_system_properties.h中:
#define PROP_FILENAME "/dev/__properties__"
- property_load_boot_defaults
void property_load_boot_defaults(void)
{
load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);
}
函数property_load_boot_defaults通过调用另外一个函数load_properties_from_file将定义在文件PROP_PATH_RAMDISK_DEFAULT里面的属性加载前面创建的属性内存区域中。 PROP_PATH_RAMDISK_DEFAULT是一个宏,定义在文件bionic/libc/include/sys/_system_properties.h中,
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"
- property_service_init_action(system/cor/init/init.c)
static int property_service_init_action(intnargs, char **args)
{
start_property_service();
if(get_property_set_fd() < 0) {
ERROR("start_property_service() failed\n");
exit(1);
}
return0;
}
它通过调用另外一个函数start_property_service来启动属性管理服务。查看start_property_service(system/core/init/property_service.c):
void start_property_service(void)
{
intfd;
fd =create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0,NULL);
if(fd <0) return;
fcntl(fd,F_SETFD, FD_CLOEXEC);
fcntl(fd,F_SETFL, O_NONBLOCK);
listen(fd,8);
property_set_fd = fd;
}
调用函数create_socket创建了一个名称为PROP_SERVICE_NAME的Socket,并且调用函数listen来监听其它进程发送过的增加或者修改属性请求。其中,PROP_SERVICE_NAME是一个定义在文件bionic/libc/include/sys/_system_properties.h中的宏。#define PROP_SERVICE_NAME "property_service"
最后,函数将前面创建Socket获得的文件描述符保存在全局变量property_set_fd中,以便init进程的main函数可以通过函数get_property_set_fd获取。查看get_property_set_fd的实现:
int get_property_set_fd()
{
returnproperty_set_fd;
}
- queue_property_triggers_action
static int queue_property_triggers_action(int nargs, char**args)
{
queue_all_property_triggers();
property_triggers_enabled = 1;
return0;
}
查看queue_all_property_triggers(system/core/init/init_parser.c)
void queue_all_property_triggers()
{
structlistnode *node;
structaction *act;
list_for_each(node, &action_list) {
act = node_to_item(node, struct action, alist);
if (!strncmp(act->name, "property:", strlen("property:"))){
const char* name = act->name + strlen("property:");
const char* equals = strchr(name, '=');
if (equals) {
char prop_name[PROP_NAME_MAX + 1];
char value[PROP_VALUE_MAX];
int length = equals - name;
if (length > PROP_NAME_MAX) {
ERROR("property name too long in trigger %s", act->name);
} else {
int ret;
memcpy(prop_name, name, length);
prop_name[length] = 0;
ret = property_get(prop_name, value);
if (ret > 0 && (!strcmp(equals + 1, value) ||
!strcmp(equals + 1, "*"))) {
action_add_queue_tail(act);
}
}
}
}
}
}
以上就是Init进程创建和初始化属性内存区域以及创建属性管理服务的过程。前面我们提到,其它进程也需要将Init进程创建的属性内存区域映射到自己的进程地址访问来,以便可以对属性进行直接的读取。接下来我们继续分析其它进程是如何将Init进程创建的属性内存区域映射到自己的进程地址空间的。以应用程序为例,Android中,所有的应用程序都是Zygote进程fork出来的,查看Zygote的启动脚本(system/core/rootdir/init.zygote32.rc):
service zygote /system/bin/app_process -Xzygote /system/bin--zygote --start-system-server
classmain
socketzygote stream 660 root system
onrestartwrite /sys/android_power/request_state wake
onrestartwrite /sys/power/state on
onrestartrestart media
onrestartrestart netd
所有service都是通过函数启动的,这个函数在system/core/init/init.c中,它的定义如下所示:
void service_start(struct service *svc, const char*dynamic_args)
{
......
char *scon =NULL;
intrc;
......
if(is_selinux_enabled() > 0) {
......
rc = getcon(&mycon);
......
rc = getfilecon(svc->args[0], &fcon);
......
rc = security_compute_create(mycon, fcon,string_to_security_class("process"), &scon);
......
}
........
pid =fork();
if (pid ==0) {
........
if (properties_inited()) {
get_property_workspace(&fd, &sz);
sprintf(tmp, "%d,%d", dup(fd), sz);
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
}
.......
for (si = svc->sockets; si; si = si->next) {
int socket_type = (
!strcmp(si->type, "stream") ? SOCK_STREAM :
(!strcmp(si->type, "dgram") ? SOCK_DGRAM :SOCK_SEQPACKET));
int s = create_socket(si->name, socket_type,
si->perm, si->uid, si->gid, si->socketcon ?:scon);
if (s >= 0) {
publish_socket(si->name, s);
}
}
........
if (!dynamic_args) {
if (execve(svc->args[0], (char**) svc->args, (char**) ENV)< 0) {
ERROR("cannot execve('%s'): %s\n", svc->args[0],strerror(errno));
}
} else {
........
execve(svc->args[0], (char**) arg_ptrs, (char**) ENV);
}
........
}
........
}
参数svc描述的是要启动的服务的信息,参数dynamic_args描述的是服务启动参数。首先进行四个SEAndroid检测:
1.是否开启了SEAndroid:
is_selinux_enabled()
2.能否获取当前进程的安全上下文mycon:
rc = getcon(&mycon);
3.能否获取服务的可执行文件(保存在参数args[0])的安全上下文fcon:
rc = getfilecon(svc->args[0], &fcon);
4.能否获取获得即将要创建的进程的安全上下文scon:
rc = security_compute_create(mycon, fcon,string_to_security_class("process"), &scon);
然后调用函数properties_inited检查Init进程是否已经创建和初始化好属性内存区域了。如果已经创建和初始化好,那么就继续调用函数get_property_workspace来获得用来描述属性内存区域的文件描述符fd以及属性内存区域的大小sz。属性内存区域是通过将文件/dev/__properties__映射到内存中创建的。因此,这里得到的文件描述符fd实际上指向的就是文件/dev/__properties__。再接下来将属性内存区域的文件描述符以及大小拼接成一个字符串,并且将得到的字符串作为环境变量ANDROID_PROPERTY_WORKSPACE的值。
调用函数create_socket创建一个名为Zygote的Socket,并设置它的安全上下文。这个Socket是在Init进程的启动脚本init.rc中配置要创建的,并且是用来接收ActivityManagerService发送过来的应用程序进程创建请求的。这一步说明在SEAndroid中,除了进程、文件、属性,Socket也是有安全上下文的。通过给Socket设置安全上下文,我们就可以通过SEAndroid安全策略来设置什么样的进程可以连接什么样的Socket。例如,我们可以通过SEAndroid安全策略限定只有ActivityManagerService所运行在的SystemServer进程才会权限连接名称为zygote的Socket。这样就可以使得只有有权限的进程,才可以请求Zygote进程创建应用程序进程。
最后调用函数execve在新创建的进程中加载参数args[0]所描述的要执行文件,实际上就是/system/bin/app_process文件。按照SEAndroid的规则规定,执行了/system/bin/app_process文件之后,新进程的安全上下文的domain就会被设置为zygote了。这样就可以与init进程的安全上下文区分开来。
应用程序进程都是通过Zygote进程fork出来的,这意味着应用程序进程会继续父进程zygote的环境变量,也就是会继续前面第2步创建的环境变量ANDROID_PROPERTY_WORKSPACE。有了这个环境变量之后,就可以得到用来描述在Init进程创建的属性内存区域的文件描述符以及大小了。
应用程序初始化属性内存区域的方法是
__system_properties_init(),在bionic/libc/bionic/system_properties.cpp中,查看这个函数:
int __system_properties_init()
{
returnmap_prop_area();
}
查看map_prop_area():
static char property_filename[PATH_MAX] =PROP_FILENAME;
static int map_prop_area()
{
intfd(open(property_filename, O_RDONLY | O_NOFOLLOW |O_CLOEXEC));
if (fd >=0) {
const int ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
if (ret < 0) {
close(fd);
return -1;
}
}
boolclose_fd = true;
if ((fd <0) && (errno == ENOENT)) {
fd = get_fd_from_env();
close_fd = false;
}
if (fd <0) {
return -1;
}
const intmap_result =(fd);
if(close_fd) {
close(fd);
}
returnmap_result;
}
在文件system_properties.c中,定义有一个全局变量__system_property_area__,它的初始值被设置为NULL,用来表明在本进程中,属性内存区域还没有初始过。一旦本进程的属性内存区域初始化之后,全局变量__system_property_area__就会指向这块属性内存区域。
了解了全局变量__system_property_area__的作用之后,我们就开始分析函数map_prop_area初始化属性内存区域的过程。
首先是调用函数open以只读方式打开文件PROP_FILENAME。从前面的分析可以知道,PROP_FILENAME是一个宏,它的值等于/dev/__properties__,init进程也是通过打开它来创建属性内存区域的。如果打开文件PROP_FILENAME失败,那么就通过调用函数get_fd_from_env来获得指向属性内存区域的文件描述符。
函数get_fd_from_env的实现如下所示:
static int get_fd_from_env(void)
{
// Thisenvironment variable consistes of two decimal integer
// valuesseparated by a ",". The first value is a file descriptor
// and thesecond is the size of the system properties area. The
// size iscurrently unused.
char *env =getenv("ANDROID_PROPERTY_WORKSPACE");
if (!env){
return -1;
}
returnatoi(env);
}
函数get_fd_from_env是通过读取环境变量ANDROID_PROPERTY_WORKSPACE来获得指向属性内在区域的文件描述符的。这个环境变量刚好就是在Zygote进程创建的时候设置的。也就是说,应用程序进程可以通过父进程Zygote设置的环境变量ANDROID_PROPERTY_WORKSPACE来获得指向属性内存区域的文件描述符。
回到函数map_prop_area中,获得了指向属性内存区域的文件描述符之后,接下来就调用函数map_fd_ro,查看这个函数:
static int map_fd_ro(const int fd) {
struct statfd_stat;
if(fstat(fd, &fd_stat) < 0) {
return -1;
}
if((fd_stat.st_uid != 0)
|| (fd_stat.st_gid != 0)
|| ((fd_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0)
|| (fd_stat.st_size < static_cast(sizeof(prop_area))) ) {
return -1;
}
pa_size =fd_stat.st_size;
pa_data_size= pa_size - sizeof(prop_area);
void* constmap_result = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd,0);
if(map_result == MAP_FAILED) {
return -1;
}
prop_area*pa = reinterpret_cast(map_result);
if((pa->magic != PROP_AREA_MAGIC) || (pa->version !=PROP_AREA_VERSION &&
pa->version != PROP_AREA_VERSION_COMPAT)) {
munmap(pa, pa_size);
return -1;
}
if(pa->version == PROP_AREA_VERSION_COMPAT) {
compat_mode = true;
}
__system_property_area__ = pa;
return0;
}
这个函数以只读共享的方式将该文件描述符指向的文件/dev/__properties__映射到本进程来。我们注意到,init进程也是通过共享的方式来映射文件/dev/__properties__的。正是由于如此,init进程和其它进程才可以做到共享属性内存区域。但是,属性内存区域在init进程中可读可写的,而在其它进程中只可以读。
获得了属性内存区域之后,就会验证它头部的魔数和版本号是否正确。正确的话,就说明得到的是一块有效的属性内存区域,因此,就可以把它的地址保存在全局变量__system_property_area__中。
这样,我们就以应用程序进程为例,分析了其它进程初始化属性内存区域的过程。重点就是通过共享方式将/dev/__properties__映射到自己的进程地址空间来。以实现共享在init进程创建的属性内存区域。
二.属性的get和set
- C库提供了一个函数__system_property_get,用来读取Android系统中的属性,它在bionic/libc/bionic/system_properties.c中,定义如下:
int __system_property_get(const char *name, char *value)
{
constprop_info *pi = __system_property_find(name);
if (pi != 0){
return __system_property_read(pi, 0, value);
} else{
value[0] = 0;
return 0;
}
}
参数name用来指定要读取的属性的名称,参数value用来保存读取出来的属性的值。
函数__system_property_get首先是调用函数__system_property_find在前面已经初始化好的属性内存区域__system_property_area__中找到与参数name对应的一个结构体prop_info,接着再调用函数__system_property_read来读保存在该结构体中的属性的值。
函数__system_property_find通过遍历__system_property_area__描述的属性区域头部中的索引表,找到每一个属性的名称,并且拿来与参数name描述的属性名称比较,直到找到一个相等的值为止,这样就意味着找到了对应的prop_info结构体。
一个进程在读取属性值中,需要检查是否有另外一个进程正在修改该属性的值。如果有的话,就需要等待。这个功能通过调用函数__system_property_read来实现
从函数__system_property_get的实现就可以看出,读取Android系统的属性是没有权限控制的,并且可以直接从本进程的地址空间读取。
- 函数__system_property_set用来增加或者修改属性,它的定义如下:
int __system_property_set(const char *key, const char*value)
{
if (key ==0) return -1;
if (value ==0) value = "";
if(strlen(key) >= PROP_NAME_MAX) return -1;
if(strlen(value) >= PROP_VALUE_MAX) return -1;
prop_msgmsg;
memset(&msg, 0, sizeof msg);
msg.cmd =PROP_MSG_SETPROP;
strlcpy(msg.name, key, sizeof msg.name);
strlcpy(msg.value, value, sizeof msg.value);
const interr = send_prop_msg(&msg);
if (err <0) {
return err;
}
return0;
}
参数key描述的是要增加或者修改的属性的名称,参数value描述的是要增加或者要修改的属性的值。函数__system_property_get首先是将要增加或者修改的属性的相关信息封装在一个类型为PROP_MSG_SETPROP的消息中,接着再调用函数send_prop_msg将它发送给init进程。
函数send_prop_msg的实现如下:
static const char property_service_socket[] = "/dev/socket/"PROP_SERVICE_NAME;
static int send_prop_msg(const prop_msg *msg)
{
const int fd= socket(AF_LOCAL, SOCK_STREAM |SOCK_CLOEXEC, 0);
if (fd ==-1) {
return -1;
}
const size_tnamelen = strlen(property_service_socket);
sockaddr_unaddr;
memset(&addr, 0, sizeof(addr));
strlcpy(addr.sun_path, property_service_socket,sizeof(addr.sun_path));
addr.sun_family = AF_LOCAL;
socklen_talen = namelen + offsetof(sockaddr_un, sun_path) + 1;
if(TEMP_FAILURE_RETRY(connect(fd,reinterpret_cast(&addr), alen)) < 0) {
close(fd);
return -1;
}
const intnum_bytes = TEMP_FAILURE_RETRY(send(fd, msg,sizeof(prop_msg), 0));
int result =-1;
if(num_bytes == sizeof(prop_msg)) {
// We successfully wrote to the property server but now we
// wait for the property server to finish itswork. It
// acknowledges its completion by closing the socket so we
// poll here (on nothing), waiting for the socket to close.
// If you 'adb shell setprop foo bar' you'll see the POLLHUP
// once the socket closes. Out of paranoia we capour poll
// at 250 ms.
pollfd pollfds[1];
pollfds[0].fd = fd;
pollfds[0].events = 0;
const int poll_result = TEMP_FAILURE_RETRY(poll(pollfds, 1, 250 ));
if (poll_result == 1 && (pollfds[0].revents & POLLHUP)!= 0) {
result = 0;
} else {
// Ignore the timeout and treat it like a success anyway.
// The init process is single-threaded and its property
// service is sometimes slow to respond (perhaps it's off
// starting a child process or something) and thus this
// times out and the caller thinks it failed, even though
// it's still getting around to it. So we fake ithere,
// mostly for ctl.* properties, but we do try and wait 250
// ms so callers who do read-after-write can reliably see
// what they've written. Most of the time.
// TODO: fix the system properties design.
result = 0;
}
}
close(fd);
returnresult;
}
全局变量property_service_socket,它的值等于"/dev/socket/"PROP_SERVICE_NAME。从前面分析属性管理服务的启动过程可以知道,PROP_SERVICE_NAME是一个宏,它的值等于“property_service”,是属性管理服务正在监听的一个Socket的名称。
函数send_prop_msg的执行过程如下:
1.调用函数socket创建一个本地Socket,并且将它要连接的对端Socket的地址设置为/dev/socket/property_service。
2.调用函数connect请求与对端Socket进行连接。
3.连接成功后,调用函数send向对端发送参数msg描述的消息。
4. 发送成功后,调用函数poll等待对端关闭连接。
一旦对端Socket关闭了连接,就说明增加或者修改属性成功了。
前面在分析init进程的入口函数main时提到,一旦有进程通过文件/dev/socket/property_service描述的Socket发送连接请求时,函数handle_property_set_fd就会被调用,它的实现在system/core/init/property_service.c,函数实现如下:
void handle_property_set_fd()
{
prop_msgmsg;
int s;
int r;
intres;
struct ucredcr;
structsockaddr_un addr;
socklen_taddr_size = sizeof(addr);
socklen_tcr_size = sizeof(cr);
char *source_ctx = NULL;
structpollfd ufds[1];
const inttimeout_ms = 2 * 1000;
intnr;
if ((s =accept(property_set_fd, (struct sockaddr *)&addr, &addr_size)) < 0) {
return;
}
if(getsockopt(s, SOL_SOCKET, SO_PEERCRED,&cr, &cr_size) < 0) {
close(s);
ERROR("Unable to receive socket options\n");
return;
}
ufds[0].fd =s;
ufds[0].events = POLLIN;
ufds[0].revents = 0;
nr =TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
if (nr == 0){
ERROR("sys_prop: timeout waiting for uid=%d to send propertymessage.\n", cr.uid);
close(s);
return;
} else if(nr < 0) {
ERROR("sys_prop: error waiting for uid=%d to send property message.err=%d %s\n", cr.uid, errno, strerror(errno));
close(s);
return;
}
r =TEMP_FAILURE_RETRY(recv(s, &msg,sizeof(msg), MSG_DONTWAIT));
if(r !=sizeof(prop_msg)) {
ERROR("sys_prop: mis-match msg size received: %d expected: %zuerrno: %d\n",
r, sizeof(prop_msg), errno);
close(s);
return;
}
switch(msg.cmd) {
casePROP_MSG_SETPROP:
msg.name[PROP_NAME_MAX-1] = 0;
msg.value[PROP_VALUE_MAX-1] = 0;
if (!is_legal_property_name(msg.name, strlen(msg.name))) {
ERROR("sys_prop: illegal property name. Got: \"%s\"\n",msg.name);
close(s);
return;
}
getpeercon(s, &source_ctx);
if(memcmp(msg.name,"ctl.",4) == 0){
// Keep the old close-socket-early behavior when handling
// ctl.* properties.
close(s);
if (check_control_mac_perms(msg.value, source_ctx)) {
handle_control_message((char*) msg.name + 4, (char*)msg.value);
} else {
ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%dpid:%d\n",
msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
}
} else {
if (check_perms(msg.name, source_ctx)){
property_set((char*) msg.name, (char*)msg.value);
} else {
ERROR("sys_prop: permission denied uid:%d name:%s\n",
cr.uid, msg.name);
}
// Note: bionic's property client code assumes that the
// property server will not close the socket until *AFTER*
// the property is written to memory.
close(s);
}
freecon(source_ctx);
break;
default:
close(s);
break;
}
}
函数执行过程如下:
1. 调用函数accept接受其它进程发送过来的连接请求。
2. 接受了请求后,调用函数getsocketopt获得发送端进程的信息,例如uid和guid。
3. 调用函数recv获取请求的消息内容,并且保存在一个类型为prop_msg的结构体msg中。
4.目前属性管理服务只处理类型为PROP_MSG_SETPROP的消息,对应的就是增加或者修改属性的请求,按照下面的步骤进行处理。
5.调用函数getpeercon获得发送端Socket的安全上下文source_ctx,一般就是等于发送端进程的安全上下文了。
6.如果发送过来的消息请求处理的属性的名称以“ctl.”开头,那么就说明要处理的是一个控制消息。这时候先调用check_control_mac_perms检查发送端进程是否具有相应的权限。如果有的话,再调用函数handle_control_message来处理该消息。
7.如果发送过来的消息请求处理的属性的名称不是以“ctl.”开头,那么就说明要处理的是一个普通消息。这时候先调用check_perms检查发送端进程是否具有相应的权限。如果有的话,再调用函数property_set来处理该消息。
我们以普通消息的处理过程为例,来说明SEAndroid是如何保护系统的属性的。
查看函数check_perms的实现:
static int check_perms(const char *name, char *sctx)
{
int i;
unsigned intapp_id;
if(!strncmp(name, "ro.", 3))
name +=3;
returncheck_mac_perms(name, sctx);
}
name是要增加或者修改的属性的名称,sctc描述要增加或者修改属性的进程的安全上下文。
函数check_perms是SEAndroid的权限检查,它的作用是检查请求增加或者修改属性的进程是否具有应的权限,最终检测通过调用check_mac_perms函数实现:
static int check_mac_perms(const char *name, char *sctx)
{
if(is_selinux_enabled() <= 0)
return 1;
char *tctx =NULL;
const char*class = "property_service";
const char*perm = "set";
int result =0;
if(!sctx)
goto err;
if(!sehandle_prop)
goto err;
if(selabel_lookup(sehandle_prop, &tctx, name, 1) != 0)
goto err;
if(selinux_check_access(sctx, tctx,class, perm, (void*) name) == 0)
result = 1;
freecon(tctx);
err:
returnresult;
}
参数name描述的是要进行增加或者修改的属性的名称,参数sctx描述的是请求增加或者修改属性的进程的安全上下文。
sehandle_prop是一个全局变量,用来描述external/sepolicy/property_contexts。这个文件定义了具有什么样的安全上下文的进程能够增加或者修改什么样的属性。查看其内容:
##########################
# property service keys
#
#
net.rmnet u:object_r:net_radio_prop:s0
net.gprs u:object_r:net_radio_prop:s0
net.ppp u:object_r:net_radio_prop:s0
net.qmi u:object_r:net_radio_prop:s0
net.lte u:object_r:net_radio_prop:s0
net.cdma u:object_r:net_radio_prop:s0
net.dns u:object_r:net_radio_prop:s0
sys.usb.config u:object_r:system_radio_prop:s0
ril. u:object_r:radio_prop:s0
gsm. u:object_r:radio_prop:s0
persist.radio u:object_r:radio_prop:s0
net. u:object_r:system_prop:s0
dev. u:object_r:system_prop:s0
runtime. u:object_r:system_prop:s0
hw. u:object_r:system_prop:s0
sys. u:object_r:system_prop:s0
sys.powerctl u:object_r:powerctl_prop:s0
service. u:object_r:system_prop:s0
wlan. u:object_r:system_prop:s0
dhcp. u:object_r:dhcp_prop:s0
dhcp.bt-pan.result u:object_r:pan_result_prop:s0
bluetooth. u:object_r:bluetooth_prop:s0
debug. u:object_r:debug_prop:s0
debug.db. u:object_r:debuggerd_prop:s0
log. u:object_r:shell_prop:s0
service.adb.root u:object_r:shell_prop:s0
service.adb.tcp.port* u:object_r:shell_prop:s0
persist.audio. u:object_r:audio_prop:s0
persist.logd. u:object_r:logd_prop:s0
persist.sys. u:object_r:system_prop:s0
persist.service. u:object_r:system_prop:s0
persist.service.bdroid. u:object_r:bluetooth_prop:s0
persist.security. u:object_r:system_prop:s0
# selinux non-persistent properties
selinux. u:object_r:security_prop:s0
# default property context
* u:object_r:default_prop:s0
# data partition encryption properties
vold. u:object_r:vold_prop:s0
crypto. u:object_r:vold_prop:s0
# ro.build.fingerprint is either set in /system/build.prop, oris
# set at runtime by system_server.
build.fingerprint u:object_r:fingerprint_prop:s0
# ctl properties
ctl.bootanim u:object_r:ctl_bootanim_prop:s0
ctl.dumpstate u:object_r:ctl_dumpstate_prop:s0
ctl.fuse_ u:object_r:ctl_fuse_prop:s0
ctl.mdnsd u:object_r:ctl_mdnsd_prop:s0
ctl.ril-daemon u:object_r:ctl_rildaemon_prop:s0
ctl.bugreport u:object_r:ctl_bugreport_prop:s0
ctl.dhcpcd_bt-pan u:object_r:ctl_dhcp_pan_prop:s0
ctl. u:object_r:ctl_default_prop:s0
# NFC properties
nfc. u:object_r:nfc_prop:s0
函数check_mac_perms首先调用函数selabel_lookup获得增加或者修改名称为name的属性所需要的安全上下文tctx,接着再调用函数selinux_check_access检查请求增加或者修改属性的进程的安全上下文sctx是否有权限对安全上下文为tctx的属性进行操作。
回到前面分析的函数handle_property_set_fd中,一旦通过了安全检查,接下来就会调用函数property_set对请求的属性进行增加或者修改。查看这个函数(system/core/init/property_service.c):
int property_set(const char *name, const char *value)
{
prop_info*pi;
intret;
size_tnamelen = strlen(name);
size_tvaluelen = strlen(value);
if(!is_legal_property_name(name, namelen)) return -1;
if (valuelen>= PROP_VALUE_MAX) return -1;
pi = (prop_info*) __system_property_find(name);
if(pi != 0){
if(!strncmp(name, "ro.", 3)) return -1;
__system_property_update(pi, value,valuelen);
} else{
ret = __system_property_add(name,namelen, value, valuelen);
if (ret < 0) {
ERROR("Failed to set '%s'='%s'\n", name, value);
return ret;
}
}
if(strncmp("net.", name, strlen("net."))== 0) {
if (strcmp("net.change", name) == 0){
return 0;
}
property_set("net.change",name);
} else if(persistent_properties_loaded &&
strncmp("persist.", name,strlen("persist.")) == 0) {
write_persistent_property(name,value);
} else if(strcmp("selinux.reload_policy", name)== 0 &&
strcmp("1", value) == 0) {
selinux_reload_policy();
}
property_changed(name, value);
return0;
}
参数name表示要增加或者修改的属性的名称,参数value表示要增加或者修改的属性的新值。
函数property_set首先是调用函数__system_property_find检查名称为name的属性是否已经存在属性内存区域中:
1. 如果存在的话,那么就会得到一个类型为prop_info的结构体pi,则调用函数 __system_property_update进指定的属性进行修改。
2. 如果不存在的话,那么指针pi的值就会等于NULL。则调用函数 __system_property_add增加属性。
无论是修改属性,还是增加属性,都需要将属性内存区域头部的serial值增加1,表示对属性内存区域进行了一次修改,并且调用函数__futex_wake通知正在等待属性内存区域修改完成的进程。
增加或者修改完成属性之后,还要进行以下的检查:
1.如果属性的名称是以“net.”开头,但是又不等于“net.change”,那么就将名称为“net.change”的属性设置为1,表示网络属性发生了变化。
2.如果属性的名称是以“persist.”开头,那么就表示该属性应该是持久化储存到文件中去,因此就会调用函数write_persistent_property执行这个操作,以便系统下次启动后,可以将该属性的初始值设置为系统上次关闭时的值。
3.如果属性的名称等于“selinux.reload_policy”,并且前面给它设置的值等于1,那么就表示要重新加载SEAndroid策略,这是通过调用函数selinux_reload_policy来实现的。
最后,函数property_set调用另外一个函数property_changed发送一个名称为name的属性发生了变化的通知。以便init进程可以执行在启动脚本init.rc中配置的操作。
至此,我们就分析完成SEAndroid安全机制对Android属性设置的保护支持了,并且顺便分析了Android属性的实现原理。属性是Android系统中特有的资源,通过它们除了可以获得系统的信息之外,还可以对系统的行为进行控制,因此,SEAndroid安全机制需要像文件一样对它们进行保护。