SElinux是一个linux安全增强功能,其允许用户和管理员对访问控制有更多的控制权。
访问控制可以做到哪个用户可能访问哪些资源的级别。这些资源可以来自文件。标准的Linux存取控制,如文件权限(-rwxr-xr-x)由用户或用户运行的程序修改。相反的,SELinux 的存取权限由系统加载时的policy确定,这个policy不能随意更改。
SELinux同样提供了细粒度的存取控制,不仅仅是对文件的读写执行权限,还可以指定指定谁可以unlink,仅仅append以及移动文件等操作。SELinux同样可以指定除了文件意外的访问权限,如网络资源和进程间通信等。
SELinux是linux上的MAC(mandatory access control)强制访问控制。
SElinux如何工作
SELinux基于类型增强模型,类型指的是资源或者应用程序分类方法。如用户home目录的文件被标签为user_home_t。user_home_t就是类型,这意味这policy对用户家目录下的所有文件起作用。
运行的应用程序同样有标签,如web浏览器也许是firefox_t。类型增强允许用户指定应用程序标签可以访问哪些资源标签。如下SELinux允许应用程序访问一些资源:
allow firefox_t user_home_t : file { read write };
查看SELinux状态
[root@localhost ~]# getenforce
Enforcing
SELinux 禁止
# setenforce 0
可以修改/etc/selinux/config文件使得SELinux进入permissive或者disabled的模式
SELinux核心组件
SELinux核心组件执行enforcement 安全策略是的流程如下图。
1.一个请求/获取资源/权限的subject必须存在。
2.一个对象管理器,其知道对特定资源需要的权限,
3.Security Server根据安全规则对请求的访问做安全决策
4.Security Policy描述了使用SELinux的policy语言描述的规则
5.Access Vector Cache(AVC)通过缓存安全策略以提高系统性能。
上图从下到上是:
a)当前security server嵌入在linux kernel空间,policy则由libselinux库提供的若干函数从用户空间取得。
OM(object manager)和AVC(access vector cache)驻留在内核空间和用户空间。
内核空间
OM服务于内核服务如文件,目录,套接字,进程间通信等,由嵌入SELinux子系统的Linux Security Module(LSM)的hook函数提供。内核空间的AVC用于缓存加速基于内核OM的访问速度。
用户空间
应用程序或用户空间服务的MAC控制。
b)SELinux 安全策略(上图右侧),其支持/etc/selinux目录下的配置文件。这个目录包括了SELinux主要配置文件。
c)支持模块策略,
d)Policy source是必须的,可以有三种基本方法提供。
i)作为SELinux Policy Language所写的源码。
ii)使用Reference Policy支持高级宏定义安全规则。这是当前SELinux采用的标准方法。
iii)使用CIL(common Intermediate Language)
e)为了能够编译连接规则源文件,以及加载安全服务需要许多工具(上图上半部分)
f)为了使得管理员可以管理策略,SELinux环境和标签文件系统,工具以及修改的linux 命令被使用。
g)为了确保安全事件被记录,audit 服务用于捕捉违反规则的信息。
h)SELinux支持网络服务。
有SELinux情况时的系统调用
DAC是标准linux的Discretionary Access Control。
安卓下的SELinux
external/libselinux
提供了SELinux用户空间函数库。
selinux_android_setcontext
当使用setcon加载应用程序时设置正确的上下文domain,seapp_contexts文件内容用于计算正确的内容。当frameworks/base/core/jni/com_android_internal_o
s_Zygote.cpp创建新进程以及system/core/run-as/run-as.c会调用该接口。
selinux_android_setfilecon
使用setfilecon应用的目录/文件,使用到seapp_contexts,这个函数在package install时被用到。
install()和make_user_data函数被frameworks/native/cmds/installd/commands.c调用
selinux_android_restorecon
selinux_android_restorecon_pkgdir
这些函数基于file_context或者seapp_context文件标记文件和目录。他们调用selinux_android_restorecon_common()重新标记请求的文件和目录。
selinux_android_restorecon由
- frameworks/native/cmds/installd/installd.c安装新app时使用。
- frameworks/base/core/jni/android_os_SELinux.cpp的native_restorecon方法
- frameworks/native/cmds/dumpstate/utils.c
selinux_android_restorecon_pkgdir
- frameworks/native/cmds/installd/commands.c的restorecon_data()和make_user_data()函数使用。
selinux_android_seapp_context_reload
当package installer加载后,为frameworks/native/cmds/installd/installd.c加载seapp_contexts
selinux_android_load_policy
当SELinux是能时挂载SELinux文件系统,然后调用selinux_android_reload_policy将policy加载到内核。由system/core/init/init.c初始化SELinux时使用。
selinux_android_reload_policy
重新加载policy到内核空间,在设置完selinux.reload_policy属性后由system/core/init/init.c调用selinux_reload_policy()。
selinux_android_use_data_policy
system/core/init/init.c调用其决定哪个policy目录加载property_contexts。
selinux_status_updated,is_selinux_enabled,
用于检查SELinux环境是否有任何改变
selinux_check_access
用于检查是否有权限
selinux_label_open;selabel_lookup;selinux_android_file_context_handle;selinux_android_prop_context_handle;setfilecon;setfscreatecon
标签管理
selinux_lookup_best_match
当ueventd创建设备节点时,由system/core/init/devices.c调用
/external/sepolicy
这是SE针对安卓的核心模块。主要是若干te文件。由于SELinux从安卓5.1开始已经强制使用了,涉及到SELinux相关的问题一般修改这里文件。最终这个目录经过m4编译成
./obj/ETC/sepolicy_intermediates/policy.conf
这个policy.conf大部分内容是te(Type Enforcement)文件以及系统相关文件的直接拷贝。
- sepolicy的重头工作是编译sepolicy安全策略文件。这个文件来源于众多的te文件,初始化相关的文件(initial_sid,initial_sid_context,users,roles,fs_context等)。
- file_context:该文件记载了不同目录的初始化SContext,它是和标签相关的。
- seapp_context:和Android中的应用程序打标签有关。
- property_contexts:和Android系统中的属性服务(property_service)有关,它为各种不同的属性打标签。
frameworks/base
JNI-增加SELinux支持函数,如isSELinuxEnabled和setFSCreateCon。
SELinux java 类以及方法定义。
检查Zygote连接内容。
package manager和墙纸服务的文件权限
安装/运行是MMAC。
system/core
SELinux对服务toolbox的支持(load_policy,runcon)
SELinux系统初始化支持(init,init.rc)
SELinux对auditing avc(auditd)
kernel LSM/SELinux支持
- LSM 位于binder的hooks(drivers/staging/android/binder.c,include/linux/security.h)
- capability支持(security/capability.c)
- LSM安全模块的hook(security/security.c).
- binder对象类和权限(security/selinux/include/classmap.h);权限检查security/selinux/hooks.c.
其它信息
- selinux版本信息,external/sepolicy/Android.mk
# SELinux policy version.
# Must be <= /sys/fs/selinux/policyvers reported by the Android kernel.
# Must be within the compatibility range reported by checkpolicy -V.
POLICYVERS ?= 30
安卓平台SEAndroid
init
android平台,SEAndroid的初始化由进程的祖先init的main函数完成,相关代码如下:
<system/core/init/init.cpp>
main()
{
process_kernel_cmdline();
// Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
selinux_initialize(is_first_stage);
restorecon("/init")
// These directories were necessarily created before initial policy load
// and therefore need their security context restored to the proper value.
// This must happen before /dev is populated by ueventd.
INFO("Running restorecon...\n");
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon_recursive("/sys");
}
main函数比较关键的两点是:
1.调用selinux_initialize(is_first_stage)初始化SELinux Android。
2.restore context,根据file_contexts内容给目录打标签。
<init.cpp>
static void selinux_initialize(bool in_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);
//如果disabled,直接返回,但是5.1之后AOSP强制enforcing,一般厂商也是测试基于enforcing模式的。
if (selinux_is_disabled()) {
return;
}
if (in_kernel_domain) {
INFO("Loading SELinux policy...\n");
//加载sepolicy
if (selinux_android_load_policy() < 0) {
ERROR("failed to load policy: %s\n", strerror(errno));
security_failure();
}
//设置SELinux模式,disabled,permissive,enforcing,这个可以在parse_cmdline中进行设置。
bool is_enforcing = selinux_is_enforcing();
security_setenforce(is_enforcing);
if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) {
security_failure();
}
NOTICE("(Initializing SELinux %s took %.2fs.)\n",
is_enforcing ? "enforcing" : "non-enforcing", t.duration());
} else {
//permissive模式,违规给出warning并记录日志,但依然放行,enforcing违规是不放行的。
selinux_init_all_handles();
}
}
该函数主要完成两件事情:
1.selinux_android_load_policy加载sepolicy
2.selinux_init_all_handles初始化file_context,seapp_context以及property_context相关内容
selinux_android_load_policy
external/libselinux/src/android.c int selinux_android_load_policy(void) {
const char *mnt = SELINUXMNT;//src/policy.h:16:#define SELINUXMNT "/sys/fs/selinux",挂载点
int rc;
//src/policy.h:20:#define SELINUXFS "selinuxfs"
rc = mount(SELINUXFS, mnt, SELINUXFS, 0, NULL);
//sys/fs/selinux为userspace和kernel的SELinux模块交互通道
set_selinuxmnt(mnt);
//加载selinux policy。
return selinux_android_load_policy_helper(false);
}
用户空间进程可以读写/sys/fs/selinux的各个文件或其中的子目录来通知kernel中的SELinux完成相关的操作。init通过mmap的方式,将sepolicy文件传递给kernel。init使用了libselinux提供的API完成相关操作。而libselinux则是通过/sys/fs/selinux下的文件来完成和Kernel中SELinux模块的交互。static int selinux_android_load_policy_helper(bool reload) { int fd = -1, rc; struct stat sb; void *map = NULL; int old_policy_index = policy_index; /* * If reloading policy and there is no /data policy or * that /data policy has the wrong version and our prior * load was from the / policy, then just return. * There is no point in reloading policy from / a second time. */ set_policy_index(); if (reload && !policy_index && !old_policy_index) return 0; //sepolicy_file指向的是sepolicy文件的路径,即root目录下的sepolicy文件。 fd = open(sepolicy_file[policy_index], O_RDONLY | O_NOFOLLOW); if (fd < 0) { selinux_log(SELINUX_ERROR, "SELinux: Could not open sepolicy: %s\n", 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", sepolicy_file[policy_index], strerror(errno)); close(fd); return -1; } //将sepolicy文件加载到内核中 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); close(fd); return -1; } munmap(map, sb.st_size); close(fd); selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", sepolicy_file[policy_index]); return 0; }
selinux_init_all_handles
<init.cpp> static void selinux_init_all_handles(void) { sehandle = selinux_android_file_context_handle(); selinux_android_set_sehandle(sehandle); sehandle_prop = selinux_android_prop_context_handle(); }
创建并设置文件handler,创建prophandler。加载/file_contexts文件,和文件安全label有关。prop和此类似。struct selabel_handle* selinux_android_file_context_handle(void) { struct selabel_handle *sehandle; set_policy_index(); sehandle = selabel_open(SELABEL_CTX_FILE, &seopts[policy_index], 1); if (!compute_contexts_hash(seopts, fc_digest)) { selabel_close(sehandle); return NULL; } return sehandle; }
restorecon
<system/core/init/util.cpp> int restorecon(const char* pathname) { return selinux_android_restorecon(pathname, 0); } <external/libselinux/src/android.c> int selinux_android_restorecon(const char *file, unsigned int flags) { return selinux_android_restorecon_common(file, NULL, -1, flags); } 设置系统属性 // Labeling successful. Mark the top level directory as completed. if (setrestoreconlast && !nochange && !error) setxattr(pathname, RESTORECON_LAST, fc_digest, sizeof fc_digest, 0);