SElinux是一个linux安全增强功能,其允许用户和管理员对访问控制有更多的控制权。
访问控制可以做到哪个用户可能访问哪些资源的级别。这些资源可以来自文件。标准的Linux存取控制,如文件权限(-rwxr-xr-x)由用户或用户运行的程序修改。相反的,SELinux 的存取权限由系统加载时的policy确定,这个policy不能随意更改。
SELinux同样提供了细粒度的存取控制,不仅仅是对文件的读写执行权限,还可以指定指定谁可以unlink,仅仅append以及移动文件等操作。SELinux同样可以指定除了文件意外的访问权限,如网络资源和进程间通信等。
SELinux是linux上的MAC(mandatory access control)强制访问控制。
SELinux基于类型增强模型,类型指的是资源或者应用程序分类方法。如用户home目录的文件被标签为user_home_t。user_home_t就是类型,这意味这policy对用户家目录下的所有文件起作用。
运行的应用程序同样有标签,如web浏览器也许是firefox_t。类型增强允许用户指定应用程序标签可以访问哪些资源标签。如下SELinux允许应用程序访问一些资源:
allow firefox_t user_home_t : file { read write };
[root@localhost ~]# getenforce
Enforcing
# setenforce 0
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支持网络服务。
DAC是标准linux的Discretionary Access Control。
提供了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由
selinux_android_restorecon_pkgdir
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调用
这是SE针对安卓的核心模块。主要是若干te文件。由于SELinux从安卓5.1开始已经强制使用了,涉及到SELinux相关的问题一般修改这里文件。最终这个目录经过m4编译成
./obj/ETC/sepolicy_intermediates/policy.conf
这个policy.conf大部分内容是te(Type Enforcement)文件以及系统相关文件的直接拷贝。
JNI-增加SELinux支持函数,如isSELinuxEnabled和setFSCreateCon。
SELinux java 类以及方法定义。
检查Zygote连接内容。
package manager和墙纸服务的文件权限
安装/运行是MMAC。
SELinux对服务toolbox的支持(load_policy,runcon)
SELinux系统初始化支持(init,init.rc)
SELinux对auditing avc(auditd)
# 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
android平台,SEAndroid的初始化由进程的祖先init的main函数完成,相关代码如下:
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内容给目录打标签。
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相关内容
external/libselinux/src/android.c
int selinux_android_load_policy(void)
{
//src/policy.h:16:#define SELINUXMNT "/sys/fs/selinux",挂载点
const char *mnt = SELINUXMNT;
用户空间进程可以读写/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;
}
init通过mmap的方式,将sepolicy文件传递给kernel。init使用了libselinux提供的API完成相关操作。而libselinux则是通过/sys/fs/selinux下的文件来完成和Kernel中SELinux模块的交互。
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();
}
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;
}
加载/file_contexts文件,和文件安全label有关。prop和此类似。
int restorecon(const char* pathname)
{
return selinux_android_restorecon(pathname, 0);
}
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);