SEAndroid安全模块简介

简介

了解SEAndroid之前需要了解一下什么是SELinux,其全称为Security Enhanced Linux,即安全增强Linux。它是一种强制访问控制(MAC)体系。用来限制进程和用户对系统资源的访问。相对于自主访问控制(Discretionary Access Control),简称DAC,这种较粗的访问控制,MAC可以更细粒度的控制访问权限。DAC主要根据文件所属的分组来限制对资源的访问,如果用户获取了root权限,在可以对系统做任何事。MAC可以让用户只访问那些有明确授权的资源。在系统中通常先DAC检查,然后再MAC检查 而SEAndroid是SELinux在Android系统中的实现,用来提高Android的整体安全性。Android并没有全部实现SELinux的功能,而是对其进行了定制,以适应android的设备。

SELinux简介

LSM

了解SELinux之前需要了解LSM和SELinux之间的关系。 LSM(Linux Security Modules)是Linux内核中的一个框架,用于实现安全模块,LSM 不是一个具体的安全策略,而是一个框架,为各种安全模块提供了一个标准的接口。而SELinux正是具体的安全模块实现,它使用LSM提供的接口来实现一种强化的安全策略。除了SELinux还有其他安全模块也使用了LSM框架。LSM使不同的安全模块能够与核心内核交互,它提供了一组钩子(hooks),允许安全模块在特定的系统调用和内核操作点上执行自定义的安全策略。不同的安全模块可以根据需要拦截和审查不同的系统调用,从而提供额外的安全性层。 LSM架构图: LSM hook: hook函数的实现在LSM安全模块中。LSM提供了一套hook函数,完成DAC检查后,调用这些hook函数可以完成更严格的安全检查。hook函数分散在内核的各个地方,每个hook函数可以认为是一种或者多种对客体资源的访问限制。

SELinux

SELinux是内置于Linux内核中的Linux安全模块。SELinux由可加载的策略规则驱动。当发生与安全相关的访问时,例如当进程尝试打开文件时,SELinux会在内核中拦截该操作。如果SELinux策略规则允许该操作,则该操作将继续,否则,该操作将被阻止并且进程会收到错误。

SELinux决策(例如允许或禁止访问)会被缓存。该缓存称为访问向量缓存 (AVC)。使用这些缓存的决策时,需要检查的SELinux策略规则较少,从而提高了性能。如果DAC规则首先拒绝访问,SELinux策略规则将不起作用。 SELinux架构图:

组成说明:

1.SELinux虚拟文件系统:SELinux内核和用户进程之间进行数据交换的接口。

2.安全服务模块:它的作用是根据安全策略来生成访问规则,生成的规则保存在访问向量缓存中,以提高速度。

3.访问向量缓存(Access Vector Cache): 为了提高系统性能,缓存了访问规则。如果缓存不能解决客体服务器的请求,则会把请求提交到安全模块中,由安全模块来响应请求,然后更新缓存。

5.主体:通常指用户或者用户运行的进程。

4.客体管理器,代表某个内核子系统,如文件系统,内存管理系统,进程管理系统等。它们是各种客体资源的管理者。客体管理器接收到来自用户进程(主体)的访问请求后,通过LSM钩子调用SELinux安全模块来做出允许访问或者拒绝访问的决定。

安全上下文(SELinux Context)

安全上下文,用于标识和控制Linux系统中的资源(如进程、文件、套接字)以及这些资源之间的访问权限。每个资源都被赋予一个SELinux上下文,这个上下文由多个部分组成,它们描述了资源的类型和安全策略。资源的安全上下文决定了它们的安全策略和可访问性。

1.查看安全上下文的命令:

查看文件的安全上下文命令:ls -Z (例:u:object_r:cgroup:s0)

查看进程的安全上下文命令:ps -Z (例:init进程安全上下文:u:r:init:s0)

2.安全上下文格式:

user:role:type:level

user: 指定用户,SELinux中通常有3种类型:user_u:代表普通用户,权限受限制。system_u:代表系统级别的进程。root: 代表root用户。注意,这里的user定义和Linux的用户有一定的相关性,但不是一个概念。

role:指定角色,不同的角色有不同的权限。文件,目录,socket等客体的角色通常是object_r, 主体进程的角色通常是r。一个用户可以有多个角色,但是同一时间,只能使用一个。

type: 定义主体和客体所属的类型。对进程而言,它的类型也称为domain。type是安全上下文中最重要的部分。SELinux通过policy文件定义类型,如:"cgroup"的定义:type cgroup, fs_type, mlstrustedobject。"init" 定义:type init, domain。客体和主体的类型定义都需要通过type语句完成。

type语句的语法如下:

type 类型名称 [,属性];

其中属性值都是预定义好的,有特别的含义。如:domain代表域,通常主体的type具体domain属性,因此,我们也把主体的type称为domain。fs_type表示文件,通常用于客体的类型(type)中。SELinux中大概定义了几十种属性。

level: 定义安全等级。只用于mls策略中。可能的值s0 ~ s15

访问规则

除了主体和客体的安全上下文外,SELinux系统还需要定义主体对客体的访问规则。它也是所有规则定义中规模最大的一部分。如:"init" 访问规则的定义:

allow init unlabeled:filesystem mount;

允许"init"域加载没有定义安全上下文的文件系统。

allow init {fs_type dev_type file_type}:dir_file_class_set relabelto;

允许"init"域重新设置文件,设备和文件系统的安全上下文。

allow init kernel:security load_policy;

允许"init"域为kernel装载policy

注意:我们只要把另一个进程(非init进程)的安全上下文的type字段指定为"init",它就拥有了这里描述的能力。

allow语句,表示允许主体对客体执行进行的操作

allow语句定义:

allow source_type target_type : class Permission

source_type: 通常是某种属性为domain的类型(type),代表主体。

target_type: 允许访问的客体的类型(type)。target_type可以同时指定多个,如前面的第2个例子。

class: 客体类别。允许访问的客体的类型。客体的目标类型(target_type)可能会涵盖较广的范围。客体类别(class)可以对客体目标类型(target_type)进行限制。如前面第1个例子,目标类型是unlabeled,它可以代表文件,目录以及文件系统,通过filesystem对它进行了限制,因此这个规则只能代表文件系统。

Permission: 指定主体可以对客体进行操作的种类。SELinux中定义了大量的许可(Permission)。

allow语句,是SELinux中访问控制规则之一,目前SELinux策略语言支持4类访问控制规则。

其他访问规则:

dontaudit: 表示不记录某条违反规则的决策信息。

auditallow: 记录某项决策信息。通常SELinux只记录失败的决策信息,应用这条规则后,将会记录成功的决策信息。

neverallow: 表示不允许主体对客体的执行指定的操作。

SELinux的安全策略的实现是通过比较安全上下文中定义的类型(type)来完成的。这种方法称为类型强制(Type Enforcement, 简称TE)。

RBAC(一种基于角色的访问控制):

在类型强制的基础上,SELinux也提供了一种基于角色的访问控制。这是一种简化的管理的安全模式。系统中先创建出不同的角色,这些角色拥有不同的授权,然后通过给用户指定不同的角色来给用户授权。对于一个系统而言,角色的数量和权限相对比较固定,而用户却很容易发生变化,对于系统管理人员,他可以预先定义好角色并分配权限,当有新用户加入系统时,只需要把用户加入到一种或者几种角色中就可以了,大大简化了管理的负担。

类型强制(TE)模式,提供了非常细粒度的权限管理方式,但是如果每个用户都直接通过TE策略来定义权限,将会让系统维护的工作量变的非常巨大。角色相当于对系统一部分权限的抽象。这样即能提供细粒度的访问控制,又没有增加管理难度。

下面是以上提到的各种语法关系图,帮助理解:

域转移(Domain Transitions)

域转移是指把进程从一个域切换到另外一个域。

Linux中当以普通用户的身份登录系统时,要执行的一些超级用户才能完成的工作,必须使用su命令。SELinux如何让普通用户执行特殊权限操作?通过域转移来实现。

以su为例说明域转移:

su可执行文件的安全上下文:

-rwsr-sr-x root root u:object_r:su_exec:s0 su

其中su的安全上下文的type为su_exec,系统中关于su_exec的规则定义:

宏:domain_auto_trans(shell, su_exec, su)

最终展开宏:

allow shell su_exec:file {getattr open read execute}; //容许shell域对类型为su_exec的文件执行getattr open read execute 4种操作。也就是允许shell进程打开su文件并执行

allow shell su:process transition; //容许shell域对类型为su的进程执行transition操作。也就是允许su进程进行域切换操作。

allow su su_exec:file {entrypoint read execute}; //容许su域对类型为su_exec的文件执行entrypoint read execute 3种操作。

allow su shell:process sigchld; //容许su域对类型为shell的进程执行sigchld操作

dontaudit shell su:process noatsecure; // 如果域shell对类型为su的进程执行noatsecure操作失败了无需记录

allow shell su:process { siginh rlimitinh }; //容许shell域对类型为su的进程执行siginh rlimitinh 操作

type_transition shell su_exec:process su; //当shell域的进程启动一个类型su_exec的可执行文件时,把新的进程的域切换到su域。

从fork到exec中间需要多次权限检查,所以需要多个规则定义。这些定义大部分是为启动一个进程准备的,只有最后一条type_transition才是进行域转移的定义。

SEAndroid简介

SEAndroid(Security Enhancements for Android)安全增强型Android,即是把SELinux移植到android中,它是SELinux在android中的实现,现在已成为Android的核心部分。 下图为SEAndroid的基本框架图

策略文件

external/selinux/libselinux/目录下放了libselinux库的源码,这个库文件提供了一些函数,用来帮助用户进程使用SELinux的功能。

在system/sepolicy/private目录下定义了很多策略文件和一些安全上下文

1.角色定义文件roles_decl:

role r

定义SELinux系统的角色。

SEAndroid只定义了一种角色r。

2.用户定义文件users:

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

用来定义用户。SEAndroid中只定义了一种u。

3.Class定义文件security_classes

c 复制代码
class security
class process
class system
class capability

#file-related classes
class filesystem
class file
class anon_inode
class dir
class fd
class lnk_file
class chr_file
class blk_file
class sock_file
class fifo_file
...

security_classes定义了所有系统中用到的class.

4.操作定义文件access_vectors

c 复制代码
...
common file
{
	ioctl
	read
	write
	create
	getattr
	setattr
	lock
	relabelfrom
	...
	
class filesystem
{
	mount
	remount
	unmount
	getattr
	relabelfrom
	relabelto
	associate
	quotamod
	quotaget
	watch
}
...

allow 语句的最后一项为允许的操作,所有操作文件都在文件access_vectors中定义。

access_vectors文件通过2种方式定义操作:

通过common语句,这种方式定义的操作是一种公共的操作,没有限定哪种类别的客体可以使用,而且可以被继承。 通过class语句,class语句后面的名称必须是客体限制类别。这意味着通过class语句定义的操作只能使用在相应的客体限制类别中。class语句可以继承common语句中定义的操作。

5.file_contexts文件

file_contexts文件保存的是系统中所有文件的安全上下文

c 复制代码
# Root
/                   u:object_r:rootfs:s0

# Data files
/adb_keys           u:object_r:adb_keys_file:s0
/build\.prop        u:object_r:rootfs:s0
/default\.prop      u:object_r:rootfs:s0
/fstab\..*          u:object_r:rootfs:s0
/init\..*           u:object_r:rootfs:s0
/res(/.*)?          u:object_r:rootfs:s0
/selinux_version    u:object_r:rootfs:s0
/ueventd\..*        u:object_r:rootfs:s0
/verity_key         u:object_r:rootfs:s0

# Executables
/init               u:object_r:init_exec:s0
/sbin(/.*)?         u:object_r:rootfs:s0
...

6.property_contexts文件

保存的系统中所有Android属性的安全上下文

c 复制代码
# 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_dns_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
...

以上介绍了一些常用的策略文件和安全上下文。在system/sepolicy/private目录下还有大量的文件,有兴趣的可以自己查看。

下面再看看android中如何使用SELinux

Init进程设置SELinux的Policy

这个步骤很简单,事实上就是加载策略文件,然后将文件写入到load文件中。SELinux虚拟目录/sys/fs/selinux下的"load"文件,是内核和进程通信的接口,向它写入数据能传递到内核中。

具体代码如下:

system/core/init/main.cpp 文件是 Android 系统中 init 进程的主要入口点。

c 复制代码
int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#elif __has_feature(hwaddress_sanitizer)
    __hwasan_set_error_report_callback(AsanReportCallback);
#endif
    // Boost prio which will be restored later
    setpriority(PRIO_PROCESS, 0, -20);
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }

    if (argc > 1) {
        if (!strcmp(argv[1], "subcontext")) {
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();

            return SubcontextMain(argc, argv, &function_map);
        }

        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv); // 设置selinux
        }

        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }

    return FirstStageMain(argc, argv);
}

在main方法中调用SetupSelinux() 方法。

c 复制代码
int SetupSelinux(char** argv) {
    ....    
    //设置 selinux_callback cb
    SelinuxSetupKernelLogging();

    // TODO(b/287206497): refactor into different headers to only include what we need.
    if (IsMicrodroid()) { // 是否是Microdroid系统。是则调用LoadSelinuxPolicyMicrodroid
        LoadSelinuxPolicyMicrodroid();
    } else { //非Microdroid系统
        LoadSelinuxPolicyAndroid();
    }

    SelinuxSetEnforcement();

    ....
    
    const char* path = "/system/bin/init";
    const char* args[] = {path, "second_stage", nullptr};
    execv(path, const_cast<char**>(args));

    // execv() only returns if an error happened, in which case we
    // panic and never return from this function.
    PLOG(FATAL) << "execv(\"" << path << "\") failed";

    return 1;
}

在这个方法中,先判断当前系统是否是Microdroid系统,如果是则调用LoadSelinuxPolicyMicrodroid()方法,如果不是则调用LoadSelinuxPolicyAndroid()。Microdroid系统提供了更好的隔离,多用户等特性。我们看看非Microdroid系统的情况:

c 复制代码
void LoadSelinuxPolicyAndroid() {
    MountMissingSystemPartitions();

    LOG(INFO) << "Opening SELinux policy";

    // Read the policy before potentially killing snapuserd.
    std::string policy;
    //将指定目录中的的策略文件读取出来,保存到policy字符串变量中
    ReadPolicy(&policy);

    auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();
    if (snapuserd_helper) {
        // Kill the old snapused to avoid audit messages. After this we cannot read from /system
        // (or other dynamic partitions) until we call FinishTransition().
        snapuserd_helper->StartTransition();
    }
    //将policy中保存的策略设置到系统中
    LoadSelinuxPolicy(policy);

    if (snapuserd_helper) {
        // Before enforcing, finish the pending snapuserd transition.
        snapuserd_helper->FinishTransition();
        snapuserd_helper = nullptr;
    }
}

该方法主要是读取指定目录中的策略文件,保存在policy变量中。然后调用LoadSelinuxPolicy()方法加载策略。LoadSelinuxPolicy方法中执行具体加载过程

c 复制代码
static void LoadSelinuxPolicy(std::string& policy) {
    LOG(INFO) << "Loading SELinux policy";
    //将挂载点路径存储在全局变量selinux_mnt中,以供程序的其他部分使用。
    set_selinuxmnt("/sys/fs/selinux");
    //存储策略数据到load文件中
    if (security_load_policy(policy.data(), policy.size()) < 0) {
        PLOG(FATAL) << "SELinux:  Could not load policy";
    }
}
//security_load_policy 方法
int security_load_policy(const void *data, size_t len)
{
	char path[PATH_MAX]; //用于保存挂载点路径
	int fd, 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;
}

LoadSelinuxPolicy 方法首先设置"/sys/fs/selinux"路径到全局变量中。再调用security_load_policy方法。在security_load_policy方法中,根据之前保存的全局变量selinux_mnt中的值生成path路径/sys/fs/selinux/load。接着打开load文件,将之前读取的策略数据data,写入到load文件中。然后关闭文件。从而实现SELinux策略的加载。

Init进程初始化安全上下文

策略文件加载完成后,再看看安全上下文的初始化过程。 在上面的方法SetupSelinux()结尾的位置可以看到有段代码:

c 复制代码
const char* path = "/system/bin/init";
const char* args[] = {path, "second_stage", nullptr};
execv(path, const_cast<char**>(args));

execv为系统调用,表示加载指定路径的程序文件并运行。第一个参数为程序文件路径,第二个参数为一个字符指针数组,存储了命令行参数。一旦新程序开始执行,原始程序的代码将被替换,而新程序将运行在当前进程中。这段代码表示执行init程序,并且第2个参数为字符串"second_stage"。再次回到上面init的入口代码system/core/init/main.cpp源码中查看main方法,可以看到第2个参数为"second_stage",则会执行SecondStageMain()方法分支。进一步来探究一下SecondStageMain方法做了哪些事。

c 复制代码
int SecondStageMain(int argc, char** argv) {
    ...
        // Now set up SELinux for second stage.
    SelabelInitialize();
    SelinuxRestoreContext();
    ...

SecondStageMain函数很长,设置SELinux主要看其中2个方法SelabelInitialize和SelinuxRestoreContext。SelabelInitialize 主要是加载安全上下文,将信息设置到全局静态变量fc_sehandle中。SelinuxRestoreContext则是设置安全上下文。仔细来看一下函数具体内容:

c 复制代码
void SelabelInitialize() {
    sehandle = selinux_android_file_context_handle();
    //设置sehandle到静态变量fc_sehandle中
    selinux_android_set_sehandle(sehandle);
}

//selinux_android_file_context_handle函数
struct selabel_handle* selinux_android_file_context_handle(void)
{
   //定义的MAX_CONTEXT_PATHS值为5
   //file_contexts保存安全上下文的地址数组
	const char* file_contexts[MAX_CONTEXT_PATHS];
	struct selinux_opt opts[MAX_CONTEXT_PATHS + 1];
	int npaths, nopts;
   // file_context_paths保存了安全上下文的路径,类型为path_alts_t
   //find_existing_files这个方法的作用是将file_context_paths定义的安全上下的可用路径加载到file_contexts数组中
	npaths = find_existing_files(&file_context_paths, file_contexts);
	paths_to_opts(file_contexts, npaths, opts);

	opts[npaths].type = SELABEL_OPT_BASEONLY;
	opts[npaths].value = (char *) 1;
	nopts = npaths + 1;

	return initialize_backend(SELABEL_CTX_FILE, "file", opts, nopts);
}
//file_context_paths的定义和初始化
static const path_alts_t file_context_paths = { .paths = {
	{
		"/system/etc/selinux/plat_file_contexts",
		"/plat_file_contexts"
	},
	{
		"/system_ext/etc/selinux/system_ext_file_contexts",
		"/system_ext_file_contexts"
	},
	{
		"/product/etc/selinux/product_file_contexts",
		"/product_file_contexts"
	},
	{
		"/vendor/etc/selinux/vendor_file_contexts",
		"/vendor_file_contexts"
	},
	{
		"/odm/etc/selinux/odm_file_contexts",
		"/odm_file_contexts"
	}
}};

SelabelInitialize 函数首先调用selinux_android_file_context_handle函数。 selinux_android_file_context_handle函数中相关方法和变量说明:

file_context_paths是path_alts_t结构体,它保存了安全上下文的路径,上面代码中可以看到其中"/system/etc/selinux/plat_file_contexts" 为安全上下文的保存路径,"/plat_file_contexts"也是安全上下文的路径。如果"/system/etc/selinux/plat_file_contexts"不存在,则使用"/plat_file_contexts",如果存在,则"/plat_file_contexts"将被忽略。

find_existing_files方法主要是将file_context_paths中保存的安全上下文列表中可访问的安全上下文路径保存到file_contexts中。其函数返回值是保存的安全上下文路径的个数,并将返回值保存到npaths变量中。

paths_to_opts函数作用很简单,将file_contexts中的安全上下文路径保存到变量opts.value中。 opts类型为结构体selinux_opt。并将opts.type设置为SELABEL_OPT_PATH(定义的值为3)

函数最后调用initialize_backend函数, 其中主要调用了selabel_open函数,其他都是打印log

c 复制代码
struct selabel_handle* initialize_backend(
		unsigned int backend,
		const char* name,
		const struct selinux_opt* opts,
		size_t nopts)
{
		struct selabel_handle* sehandle;

		sehandle = selabel_open(backend, opts, nopts);
      ...
		return sehandle;
}

//selabel_open函数源码

struct selabel_handle *selabel_open(unsigned int backend,
				    const struct selinux_opt *opts,
				    unsigned nopts)
{
	struct selabel_handle *rec = NULL;
    
   // backend 传入的值是0, 如果initfuncs数组的大小为0,则退出函数
	if (backend >= ARRAY_SIZE(initfuncs)) {
		errno = EINVAL;
		goto out;
	}

	if (!initfuncs[backend]) {
		errno = ENOTSUP;
		goto out;
	}

	rec = (struct selabel_handle *)malloc(sizeof(*rec));
	if (!rec)
		goto out;

	memset(rec, 0, sizeof(*rec));
	rec->backend = backend;
	rec->validating = selabel_is_validate_set(opts, nopts);

	rec->digest = selabel_is_digest_set(opts, nopts, rec->digest);
   //backend为0,所以调用initfuncs数组的第一个函数:selabel_file_init
	if ((*initfuncs[backend])(rec, opts, nopts)) {
		selabel_close(rec);
		rec = NULL;
	}
out:
	return rec;
}

//initfuncs数组保存的是函数指针
static selabel_initfunc initfuncs[] = {
	CONFIG_FILE_BACKEND(selabel_file_init),
	CONFIG_MEDIA_BACKEND(selabel_media_init),
	CONFIG_X_BACKEND(selabel_x_init),
	CONFIG_DB_BACKEND(selabel_db_init),
	CONFIG_ANDROID_BACKEND(selabel_property_init),
	CONFIG_ANDROID_BACKEND(selabel_exact_match_init),//service init
	CONFIG_ANDROID_BACKEND(selabel_exact_match_init),//keyStore key init
};

//selabel_file_init函数源码
int selabel_file_init(struct selabel_handle *rec,
				    const struct selinux_opt *opts,
				    unsigned nopts)
{
	struct saved_data *data;

	data = (struct saved_data *)malloc(sizeof(*data));
	if (!data)
		return -1;
	memset(data, 0, sizeof(*data));

	rec->data = data;
	rec->func_close = &closef;
	rec->func_stats = &stats;
	rec->func_lookup = &lookup;
	rec->func_partial_match = &partial_match;
	rec->func_get_digests_all_partial_matches =
					&get_digests_all_partial_matches;
	rec->func_hash_all_partial_matches = &hash_all_partial_matches;
	rec->func_lookup_best_match = &lookup_best_match;
	rec->func_cmp = &cmp;
   //解析传入的文件
	return init(rec, opts, nopts);
}

文件的解析过程在init函数中。 selinux_android_file_context_handle函数最后返回selabel_handle指针。接着调用selinux_android_set_sehandle(sehandle)函数。将selabel_handle指针传入其中。selinux_android_set_sehandle函数比较简单,即将selabel_handle指针赋给静态指针变量fc_sehandle。解析出文件之后调用了SelinuxRestoreContext()函数,代码如下:

c 复制代码
void SelinuxRestoreContext() {
    LOG(INFO) << "Running restorecon...";
    selinux_android_restorecon("/dev", 0);
    selinux_android_restorecon("/dev/console", 0);
    selinux_android_restorecon("/dev/kmsg", 0);
    if constexpr (WORLD_WRITABLE_KMSG) {
        selinux_android_restorecon("/dev/kmsg_debug", 0);
    }
    selinux_android_restorecon("/dev/null", 0);
    selinux_android_restorecon("/dev/ptmx", 0);
    selinux_android_restorecon("/dev/socket", 0);
    selinux_android_restorecon("/dev/random", 0);
    selinux_android_restorecon("/dev/urandom", 0);
    selinux_android_restorecon("/dev/__properties__", 0);

    selinux_android_restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
    selinux_android_restorecon("/dev/dm-user", SELINUX_ANDROID_RESTORECON_RECURSE);
    selinux_android_restorecon("/dev/device-mapper", 0);

    selinux_android_restorecon("/apex", 0);
    selinux_android_restorecon("/bootstrap-apex", 0);
    selinux_android_restorecon("/linkerconfig", 0);

    // adb remount, snapshot-based updates, and DSUs all create files during
    // first-stage init.
    RestoreconIfExists(SnapshotManager::GetGlobalRollbackIndicatorPath().c_str(), 0);
    RestoreconIfExists("/metadata/gsi",
                       SELINUX_ANDROID_RESTORECON_RECURSE | SELINUX_ANDROID_RESTORECON_SKIP_SEHASH);
}

主要是调用selinux_android_restorecon函数,传入不同的路径参数。这个函数主要目的是为指定的路径设置安全上下文。首先查询fc_sehandle中保存的安全上下文,然后调用lgetfilecon(pathname, &oldsecontext)函数设置对应路径的安全上下文。

所以总结起来Init进程初始化安全上下文的过程,加载指定路径下的安全上下文保存在系统中,然后设置指定系统路径的安全上下文。

设置应用进程的安全上下文

应用程序如何设置安全上下文的。应用程序通过Zygote创建,在之前的文章<<Android源码阅读 --- Zygote>>中已经有介绍。zygote进程fork出进程后,会调用SpecializeCommon函数,这个函数很长,和设置安全上下文相关的代码如下:

c 复制代码
///SpecializeCommon函数
    ...
    if (selinux_android_setcontext(uid, is_system_server, se_info_ptr, nice_name_ptr) == -1) {
        fail_fn(CREATE_ERROR("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid,
                             is_system_server, se_info_ptr, nice_name_ptr));
    }
    ...
    
//selinux_android_setcontext函数:
int selinux_android_setcontext(uid_t uid,
			       bool isSystemServer,
			       const char *seinfo,
			       const char *pkgname)
{
	char *orig_ctx_str = NULL;
	const char *ctx_str = NULL;
	context_t ctx = NULL;
	int rc = -1;

	if (is_selinux_enabled() <= 0)
		return 0;
    
    //得到当前进程从 Zygote 进程继承的安全上下文
	rc = getcon(&orig_ctx_str);
	if (rc)
		goto err;

   //以 ctx_str 为模板创建一个新的安全上下文 
	ctx = context_new(orig_ctx_str);
	if (!ctx)
		goto oom;
   // 查找安全上下文
	rc = seapp_context_lookup(SEAPP_DOMAIN, uid, isSystemServer, seinfo, pkgname, ctx);
	if (rc == -1)
		goto err;
	else if (rc == -2)
		goto oom;

	ctx_str = context_str(ctx);
	if (!ctx_str)
		goto oom;

	rc = security_check_context(ctx_str);
	if (rc < 0)
		goto err;

	if (strcmp(ctx_str, orig_ctx_str)) {
	   //设置安全上下文
		rc = selinux_android_setcon(ctx_str);
		if (rc < 0)
			goto err;
	}

	rc = 0;
out:
	freecon(orig_ctx_str);
	context_free(ctx);
	return rc;
err:
	if (isSystemServer)
		selinux_log(SELINUX_ERROR,
				"%s:  Error setting context for system server: %s\n",
				__FUNCTION__, strerror(errno));
	else
		selinux_log(SELINUX_ERROR,
				"%s:  Error setting context for app with uid %d, seinfo %s: %s\n",
				__FUNCTION__, uid, seinfo, strerror(errno));

	rc = -1;
	goto out;
oom:
	selinux_log(SELINUX_ERROR, "%s:  Out of memory\n", __FUNCTION__);
	rc = -1;
	goto out;
}

seapp_context_lookup函数查找安全上下文,查找之前调用selinux_android_seapp_context_init函数,确保默认的安全上下文已经加载

c 复制代码
int seapp_context_lookup(enum seapp_kind kind,
				uid_t uid,
				bool isSystemServer,
				const char *seinfo,
				const char *pkgname,
				context_t ctx)
{
	// Ensure the default context files are loaded.
	selinux_android_seapp_context_init();
	return seapp_context_lookup_internal(kind, uid, isSystemServer, seinfo, pkgname, ctx);
}

在 selinux_android_seapp_context_init 中将应用程序的安全上下文保存在静态变量seapp_contexts中。接着调用seapp_context_lookup_internal函数,其中根据应用的签名,先得到对应的seinfo值。再比较seinfo和用户名得到domain的值。domain是安全上下文的关键项,得到它就等于得到了安全上下文。将其保存到ctx中,供后面的设置安全上下文使用。

调用完成seapp_context_lookup函数后,最后调用selinux_android_setcon函数设置进程的安全上下文。

c 复制代码
int selinux_android_setcon(const char *con)
{
	int ret = setcon(con);
	if (ret)
		return ret;
	/*
	  System properties must be reinitialized after setcon() otherwise the
	  previous property files will be leaked since mmap()'ed regions are not
	  closed as a result of setcon().
	*/
	return __system_properties_init();
}

在selinux_android_setcon函数中调用setcon(con)设置了当前进程的安全上下文,然后调用__system_properties_init()来重新初始化系统属性。这可以确保属性系统在安全上下文设置后能够正常工作。

源码路径:

system/core/init/main.cpp system/core/init/selinux.cpp

参考资料

本文根据《深入解析android5.0系统》内容,结合新的android系统代码做的读书总结。如有错误欢迎指正。

SEAndroid 在AOSP中的说明:

www.ndss-symposium.org/wp-content/...

SELinux Project Wiki:selinuxproject.org/page/Main_P...

相关推荐
程序猿阿越11 天前
Kafka源码(六)消费者消费
java·后端·源码阅读
zh_xuan17 天前
Android android.util.LruCache源码阅读
android·源码阅读·lrucache
魏思凡20 天前
爆肝一万多字,我准备了寿司 kotlin 协程原理
kotlin·源码阅读
白鲸开源24 天前
一文掌握 Apache SeaTunnel 构建系统与分发基础架构
大数据·开源·源码阅读
Tans51 个月前
Androidx Fragment 源码阅读笔记(下)
android jetpack·源码阅读
Tans51 个月前
Androidx Fragment 源码阅读笔记(上)
android jetpack·源码阅读
Tans51 个月前
Androidx Lifecycle 源码阅读笔记
android·android jetpack·源码阅读
凡小烦1 个月前
LeakCanary源码解析
源码阅读·leakcanary
程序猿阿越2 个月前
Kafka源码(四)发送消息-服务端
java·后端·源码阅读
CYRUS_STUDIO2 个月前
Android 源码如何导入 Android Studio?踩坑与解决方案详解
android·android studio·源码阅读