安全增强 Linux(SELinux)剖析

Linux® 一直被认为是最安全的操作系统之一,但是通过引入安全增强 Linux(Security-Enhanced Linux,SELinux),National Security Agency (NSA) 将 Linux 的安全性提升到了新的高度。SELinux 通过对内核和用户空间进行修改,对现有的 GNU/Linux 操作系统进行了扩展,从而使其变得坚不可摧。如果您现在正在使用 2.6 版的内核,就会惊奇地发现您已经在使用 SELinux 了!本文将探究 SELinux 背后的思想及其实现方法。

简介

公共网络(比如 Internet)充满着危险。只要将电脑连接到 Internet(即使只连接很短的时间),您就会感受到这一点。攻击者可以利用不安全性来获得对一个系统的访问,获得对信息的未授权访问,或者对一台计算机进行改造,以利用它发送垃圾邮件或攻击其他高端系统(使用 SYN 泛洪攻击,一种分布式拒绝服务攻击)。

分布式拒绝服务攻击(DDoS)通过 Internet 上的多个系统来实现(所以也称为僵尸电脑),这些系统消耗目标系统上的资源,(利用 TCP 的三向握手)使其不能被合法的用户访问。一种带有 cookie 的四向握手协议(Stream Control Transmission Protocol [SCTP])可以防御这种攻击。

GNU/Linux 非常安全,但它也非常动态:所做的更改会为操作系统带来新的漏洞,这些漏洞可能被攻击者利用,尽管人们都非常关心阻止未授权访问,但是发生入侵后会发生什么呢?

本文将探究 SELinux 背后的思想及其基本架构。关于 SELinux 的完整描述涉及一整本书的内容,所以本文只关注其基本原理,使您了解 SELinux 的重要性及其实现过程。

SELinux 的起源
SELinux 是一个面向政府和行业的产品,由 NSA、Network Associates、Tresys 以及其他组织设计和开发。尽管 NSA 将其作为一个补丁集引入,但从 2.6 版开始,它就被加入到 Linux 内核中。

访问控制方法

大多数操作系统使用访问控制来判断一个实体(用户或程序)是否能够访问给定资源。基于 UNIX® 的系统使用一种自主访问控制(discretionary access control,DAC)的形式。此方法通常根据对象所属的分组来限制对对象的访问。例如,GNU/Linux 中的文件有一个所有者、一个分组和一个权限集。权限定义谁可以访问给定文件、谁可以读取它、谁可以向其写入,以及谁可以执行它。这些权限被划分到三个用户集中,分别表示用户(文件所有者)、分组(一个用户组的所有成员)和其他(既不是文件所有者,又不是该分组的成员的所有用户)。

很多这样的访问控制都会带来一个问题,因为所利用的程序能够继承用户的访问控制。这样,该程序就可以在用户的访问层进行操作。与通过这种方式定义约束相比,使用最小特权原则 更安全:程序只能执行完成任务所需的操作。例如,如果一个程序用于响应 socket 请求,但不需要访问文件系统,那么该程序应该能够监听给定的 socket,但是不能访问文件系统。通过这种方式,如果该程序被攻击者利用,其访问权限显然是最小的。这种控制类型称为强制访问控制(MAC)。

另一种控制访问的方法是基于角色的访问控制(RBAC)。在 RBAC 中,权限是根据安全系统所授予的角色来提供的。角色的概念与传统的分组概念不同,因为一个分组代表一个或多个用户。一个角色可以代表多个用户,但它也代表一个用户集可以执行的权限。

SELinux 将 MAC 和 RBAC 都添加到了 GNU/Linux 操作系统中。下一节将探讨 SELinux 实现,以及如何将安全增强透明地添加到 Linux 内核中。

Linux 安全实现

在早期的 SELinux 中,它还是一个补丁集,它提供了自己的安全性框架。这存在着一些问题,因为它将 GNU/Linux 限制到一个单独的访问控制架构。Linux 内核继承了一种通用框架,将策略从实现中分离了出来,而不是采用单一的方法。该解决方案就是 Linux 安全模块(Linux Security Module,LSM)框架。LSM 提供了一种通用的安全框架,允许将安全模型实现为可载入内核模块(参见图 1)。

图 1. SELinux 将安全策略和实施分离

在访问内部对象之前对内核代码进行修改,以调用一个代表实施函数的钩子,该实施函数实现安全策略。该函数根据预定义的策略验证操作能否继续进行。安全函数存储在一个安全操作结构中,该结构包含必须受到保护的基本操作。例如,security_socket_create 钩子(security_ops->socket_create)在创建新 socket 之前检查权限,并考虑协议集、类型、协议,以及 socket 是在内核中创建还是在用户空间中创建。清单 1 提供了 socket.c 中用于创建 socket 的示例代码(参见 ./linux/net/socket.c)。

清单 1. 创建 socket 的内核代码

 
static int __sock_create(int family, int type, int protocol, 
                          struct socket **res, int kern)
{
        int err;
        struct socket *sock;
 
        /*
         *      Check protocol is in range
         */
        if (family < 0 || family >= NPROTO)
                return -EAFNOSUPPORT;
        if (type < 0 || type >= SOCK_MAX)
                return -EINVAL;
 
        err = security_socket_create(family, type, protocol, kern);
        if (err)
                return err;
 
        ...

security_socket_create 函数在 ./linux/include/linux/security.h 中定义。它提供了从 security_socket_create 到 security_ops 结构中动态安装的函数的间接调用(参见清单 2)。

清单 2. 用于 socket 创建检查的间接调用

static inline int security_socket_create (int family, int type,
                                          int protocol, int kern)
{
        return security_ops->socket_create(family, type, protocol, kern);
}

security_ops 结构中的函数通过安全模块安装。在本例中,这些钩子在可载入的 SELinux 内核模块中定义。每个 SELinux 调用在 hooks 文件内部定义,该文件实现从内核函数到特定安全模块的动态调用的间接性(参见清单 3 中的 …/linux/security/selinux/hooks.c 代码)。

清单 3. SELinux socket 创建检查

static int selinux_socket_create(int family, int type,
                                 int protocol, int kern)
{
        int err = 0;
        struct task_security_struct *tsec;
 
        if (kern)
                goto out;
 
        tsec = current->security;
        err = avc_has_perm(tsec->sid, tsec->sid,
                           socket_type_to_security_class(family, type,
                           protocol), SOCKET__CREATE, NULL);
 
out:
        return err;
}

清单 3 的核心部分是一个调用,用于验证当前操作是否是当前任务(通过 current->security 定义,其中 current 代表当前正在执行的任务)所允许的。访问向量缓存(Access Vector Cache,AVC)缓存了之前的 SELinux 决策(提高进程的性能)。此调用包括源安全标识符(sid)、安全类(根据请求操作的详细信息构造)、特定 socket 调用,以及可选的辅助审计数据。如果未在缓存中找到决策,那么会调用安全服务器来获取决策(此过程如图 2 所示)。

图 2. 分层 Linux 安全进程

初始化到 security_ops 中的回调钩子被动态定义为一个可载入内核模块(通过 register_security()),但是在没有载入安全模块时,这些钩子包含伪桩(dummy stub)函数(参见 ./linux/security/dummy.c)。这些桩函数实现标准 Linux DAC 策略。始终存在回调钩子,其中必须提供对象中介(mediation)来保证安全性。这包括任务管理(创建、通知、等待)、程序载入(execve)、文件系统管理(超级块、inode、文件钩子)、IPC(消息队列、共享内存、信号量(semaphore)操作)、模块钩子(插入和删除)、网络钩子(覆盖 socket、netlink、网络设备和其他协议接口)。

其他方法

SELinux 是目前最全面的安全框架之一,但它不是惟一的。本节回顾其他一些可用的方法。

AppArmor

AppArmor 最初由 Immunix 开发,随后由 Novell 维护,它是 SELinux 的替代方法,也使用了 Linux 安全模块(LSM)框架。由于 SELinux 和 AppArmor 使用了同样的框架,所以它们可以互换。AppArmor 的开发初衷是因为人们认为 SELinux 太过复杂,不适合普通用户管理。AppArmor 包含一个完全可配置的 MAC 模型和一个学习模式,在学习模式中,程序的典型行为可以转换为一个配置文件。

SELinux 的一个问题在于,它需要一个支持扩展属性的文件系统;而 AppArmor 对文件系统没有任何要求。您可以在 SUSE、OpenSUSE,以及 Ubuntu 的 Gutsy Gibbon 中找到 AppArmor。

Solaris 10(是受信任的 Solaris)

Solaris 10 操作系统通过其增强了安全性的 Trusted Extensions 组件提供了强制访问控制。该功能适用于 MAC 和 RBAC。Solaris 通过向所有对象添加敏感性标签实现了这一点,使您能够控制设备、文件、连网访问,甚至窗口管理服务。Solaris 10 中的 RBAC 的优点在于,它通过提供对管理任务(可在以后进行分配)的细粒度控制最小化了对根访问的需求。

TrustedBSD

TrustedBSD 是一个正在进行中的项目,主要开发可靠的操作系统扩展,这些扩展最终会加入 FreeBSD 操作系统。它包括构建在 Flux Advanced Security Kernel (Flask) 安全架构之上的强制访问控制,后者包括以插件模块形式提供的类型强制和多级安全(MLS)。TrustedBSD 还合并了来自 Apple Darwin 操作系统的开源 Basic Security Module (BSM) 审计实现(BSM 最初由 Sun 引入)。BSM 是一个审计 API 和文件格式,它支持普通的审计跟踪处理。TrustedBSD 还构成了供 Security Enhanced Darwin (SEDarwin) 使用的框架。

操作系统虚拟化

增强操作系统内部安全性的最后一个选择是操作系统虚拟化(也称为虚拟专用服务器(virtual private servers))。一个操作系统拥有多个独立的用户空间实例,可以实现功能分离。操作系统虚拟化对在独立用户空间内部运行的应用程序功能进行了限制。例如,一个用户空间实例也许不能修改内核(载入或移除内核模块),也不能挂载或卸载文件系统。并且不允许修改内核参数(例如,通过 proc 文件系统)。任何修改其他用户实例环境的操作都是不允许的。

许多操作系统都能实现操作系统虚拟化。 GNU/Linux 支持 VServer、Parallels Viruozzo Container 和 OpenVZ。在其他操作系统中,您可以找到容器(Solaris)和 jail(BSD)。在 Linux-VServer 中,每个单独的用户空间实例称为一个安全上下文。在每个安全上下文中,会为专用服务器实例启动一个新的 init。

结束语

对于 Linux 内核来说,强制访问控制和基于角色的访问控制都是相对较新的功能。随着 LSM 框架的引入,新的安全模块将会出现。除了对框架的增强,还可以堆叠安全模块,从而允许多个安全模块共存,而且最大限度地覆盖了 Linux 的安全需求。随着对操作系统安全性的深入研究,将会引入新的访问控制方法。

转载:http://www.ibm.com/developerworks/cn/linux/l-selinux/

One thought on “安全增强 Linux(SELinux)剖析”

Leave a Reply

Your email address will not be published. Required fields are marked *