6.系统(system)

文件可以有多个,套接字也可以有多个,但系统类别的客体实例是全局性的,只有一个。system 类型之上的操作有获取 ipc 信息(ipc_info)、 syslog 相关操作(syslog_console、 syslog_ mod、 syslog_read)和内核模块相关操作(module_request)。虽然叫系统,却只涉及寥寥几个操作许可,让人感到有些奇怪。

(1)进程间通信

● ipc_info 读取系统 ipc 信息

具体来说,当系统调用 msgctl 的 cmd 参数为 IPC_INFO 或 MSG_INFO 时,或当系统调用semctl 的 cmd 参数为 IPC_INFO 或 SEM_INFO 时,或当系统调用 shmctl 的 cmd 参数为 IPC_INFO或 SHM_INFO 时, SELinux 会判断此操作许可。

(2) syslog

以下几个操作都和系统调用 syslog 相关。

● syslog_read:读取 kernel 日志消息。

● syslog_mod:清空 kernel 消息缓冲区。

● syslog_console:控制 kernel 日志打印到控制台。

(3) module

● module_request:加载内核模块。

7.安全(security)

SELinux 的设计者希望设计出一个完备的系统,对于自身的管理也要纳入到 SELinux 的管理体系中。所以 SELinux 引入了新的客体类别——安全。在安全类别上的操作都和 SELinux 自身管理有关。

这里有一个问题: SELinux 是如何管理自身的呢?因为 Linux 内核对增加系统调用非常慎重,所以 SELinux 没有引入新的系统调用,而是构建了一个文件系统——selinuxfs。在 Linux上增加文件系统是很容易的。用户态进程通过读写 selinuxfs 上的文件来实现对 SELinux 的管理。相应地, SELinux 规定了以下操作:

● check_context:查看安全上下文是否合法。

● compute_av:根据源安全上下文、目的安全上下文、客体类型计算访问许可。

● compute_create:输入源安全上下文、目的安全上下文、客体类型,根据 type_transition策略计算新安全上下文。

● compute_member:输入源安全上下文、目的安全上下文、客体类型,根据 type_member策略计算新安全上下文。

● compute_relabel:输入源安全上下文、目的安全上下文、客体类型,根据 type_change策略计算新安全上下文。

● compute_user:输入安全上下文和用户名,计算新的安全上下文。

● load_policy:加载安全策略。

● setbool:改变 SELinux 布尔变量值。

● setcheckreqprot:修改 SELinux 变量 checkreqprot 值。在执行 mmap 和 mprotect 系统调用时,若值为 0,执行来自内核的检验方法,若值为 1,执行来自应用的检验模式。内核的检查方法是将 read 和 exec 联系起来。

● setenforce:改变 SELinux 的 enforcement 状态(permissive 或 enforcing)。

● setsecparam:改变 AVC 参数。

8.能力

能力是进程的特权。从逻辑上讲,能力是进程的一种资源。如果能力可以是一种客体,那么进程的内存大小、进程的描述符数量、文件占用的磁盘块数量、用户同时拥有的进程数量都可以是客体。

如果把能力作为一种客体,那么对能力的操作应该是添加、删除、查看。但是这样设计没有任何意义。因为在 execve 系统调用中不会区分添加和删除;在 capset 系统调用中只允许删除能力,而删除能力本身不应被限制;查看进程的能力也没有什么安全问题,没有限制的必要。

SELinux 要套用它的标准逻辑“主体操作客体”。所以 SELinux 将能力定义为一种客体类别,将能力上的操作定义为具体的能力,如 cap_chown。这种设计带来了三个问题:

(1)具体的能力怎么能成为能力的操作呢?这不合逻辑。

(2)进程是主体,能力是客体,能力本身就是进程的一个属性,主体和客体的安全上下文总是一样的。

(3) SELinux 为能力设计了两个客体类别: capability 和 capability2。 capability 对应前 32 个能力, capability2 对应后 32 个能力。

SELinux 内部用一个比特表示一个操作, 用一个 32 位整数表示在某个客体类别上的全部操作类型。如进程上的操作一共有 30 个,目录上的操作一共有 25 个,都没有超过 32。但是在能力上,原有代码的数据结构无法表示,为了解决这个问题, SELinux 就多引入了一个能力相关的客体类别 capability2。

能力只有一个, capability 和 capability2 在语义上没有区别,这种设计实在算不上优雅。9.用户态客体

Linux 系统中还有一些客体存在于用户态。如数据库,在内核眼里只有文件,没有表( table)、记录(record)之类。所以对数据库的控制自然就应该由用户态服务进程来实施。但是,相关的策略仍然被内核掌控,整个系统只有一个策略库,这个策略库存储在内核内存之中,由内核的安全服务器负责管理。那些用户态服务进程所做的工作就是查询策略库确定对用户态客体的操作是否允许。

SELinux 的用户态客体包括数据库类型(db_database、 db_table、 db_procedure、 db_column、db_tuple……)、 X-Window 相关类型(x_drawable、 x_screen、 x_gc、 x_font、 x_colormap……)、dbus 等。

10.其他

SELinux 中还有一些很少用到的客体,这里就不列举了。

7.2.3 安全上下文的生成和变化

7.2.1 节介绍了安全上下文, 7.2.2 节介绍了客体类别和客体上的操作。有了安全上下文,有了操作,再加上 7.3 节要介绍的 SELinux 策略,系统管理员就可以规定安全上下文为 A 的进程可以对安全上下文为 B 的文件进行 C 操作。这样就完成了 SELinux 的强制访问控制。

但是,主体和客体的安全上下文的值是怎么设置的?主体和客体的安全上下文能不能改变?如果能,可以改变成什么值?

1.安全上下文的初始值

进程的安全上下文的初始值有两个来源:

(1)创建进程时,子进程的安全上下文是父进程的安全上下文的副本。

security/selinux/hooks.c
static int selinux_cred_prepare(struct cred *new, const struct cred *old,
gfp_t gfp)
{
const struct task_security_struct *old_tsec;
struct task_security_struct *tsec;
old_tsec = old->security;
tsec = kmemdup(old_tsec, sizeof(struct task_security_struct), gfp);
if (!tsec)
return -ENOMEM;
new->security = tsec;
return 0;
}

(2) Linux 系统中第一个进程的安全上下文是 SECINITSID_KERNEL 所对应的安全上下文。
 

security/selinux/hooks.c
static void cred_init_security(void)
{
struct cred *cred = (struct cred *) current->real_cred;
struct task_security_struct *tsec;
tsec = kzalloc(sizeof(struct task_security_struct), GFP_KERNEL);
if (!tsec)
panic("SELinux: Failed to initialize initial task.\n");
tsec->osid = tsec->sid = SECINITSID_KERNEL;
cred->security = tsec;
}

SELinux 代码将所有的安全上下文存储在一个数组中,为了提高效率, SELinux 代码用数组项的序号来代表安全上下文。这个序号被称作 sid。上面代码中的 SECINITSID_KERNEL 就是一个 sid。

cred_init_security 函数被 SELinux 的初始化函数调用,它所设置的进程正是系统的第一个进程:

security/selinux/hooks.c
static __init int selinux_init(void)
{
if (!security_module_enable(&selinux_ops)) {
selinux_enabled = 0;
return 0;
}
if (!selinux_enabled) {
printk(KERN_INFO "SELinux: Disabled at boot.\n");
return 0;
}
printk(KERN_INFO "SELinux: Initializing.\n");
/* Set the security state for the initial task. */
cred_init_security();
default_noexec = !(VM_DATA_DEFAULT_FLAGS & VM_EXEC);
sel_inode_cache = kmem_cache_create("selinux_inode_security",
sizeof(struct inode_security_struct),
0, SLAB_PANIC, NULL);
avc_init();
if (register_security(&selinux_ops))
panic("SELinux: Unable to register with kernel.\n");
if (selinux_enforcing)
printk(KERN_DEBUG "SELinux: Starting in enforcing mode\n");
else
printk(KERN_DEBUG "SELinux: Starting in permissive mode\n");
return 0;
}

2.安全上下文的改变

不是所有的主体和客体都需要改变安全上下文,进程间通信类客体就没有必要改变安全上下文。下面列出改变进程和文件的安全上下文的逻辑。

(1)进程

SELinux 提供了两种方式改变进程的安全上下文。第一种方式是进程调用 execve 系统调用,第二种方式是通过写/proc/self/attr/current 文件。这两种方式在前面已经列举。

(2)文件

文件的安全上下文记录在文件的扩展属性 security.selinux 中,修改扩展属性 security.selinux的值就是修改文件的安全上下文。

security/selinux/hooks.c
static int selinux_inode_setxattr(struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
{
struct inode *inode = dentry->d_inode;
struct inode_security_struct *isec = inode->i_security;
struct superblock_security_struct *sbsec;
struct common_audit_data ad;
u32 newsid, sid = current_sid();
int rc = 0;
if (strcmp(name, XATTR_NAME_SELINUX))
return selinux_inode_setotherxattr(dentry, name);
sbsec = inode->i_sb->s_security;
if (!(sbsec->flags & SBLABEL_MNT))
return -EOPNOTSUPP;
if (!inode_owner_or_capable(inode))
return -EPERM;
ad.type = LSM_AUDIT_DATA_DENTRY;
ad.u.dentry = dentry;
rc = avc_has_perm(sid, isec->sid, isec->sclass,
FILE__RELABELFROM, &ad);
if (rc)
return rc;
rc = security_context_to_sid(value, size, &newsid);
…
if (rc)
return rc;
rc = avc_has_perm(sid, newsid, isec->sclass,
FILE__RELABELTO, &ad);
if (rc)
return rc;
rc = security_validate_transition(isec->sid, newsid, sid,
isec->sclass);
if (rc)
return rc;
return avc_has_perm(newsid,
sbsec->sid,
SECCLASS_FILESYSTEM,
FILESYSTEM__ASSOCIATE,
&ad);
}

上述代码判断的操作许可较多,主要的有三个: 1)文件的安全上下文不再是现在的值(RELABELFROM)。2)文件的安全上下文可以是新的值(RELABELTO)。3)文件所在的文件系统允许文件新的安全上下文(ASSOCIATE)。

7.3 安全策略

UNIX/Linux 的设计传统是机制和策略分离。 SELinux 遵循了这一传统。 7.2 节介绍的SELinux 机制是:

1)主体是进程,客体细分为若干类别。2)在每个客体类别上定义若干操作。3)每一个主体和客体的实例都关联安全上下文。4)主体操作客体时, SELinux 会根据策略判断操作是否允许。本节集中讲述一下 SELinux 的策略。下面看一个策略的例子:

策略语句 策略含义
allow init sshd_exec_t:file { getattr open read execute }; 允许执行
allow init sshd:process transition; 允许域转换
allow sshd sshd_exec_t:file { entrypoint read execute }; 允许作为入口点
allow sshd init:process sigchild; 允许发 sigchild
allow init sshd:process { siginh rlimitinh }; 允许发 siginh
type_transition init sshd_exec_t:process sshd; 让域转换成为缺省操作

简单来说,上述语句就是让 init 进程可以执行 sshd 文件,并且新的 sshd 进程的安全上下文是sshd。

策略是用策略语言编写的。用户态策略语言文件一般有多个,其格式是适合用户阅读的文本格式。要让策略起作用,管理员需要用 SELinux 用户态工具将策略文件编译成一个二进制文件, 然后通过 selinuxfs 接口, 将这个二进制文件所表示的策略输入到内核存储空间的策略库中,即图 7-3 中的 Security Server(安全服务器)。最终使用策略的是 SELinux 的钩子函数(hooks),为了提高效率, SELinux 的设计者在安全服务器和钩子函数之间放置了一个缓存——Access Vector Cache。内核代码在系统调用函数中嵌入了对内核安全模块钩子函数的调用, SELinux 的钩子函数会根据策略返回结果。这个返回值会影响系统调用成功与否。图 7-3 简单描述了SELinux 的架构。

强调一下, SELinux 的策略是允许策略,访问控制是白名单机制,没有在策略中定义的操作都是被禁止的,即“法无允许即禁止”。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐