当前位置:   article > 正文

android系统tun_SEAndroid系统架构总体分析

otapreopt_slot

1.前言

在Linux中NSN开发了SELinux,来保护linux系统的安全,因为Android有着独特的用户运行时空间,

所以SELinux并不完全适用Android,故而在SELinux的基础上有了SEAndroid,SEAndroid主要保护的

对象就是系统中的资源例如文件,属性等和系统中的进程,Socket,IPC等。

2.SELinux的整体结构

45fb8d1e98a781016968f58294353156.png

在SEAndroid的整体结构中,通过SELinux的文件系统,将其划分为了用户空间和内核空间。用户空间中包含了

Security Context,Security Server,selinux库libselinux。内核空间包含了LSM.

1.Security Context:描述了对象安全上下,安全策略就是由资源安全上下文决定的

2.Security Server:一方面到Security Context中去检索安全上下文,另一方面也到内核空间操作安全上下文

3.SEAndroid Policy:安全策略。在系统启动的时候,Security Server会将其加载到内核空间的LSM中。

4.libselinux:是SELinux库,可以用来读写SELinux文件系统,加载SEAndroid Policy,检索和操作安全上下文等都是由libselinux完成的

5.LSM:负责内核资源的访问控制

3.内核空间

LSM:LSM是内核MAC机制的一个通用的模块,而SEAndroid仅仅是LSM的一个实现。这体现了内核设计的一个思想。在LSM中包含了Access Vector Cache

和Security Server两个模块。当要访问一个资源的时候,首先会到Access Vector Cache检查是否允许访问,如果不允许访问,则到Security Server

中查询是否可以访问,如果可以访问在访问的同时,也会在Access Vector Cache中将结果进行保存。

SELinux LSM 和内核子系统的交互:当访问内核子系统中的资源的时候,LSM就会调用他的Hook代码,而Hook代码就会回调SELinux中的回调方法。

例如当调用系统的read函数的时候,进入到内核子系统中的文件系统。在文件子系统中负责读取文件的vfs_read函数,就会回调LSM加入的Hook代码,

Hook代码就会回调SELinux中的回调函数,以确保安全性的检查

安全访问资源的流程:

f5c641ef199a70d71be342e94315c837.png

进行了三次错误性检查:

1. 检查对象是否存在,访问参数是否正确等

2. Linux UID/GID是否允许

3. 安全策略是否允许

4.用户空间

1.Security Context:对象安全上下文,用来定制安全策略。由用户,角色,类型和安全级别组成。在第一篇文章我们可知

查看上下文的方法。对象安全上下文又分为主体(指进程)和客体(指资源如文件等)。

其实用户和角色并不重要,重要的是类型。下面分别来对其进行介绍。

用户和角色只是用来限制进程可以标注的类型,而对文件来说是可以完成不记的。对所有主体和客体来说,只有一个SELinux用户,那就是

u(系统中只定义了这一个SELinux用户),对客体来说通常将它的角色定为object_r。而对所有的主体来说,只定义了一个角色那就是r。

在src/system/sepolicy/private/users中可以看到系统定义的SELinux用户

user u roles { r } level s0 range s0 - mls_systemhigh;

表明了一个SELinux的用户u,他可用的角色为r,安全级别默认为s0,而安全级别的范围为s0-mls_systemhigh

在src/system/sepolicy/private/roles_decl中可以看到角色的定义

role r;

在src/system/sepolicy/public/roles中可以看到角色可以关联的类型:

role r types domain;

表明角色r可以关联的类型为domain

可以看到u和r关联,而r又和类型domain关联。换句话说如果没有定义其他的用户,角色和类型,那么只有u,r和domain的组合才是正确的。

可是我们又会看到

$ ps -Z

LABEL USER PID PPID NAME

u:r:init:s0 root 1 0 /init

难道u:r:init:s0的组合是错误的吗?当然不是了。在src/system/sepolicy/public/init.te中

type init, domain, mlstrustedsubject;表明了将domain定义为init的属性,那么能用domain的地方就能用init.所以上面的说法也是正确的。

安全级别:在SELinux中安全级别是由敏感性(sensitivity)和类别(category)两部分组成的.但是类别这部分是可以省略的。一般的形式为sensitivity[:category]

如由敏感性s0和策略c0,c1组成的安全级别就为s0[:c0,c1]. 在SEAndroid中,低安全级别可以向高安全级别的写入数据,但是不能读。高安全级别的可以读取低安全

级别的数据,但是不能写入。如果两个安全级别是相同的,那么既可以读又可以写。

类型:类型是SEAndroid中最为重要的。在src/system/sepolicy/public/roles中我们可以看到

role r types domain

domain表示的是进程的类型。每一个进程的类型都会将domain作为属性,每一个文件的类型都会将file_context作为属性。

那么安全策略又是如何定义的呢?

主要分析四种安全策略的定义,app进程,app数据文件,系统文件已经系统属性。与其相关的文件有

src/system/sepolicy/private/mac_permissions.xml,src/system/sepolicy/private/seapp_contexts,src/system/sepolicy/private/file_contexts,

property_contexts.

安全策略的确定首先要确定安全上下文,其实也就是确定最为重要的类型。

看mac_permissions.xml

如果我们的app使用的是系统的签名,那么从该文件中得知,对应的seinfo为platform.从当前代码中我们可以看到系统只提供了两个签名(nexus8.1的代码),如果

还有其他的签名的app怎么办?从注释中我们可以看到,它将会用seinfo为"default"。seinfo并不是app进程的类型,接下来再看seapp_contexts文件

# Input selectors:

# isSystemServer (boolean)

# isEphemeralApp (boolean)

# isV2App (boolean)

# isOwner (boolean)

# user (string)

# seinfo (string)

# name (string)

# path (string)

# isPrivApp (boolean)

# minTargetSdkVersion (unsigned integer)

# isSystemServer=true can only be used once.

# An unspecified isSystemServer defaults to false.

# isEphemeralApp=true will match apps marked by PackageManager as Ephemeral

# isV2App=true will match apps in the v2 app sandbox.

# isOwner=true will only match for the owner/primary user.

# isOwner=false will only match for secondary users.

# If unspecified, the entry can match either case.

# An unspecified string selector will match any value.

# A user string selector that ends in * will perform a prefix match.

# user=_app will match any regular app UID.

# user=_isolated will match any isolated service UID.

# isPrivApp=true will only match for applications preinstalled in

# /system/priv-app.

# minTargetSdkVersion will match applications with a targetSdkVersion

# greater than or equal to the specified value. If unspecified,

# it has a default value of 0.

# All specified input selectors in an entry must match (i.e. logical AND).

# Matching is case-insensitive.

......

#

isSystemServer=true domain=system_server

user=system seinfo=platform domain=system_app type=system_app_data_file

user=bluetooth seinfo=platform domain=bluetooth type=bluetooth_data_file

user=nfc seinfo=platform domain=nfc type=nfc_data_file

user=radio seinfo=platform domain=radio type=radio_data_file

user=shared_relro domain=shared_relro

user=shell seinfo=platform domain=shell type=shell_data_file

user=_isolated domain=isolated_app levelFrom=user

user=_app seinfo=media domain=mediaprovider name=android.process.media type=app_data_file levelFrom=user

user=_app seinfo=platform domain=platform_app type=app_data_file levelFrom=user

user=_app isV2App=true isEphemeralApp=true domain=ephemeral_app type=app_data_file levelFrom=user

user=_app isPrivApp=true domain=priv_app type=app_data_file levelFrom=user

user=_app minTargetSdkVersion=26 domain=untrusted_app type=app_data_file levelFrom=user

user=_app domain=untrusted_app_25 type=app_data_file levelFrom=user

当在mac_permissions.xml中查询到seinfo信息后,就会到该文件查询对应类型。如platform对应的app的进程的类型就为platform_app,而由该app创建的文件的类型

就为app_data_file。而对于seinfo为default的app进程的类型为untrusted_app_25,由该app进程所创建的文件的类型为app_data_file.知道了安全上下文是如何定义的

之后就可以看看安全策略是如何定义的了。

继续看init.te文件

ypeattribute init coredomain;

tmpfs_domain(init)

# Transitions to seclabel processesininit.rc

domain_trans(init, rootfs, charger)

domain_trans(init, rootfs, healthd)

domain_trans(init, rootfs, slideshow)

domain_auto_trans(init, e2fs_exec, e2fs)

recovery_only(`

domain_trans(init, rootfs, adbd)

domain_trans(init, rootfs, recovery)')

domain_trans(init, shell_exec, shell)

domain_trans(init, init_exec, ueventd)

domain_trans(init, init_exec, watchdogd)

domain_trans(init, { rootfs toolbox_exec }, modprobe)

#case where logpersistd is actually logcat -f inlogd context (nee: logcatd)

userdebug_or_eng(`

domain_auto_trans(init, logcat_exec, logpersist)')

可以看到类似于tmpfs_domain(init)的调用,这是所定义的宏,为init类型添加一些权限

再看文件src/system/sepolicy/te_macros

......

define(`tmpfs_domain', `

type $1_tmpfs, file_type;

type_transition $1 tmpfs:file $1_tmpfs;

allow $1 $1_tmpfs:file { read write getattr };

allow $1 tmpfs:dir { getattr search };

')

......

可以看到tmpfs_domain为将file_type设置为init_tmpfs的属性,那么init_tmpfs将也是描述文件的类型,之后还通过allow语句为init设置了其他权限。

系统文件的权限设置。file_contexts文件中的内容如下

......

/system(/.*)? u:object_r:system_file:s0

/system/bin/atrace u:object_r:atrace_exec:s0

/system/bin/e2fsdroid u:object_r:e2fs_exec:s0

/system/bin/mke2fs u:object_r:e2fs_exec:s0

/system/bin/e2fsck -- u:object_r:fsck_exec:s0

/system/bin/fsck\.f2fs -- u:object_r:fsck_exec:s0

/system/bin/make_f2fs -- u:object_r:fsck_exec:s0

/system/bin/fsck_msdos -- u:object_r:fsck_exec:s0

/system/bin/tune2fs -- u:object_r:fsck_exec:s0

/system/bin/toolbox -- u:object_r:toolbox_exec:s0

/system/bin/toybox -- u:object_r:toolbox_exec:s0

/system/bin/logcat -- u:object_r:logcat_exec:s0

/system/bin/logcatd -- u:object_r:logcat_exec:s0

/system/bin/sh -- u:object_r:shell_exec:s0

/system/bin/run-as -- u:object_r:runas_exec:s0

/system/bin/bootanimation u:object_r:bootanim_exec:s0

/system/bin/bootstat u:object_r:bootstat_exec:s0

/system/bin/app_process32 u:object_r:zygote_exec:s0

/system/bin/app_process64 u:object_r:zygote_exec:s0

/system/bin/servicemanager u:object_r:servicemanager_exec:s0

/system/bin/hwservicemanager u:object_r:hwservicemanager_exec:s0

/system/bin/surfaceflinger u:object_r:surfaceflinger_exec:s0

/system/bin/bufferhubd u:object_r:bufferhubd_exec:s0

/system/bin/performanced u:object_r:performanced_exec:s0

/system/bin/drmserver u:object_r:drmserver_exec:s0

/system/bin/dumpstate u:object_r:dumpstate_exec:s0

/system/bin/incident u:object_r:incident_exec:s0

/system/bin/incidentd u:object_r:incidentd_exec:s0

/system/bin/netutils-wrapper-1\.0 u:object_r:netutils_wrapper_exec:s0

/system/bin/vold u:object_r:vold_exec:s0

/system/bin/netd u:object_r:netd_exec:s0

/system/bin/wificond u:object_r:wificond_exec:s0

/system/bin/audioserver u:object_r:audioserver_exec:s0

/system/bin/mediadrmserver u:object_r:mediadrmserver_exec:s0

/system/bin/mediaserver u:object_r:mediaserver_exec:s0

/system/bin/mediametrics u:object_r:mediametrics_exec:s0

/system/bin/cameraserver u:object_r:cameraserver_exec:s0

/system/bin/mediaextractor u:object_r:mediaextractor_exec:s0

/system/bin/mdnsd u:object_r:mdnsd_exec:s0

/system/bin/installd u:object_r:installd_exec:s0

/system/bin/otapreopt_chroot u:object_r:otapreopt_chroot_exec:s0

/system/bin/otapreopt_slot u:object_r:otapreopt_slot_exec:s0

/system/bin/keystore u:object_r:keystore_exec:s0

/system/bin/fingerprintd u:object_r:fingerprintd_exec:s0

/system/bin/gatekeeperd u:object_r:gatekeeperd_exec:s0

/system/bin/crash_dump32 u:object_r:crash_dump_exec:s0

......

/system(/.*)? u:object_r:system_file:s0 表明了在system目录中的文件的安全上下文都是u:object_r:system_file:s0.

但是在之后,发现对system中的一些文件进行了详细的安全上下文的定义。所以这些文件的安全上下文,由最后以单独定义的安全上下文为主。

再看属性上下文的定义,文件property_contexts的内容如下:

......

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_dns_prop:s0

sys.usb.config u:object_r:system_radio_prop:s0

ril. u:object_r:radio_prop:s0

ro.ril. u:object_r:radio_prop:s0

gsm. u:object_r:radio_prop:s0

persist.radio u:object_r:radio_prop:s0

......

由net.rmnet可以指定,只有能够访问类型为net_radio_prop的进程才能访问这个属性。

当在用户空间完成策略的定义之后,还需要在系统开机的时候把它加载到内核的LMS模块中,这主要是借助了libselinux来实现。

在src/system/core/init/init.cpp中可以看到

int main(int argc, char**argv) {

......

if (is_fist_stage){mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);//Set up SELinux, loading the SELinux policy.

selinux_initialize(true); ......

}return 0;

}

在init的初始化的第一阶段会做如下几件事:

1.将selinux系统挂载在/sys/fs/selinux

2.调用selinux_initialize(true)对selinux策略进行初始化

selinux_initialize函数也是在init.cpp中定义,内容如下:

static void selinux_initialize(boolin_kernel_domain) {

Timer t;

selinux_callback cb;

cb.func_log=selinux_klog_callback;

selinux_set_callback(SELINUX_CB_LOG, cb);

cb.func_audit=audit_callback;

selinux_set_callback(SELINUX_CB_AUDIT, cb);if(in_kernel_domain) {

LOG(INFO)<< "Loading SELinux policy";if (!selinux_load_policy()) {

panic();

}bool kernel_enforcing = (security_getenforce() == 1);bool is_enforcing =selinux_is_enforcing();if (kernel_enforcing !=is_enforcing) {if(security_setenforce(is_enforcing)) {

PLOG(ERROR)<< "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");

security_failure();

}

}

std::stringerr;if (!WriteFile("/sys/fs/selinux/checkreqprot", "0", &err)) {

LOG(ERROR)<

security_failure();

}//init's first stage can't set properties, so pass the time to the second stage.

setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);

}else{

selinux_init_all_handles();

}

}

1.首先是设置selinux回调

2.调用selinux_load_policy加载SELinux policy

3.设置selinux的模式。在开启了selinux的系统中,通过getenforce可以看到系统中selinux的模式。有Enforcing和Permissive。Enforcing即为生效。

permissive为即使违反了SELinux policy,只做提示但不拒绝。

selinux_load_policy函数同样是在init.cpp中

static boolselinux_load_policy() {return selinux_is_split_policy_device() ?selinux_load_split_policy()

: selinux_load_monolithic_policy();

}

selinux_is_split_policy_device()同样是在init.cpp中,这个函数主要是判断"/system/etc/selinux/plat_sepolicy.cil"这个文件是否是可用的

可用时就会调用selinux_load_split_policy()否则调用selinux_load_monolithic_policy()。 先看selinux_load_split_policy()

static boolselinux_load_split_policy() {//IMPLEMENTATION NOTE: Split policy consists of three CIL files://* platform -- policy needed due to logic contained in the system image,//* non-platform -- policy needed due to logic contained in the vendor image,//* mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy//with newer versions of platform policy.//

//secilc is invoked to compile the above three policy files into a single monolithic policy//file. This file is then loaded into the kernel.//Load precompiled policy from vendor image, if a matching policy is found there. The policy//must match the platform policy on the system image.

std::stringprecompiled_sepolicy_file;if (selinux_find_precompiled_split_policy(&precompiled_sepolicy_file)) {

android::base::unique_fd fd(

open(precompiled_sepolicy_file.c_str(), O_RDONLY| O_CLOEXEC |O_BINARY));if (fd != -1) {if (selinux_android_load_policy_from_fd(fd, precompiled_sepolicy_file.c_str()) < 0) {

LOG(ERROR)<< "Failed to load SELinux policy from" <

}return true;

}

}//No suitable precompiled policy could be loaded

LOG(INFO)<< "Compiling SELinux policy";//Determine the highest policy language version supported by the kernel

set_selinuxmnt("/sys/fs/selinux");int max_policy_version =security_policyvers();if (max_policy_version == -1) {

PLOG(ERROR)<< "Failed to determine highest policy version supported by kernel";return false;

}//We store the output of the compilation on /dev because this is the most convenient tmpfs//storage mount available this early in the boot sequence.

char compiled_sepolicy[] = "/dev/sepolicy.XXXXXX";

android::base::unique_fd compiled_sepolicy_fd(mkostemp(compiled_sepolicy, O_CLOEXEC));if (compiled_sepolicy_fd < 0) {

PLOG(ERROR)<< "Failed to create temporary file" <

}//Determine which mapping file to include

std::stringvend_plat_vers;if (!selinux_get_vendor_mapping_version(&vend_plat_vers)) {return false;

}

std::string mapping_file("/system/etc/selinux/mapping/" + vend_plat_vers + ".cil");const std::string version_as_string =std::to_string(max_policy_version);//clang-format off

const char* compile_args[] ={"/system/bin/secilc",

plat_policy_cil_file,"-M", "true", "-G", "-N",//Target the highest policy language version supported by the kernel

"-c", version_as_string.c_str(),

mapping_file.c_str(),"/vendor/etc/selinux/nonplat_sepolicy.cil","-o", compiled_sepolicy,//We don't care about file_contexts output by the compiler

"-f", "/sys/fs/selinux/null", ///dev/null is not yet available

nullptr};//clang-format on

if (!fork_execve_and_wait_for_completion(compile_args[0], (char**)compile_args, (char**)ENV)) {

unlink(compiled_sepolicy);return false;

}

unlink(compiled_sepolicy);

LOG(INFO)<< "Loading compiled SELinux policy";if (selinux_android_load_policy_from_fd(compiled_sepolicy_fd, compiled_sepolicy) < 0) {

LOG(ERROR)<< "Failed to load SELinux policy from" <

}return true;

}

这个函数主要做三件事:

1.通过selinux_find_precompiled_split_policy函数检查系统中是否存在预编译的CIL格式的策略文件。CIL格式的策略文件存在三种类型系统所要用的,

厂商要使用的和帮助厂商实现向前兼容。

2.如果已经存在预编译好的策略文件就直接进行加载,如果不存在则调用secilc,对策略文件进行编译。

3.获取到策略文件后调用selinux_android_load_policy_from_fd函数将策略文件添加到内核的LSM中。这个函数是libselinux中的

在src/external/selinux/libselinux/src/android/android_platform.c中

int selinux_android_load_policy_from_fd(int fd, const char *description)

{intrc;structstat sb;void *map =NULL;static int load_successful = 0;/** Since updating policy at runtime has been abolished

* we just check whether a policy has been loaded before

* and return if this is the case.

* There is no point in reloading policy.*/

if(load_successful){

selinux_log(SELINUX_WARNING,"SELinux: Attempted reload of SELinux policy!/n");return 0;

}

set_selinuxmnt(SELINUXMNT);if (fstat(fd, &sb) < 0) {

selinux_log(SELINUX_ERROR,"SELinux: Could not stat %s: %s\n",

description, strerror(errno));return -1;

}

map= mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);if (map ==MAP_FAILED) {

selinux_log(SELINUX_ERROR,"SELinux: Could not map %s: %s\n",

description, strerror(errno));return -1;

}

rc=security_load_policy(map, sb.st_size);if (rc < 0) {

selinux_log(SELINUX_ERROR,"SELinux: Could not load policy: %s\n",

strerror(errno));

munmap(map, sb.st_size);return -1;

}

munmap(map, sb.st_size);

selinux_log(SELINUX_INFO,"SELinux: Loaded policy from %s\n", description);

load_successful= 1;return 0;

}

该函数会将SEAndroid的策略添加到LSM中。

1.判断之前是否加载过,如果加载过则不再加载

2.将策略文件添加到内存中

3.调用security_load_policy将安全策略添加到LSM中。security_load_policy在src/external/selinux/libselinux/src/load_policy.c

int security_load_policy(void *data, size_t len)

{charpath[PATH_MAX];intfd, ret;if (!selinux_mnt) {

errno=ENOENT;return -1;

}

snprintf(path,sizeof path, "%s/load", selinux_mnt);

fd= open(path, O_RDWR |O_CLOEXEC);if (fd < 0)return -1;

ret=write(fd, data, len);

close(fd);if (ret < 0)return -1;return 0;

}

selinux_mnt最终的值为/sys/fs/selinux或者为/selinux.

判断selinux_mnt是否存在,如果存在就打开该文件将安全策略写入到该文件中。其实将安全策略加载到LSM中就是将安全策略写入到该文件中,从此内核和selinux

的交互就是通过该文件。

再看selinux_load_monolithic_policy()

static boolselinux_load_monolithic_policy() {

LOG(VERBOSE)<< "Loading SELinux policy from monolithic file";if (selinux_android_load_policy() < 0) {

PLOG(ERROR)<< "Failed to load monolithic SELinux policy";return false;

}return true;

}

可以看到会调用selinux_android_load_policy(),该函数在src/external/selinux/libselinux/src/android/android_platform.c中

intselinux_android_load_policy()

{int fd = -1;

fd= open(sepolicy_file, O_RDONLY | O_NOFOLLOW |O_CLOEXEC);if (fd < 0) {

selinux_log(SELINUX_ERROR,"SELinux: Could not open %s: %s\n",

sepolicy_file, strerror(errno));return -1;

}int ret =selinux_android_load_policy_from_fd(fd, sepolicy_file);

close(fd);returnret;

}

sepolicy_file的值为/sepolicy,该函数主要就是打开该文件,之后调用selinux_android_load_policy_from_fd这个函数。

2.Security Server

Security Server是用来保护用户空间的资源和访问内核空间对象的安全上下文的。它由应用程序安装服务PackageManagerService,应用程序安装守护进程

Installd,创建应用程序进程Zygote,以及init进程。

PackageManagerService和Installd负责创建app的数据目录。在创建数据目录时,PackageManagerService会根据包名或者签名查询mac_permission.xml文件

找到与之对应的seinfo,之后将seinfo传递给Installd,Installd根据libselinux提供的selable_lookup查询seapp_contexts文件得到与之对应的类型,设置

该app数据目录的安全上下文。

当ActivityManagerService向Zygote发出创建app进程的请求的时候,ActivityManagerService会去PackageManagerService找到对应的seinfo,并且将其传递

给Zygote,Zygote根据libselinux提供的setlabel_lookup找到对应的类型,设置该app进程的安全上下文。

在系统启动的时候,init进程会维护一段内存空间存放系统属性,并且启动Property服务,Property提供Socket接口,供其他进程访问系统属性。当其他进程访

问系统属性的时候,Property会获取到该进程的安全上下文,之后会去property_contexts,查询该属性的安全上下文,以此来判断该进程是否可以访问该属性。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/324219
推荐阅读
相关标签
  

闽ICP备14008679号