从头开始生成 SELinux,构建一个 SELinux 就绪的 Gentoo 系统

Serge E. Hallyn (sergeh@us.ibm.com), 专职程序员, EMC 发布日期: 2006 年 6 月 19 日

简介

SELinux 是 2.6 版本的 Linux 内核中提供的强制访问控制 (MAC)系统。对于目前可用的 Linux 安全模块来说,SELinux 是功能最全面,而且测试最充分的,它是在 20 年的 MAC 研究基础上建立的。SELinux 在类型强制服务器中合并了多级安全性或一种可选的多类策略,并采用了基于角色的访问控制概念。有关这些主题的更多信息的链接,请参见本文后面的 参考资料 部分。

大部分使用 SELinux 的人使用的都是 SELinux 就绪的发行版,例如 Fedora、Red Hat Enterprise Linux (RHEL)、Debian 或 Gentoo。它们都是在内核中启用 SELinux 的,并且提供一个可定制的安全策略,还提供很多用户层的库和工具,它们都可以使用 SELinux 的功能。

如果您与很多用户一样希望让系统与以前一样工作,但是要求安全性更好一些,那么可以通过使用一些熟悉的应用程序或者通过使用高级语言编写安全策略来查询和 操作 SELinux。然而,在出现问题时 —— 例如内核和用户空间的内容不同步 —— 这些方法就不够了。另外,这些方法还可能会干扰 UNIX® 工程师理解 SELinux 实际上是如何工作的。最后,工程师和安全社区应该明白除了目前发行版所使用的方法之外,还有其他一些方法可以使用 SELinux。

在本文中,我们将学习如何将一个最初根本不能支持 SELinux 的系统转换成一个强制使用 SELinux 的系统。我们还将学习如何强制采用一些安全访问策略。


回页首

前提条件

要开始学习本文,您需要:

  • QEMU,一个免费且易于使用的处理器模拟器。这是一种很好的体验新内核或系统映像的方法,而不会对真实系统造成任何损坏。我们可以从 QEMU 下载页面 下载 QEMU。
  • Gentoo,一个基于源代码的 Linux 发行版。Gentoo 对于本练习来说非常理想,因为我们可以在一个运行的系统上安装 Gentoo,而不用管发行版是什么。我们可以在 Gentoo 下载页面 找到最新的版本。

输入下面的命令,创建一个磁盘映像文件:

qemu-img create -f raw gentoo.img 2G

下一个步骤是启动 QEMU 来对裸磁盘映像文件进行分区,并格式化一个分区。这需要某种 Linux 引导 CD。Knoppix 可以实现这种功能,Gentoo liveCD 也可以实现这种功能。我们可以从下面的地址下载 Gentoo liveCD:

wget ftp://ftp.gtlib.cc.gatech.edu/pub/gentoo/releases/x86/current/installcd/ install-x86-minimal-2006.0.iso

为了将来引用方便 —— 少输入几个字符 —— 您可能会希望对这个映像文件重新命名:

mv install-x86-minimal-2005.1.iso gentoo.iso

下面的命令会通知 QEMU 使用 gentoo.iso 作为自己的 CD,它使用 gentoo.img 作为自己的硬盘,并从 CDROM 开始引导:

qemu -hda gentoo.img -cdrom gentoo.iso -boot d

对于默认内核来说,我们只需要按回车键即可。然后输入下面的内容对磁盘映像文件进行分区:
清单 1. 对磁盘映像文件进行分区

fdisk /dev/hda
n
p
1
(return)
w

以上命令会创建一个新 (n) 主 (p) 分区,它从块 1 (1) 开始,到默认的末尾块(文件系统上的最后一个块)结束。然后将新的分区表写入 (w) 磁盘映像文件。

现在,我们将返回到 QEMU 中的 shell 提示符。现在输入:

mksf.ext2 /dev/hda1

为了给 udev 一个机会来创建设备,我们可能需要执行这个命令两次。然后,通过输入下面的命令关机:

poweroff

这会返回真实系统的 shell。如果没有返回,或挂起了很长时间,请按 Ctrl-c 键结束。

下一个步骤是将基本的发行版安装到磁盘映像文件上。Gentoo 非常适合本练习的原因是我们可以下载并提取出一个 “步骤 3” 映像文件,这样我们就可以获得一个功能完备的 Gentoo 系统。我们应该要找一个本地映像站点来下载一个步骤 3 的 tarball。如果您离 Sandia National Laboratories 很近,就可以使用下面这个示例站点:

wget ftp://mirror.iawnet.sandia.gov/pub/gentoo/releases/x86/current/stages/ stage3-x86-2006.0.tar.bz2

这个文件包含了一个完整 Gentoo 系统的压缩文件。要将这个系统提取到我们的磁盘映像文件上,首先要将磁盘映像文件挂载到系统中。下面这个命令用来挂载这个空文件系统,并将 tarball 展开到这个磁盘映像上。
清单 2. 将 Gentoo tarball 展开到磁盘映像上

su  (give root password)
ORIG=`pwd`
mount -oloop,offset=32256 gentoo.img /mnt
cd /mnt
tar jxf $ORIG/stage3-x86-2005.1.tar.bz2

当这个映像挂载到系统上时,需要注意几个基本的问题。/etc/fstab 文件告诉系统要将文件系统挂载到什么地方。对于这个简单的系统来说,不需要引导或交换项。下面的命令用来删除这些设置,并为根分区插入一个正确的项。它还 会为 root 用户设置密码。请确保在运行 passwd 命令时,选择一个可以记住的密码。安全性对于这个玩具系统来说不是什么关键,因此使用 “password” 作为密码就可以了。
清单 3. 系统基本设置

mv /mnt/etc/fstab /mnt/etc/fstab.orig
sed -e '/[BR]OOT/d' -e '/SWAP/d' /mnt/etc/fstab.orig > 
   /mnt/etc/fstab

cat >> /mnt/etc/fstab << EOF
/dev/hda1 / ext2 noatime 0 1
EOF

chroot /mnt
passwd
exit

cd $ORIG
umount /mnt

要启动 QEMU 映像文件,就必须使用内核。现在我们可以启用 SELinux 的支持来编译 Linux 内核了,尽管您可能并不知道自己正在使用 SELinux。从 kernel.org 下载 linux-2.6.14.tar.bz2(或更新版本)的一个拷贝,并将其解压:

wget http://kernel.org/pub/linux/kernel/v2.6/linux-2.6.14.tar.bz2
tar jxf Linux-2.6.14.tar.bz2

然后,切换到 Linux-2.6.14 目录,并对内核进行配置,如下所示:

cd linux-2.6.14
make defconfig
make menuconfig

确保至少启用以下选项:
清单 4. 内核 .config 文件节选

CONFIG_EXT2_FS=y
CONFIG_EXT2_FS_XATTR=y
CONFIG_EXT2_FS_SECURITY=y

CONFIG_SECURITY=y
CONFIG_SECURITY_CAPABILITIES=y
CONFIG_SECURITY_SELINUX=y
CONFIG_SECURITY_SELINUX_BOOTPARAM=y
CONFIG_SECURITY_SELINUX_BOOTPARAM_VALUE=1
CONFIG_SECURITY_SELINUX_DISABLE=y
CONFIG_SECURITY_SELINUX_DEVELOP=y
CONFIG_SECURITY_SELINUX_AVC_STATS=y
CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE=1

现在使用下面的命令来编译内核:

make
cp arch/i386/boot/bzImage ..

当然,在转换到 SELinux 之前,我们首先需要对系统进行测试。这可以使用下面的命令来实现:

qemu -hda gentoo.img -kernel bzImage -append "ro root=/dev/hda1"

四处浏览一下,添加几个用户,在系统中玩耍一阵。完成之后,使用 poweroff 命令关闭系统。现在,我们已经准备好将这个系统转换成 SELinux 了。


回页首

转换成 SELinux

要将这个系统转换成支持并启用 SELinux 的系统,我们需要修改开始引导系统的程序 /sbin/init,并添加另外几个软件来构建 SELinux 策略及与 SELinux 进行交互。

SysVinit

开始我们可以使用一个干净的 SysVInit 拷贝。从 ftp://ftp.cistron.nl/pub/people/miquels/sysvinit/sysvinit-2.86.tar.gz 下载 2.86 版本,或者在 freshmeat 上寻找 SysVinit 页面 上的最新版本。我们可以使用后文中 下载 一节所给出的链接以 zip 文件的形式来下载补丁 (sysvinit-init.c.diff);这个补丁可以使用清单 5 中的过程应用于文件 init.c。这个补丁会在系统引导时加载 SELinux 策略。它引入了一个标志 no_selinux,如果引导命令包括了 -p 选项,这个标志就被设置为 1。如果这个标志被设置了,那么就会给出一条警告消息,然后像正常方式一样引导。否则就调用函数 load_policy(),这个函数是在这个补丁中定义的。
清单 5. 对文件 init.c 应用补丁

wget ftp://ftp.cistron.nl/pub/people/miquels/sysvinit/sysvinit-2.86.tar.gz
cd sysvinit-2.86
cd src
patch -p0 < $ORIG/sysvinit-init.c.diff
make init
(su)
mount -oloop,offset=32256 $ORIG/gentoo.img /mnt
mv /mnt/sbin/init /mnt/sbin/init.good
cp init /mnt/sbin/init
umount /mnt
exit

函数 load_policy() 会将 selinuxfs 文件系统的一个实例加载到 /selinux 目录中,并打开文件 /etc/policy.bin 准备读取内容。注意,/selinux 和 /etc/policy.bin 都是强制使用的名字,这样任何希望使用它的用户空间工具都可以遵守这种命名方式。在编译 SELinux 策略时,我们需要将二进制版本拷贝成 /etc/policy.bin。接下来,load_policy() mmap() 会对文件 /etc/policy.bin 进行映射操作,并调用 security_load_policy,这是在 load_policy 上面定义的。它会依次打开文件 /selinux/load(这是 selinuxfs 文件系统中的一个文件)来写入内容,并将 /etc/policy.bin 的所有内容都写入到 /selinux/load 中。这就是二进制策略在引导时如何加载到内核中的。

创建策略

SELinux 是一个灵活的访问控制系统,其访问决策都是由管理员定义的策略来决定的。这些策略依赖于系统中文件的布局和配置。支持 SELinux 的发行版提供了预定义的策略,可以灵活地排除与系统中没有安装的软件有关的策略。然而在本练习中,我们可以定义自己的策略。

由于新对象类和权限不是同时引入的,因此策略最终对内核版本会有一定的依赖。mdp.tar.gz (后面 下载 一节中的 zip 文件)中的程序会根据内核版本号自动构建一个空策略。由于这需要对内核进行分析,因此我们需要向其指出用来为 QEMU 映像编译内核所使用的源代码树。将 mdp.tar.gz 解压到 $ORIG/ 中,并进入结果目录 mdp/。

输入:

./build.sh -k ../linux-2.6.14.

这会编译程序 mdp,并使用内核目录作为一个参数来运行它。mdp 接着会查看内核所了解的对象类和权限,并创建一个包含数据的 SELinux 策略。所生成的策略有一个 SELinux 用户、一个 SELinux 角色以及一个 SELinux 类型。单个类型可以应用于所有的文件、内核对象和进程,它对自己具有完全的访问权限。后面我们会对这个策略进行定制,但是现在它可以用来进行初始化并使用 SELinux。

当 build.sh 运行 mdp 时,它会创建几个文件。我们需要 policy.conf 和 file_contexts。第一个是策略的文本表示,后一个是如何标记文件系统的一些指示。

现在我要将对 policy.conf 的全面学习推迟到后面,因此我们暂时跳过前面 500 到 600 行代码,直接跳到下面这行:

type base_t;

这是该策略中所定义的惟一一个类型。下面这行代码:

role base_r types { base_t };

定义了一个角色 base_r,它与 base_t 类型关联在一起。这意味着角色 code_r 中的一个进程可能会在域 base_t 下面运行。接下来是一些长长的 allow 语句,每个语句指定了一个对象类,这为 domain base_t 下面的进程提供了对为该类定义的所有权限来访问 base_t 类型的对象类的能力。

接下来,策略会定义一个用户 user_u,它与角色 base_r 建立了关联。作为 SELinux 用户 user_u 运行的进程可以在 base_r 角色下面运行。我们早已看到 base_r 是与 base_t 类型关联在一起的,因此这两个语句一起使 user_u:base_r:base_t 成为一个有效的上下文,这意味着允许进程在这个上下文中运行。

下面几行代码也非常有用。首先:

sid kernel user_u:base_r:base_t

将一个有效上下文分配给系统中的第一个进程。两行之后,您可以看到:

sid unlabeled user_u:base_r:base_t

这会将相同的上下文分配给任何没有标记的文件。

现在来看一下文件 file_contexts。其中包括两行内容,格式如下:

<regexp> <context>

其中 regexp 是一个用来对文件名进行比较的正则表达式,context 是如果这个正则表达式可以匹配就对文件应用的 SELinux 上下文。下面这行代码:

/.* user_u:base_r:base_t

用来将这个上下文分配给系统上的所有文件。

安装 SELinux 用户空间

下一个步骤是安装将以文本形式编写的 SELinux 策略编译成内核所需要的二进制格式的代码,以及一个对根文件系统进行标记的程序。源代码可以使用清单 6 所示的代码通过 CVS 从 SourceForge 上获得:
清单 6. 得到 SELinux 用户空间源代码

su
mount -oloop,offset=32256 $ORIG/gentoo.img /mnt
cd /mnt/usr/src
cvs -z3 -d:pserver:anonymous@cvs.sf.net:/cvsroot/selinux co -P 
nsa/selinux-usr

将纯文本策略拷贝到磁盘映像文件上,方法如下:

cp -r $ORIG/mdp /mnt/usr/src

进入 nsa/selinux-usr 目录,并编译 libsepol、checkpolicy、libselinux 和 policycoreutils,方法如下:
清单 7. 编译 SELinux 用户空间代码

chroot /mnt
cd /usr/src/nsa/selinux-usr/
cd libsepol/
make && make install
cd ../libselinux/
make && make install
cd ../checkpolicy/
make && make install
cd ../policycoreutils/
make && make install

如果在上一个步骤中碰到了错误,请确保所需要的 setfiles 都已经安装了,方法如下:
清单 8. 安装 setfiles

cd setfiles
make
make install

现在,要编译策略,请使用 checkpolicy 程序,方法如下:
清单 9. 编译 SELinux 策略

cd /usr/src/mdp
checkpolicy -o policy.bin policy.conf
cp policy.bin /etc/
exit  # exit chroot
exit  # exit root shell

重新对文件系统进行标记需要引导进虚拟机中。记住现在要指定 -p 选项,这样 /sbin/init 就不会加载 SELinux 策略:

qemu -hda gentoo.img -kernel bzImage -append "ro root=/dev/hda1 -p"

SELinux 是通过自己的文件系统 selinuxfs 来与用户空间的程序进行交互的。用户空间程序期望它被挂载到 /selinux 上。创建 /selinux 目录,并对文件系统重新进行标记,方法如下:
清单 10. 重新标记文件系统

mkdir /selinux
cd /usr/src/mdp
setfiles file_contexts /
poweroff

最后,您可以在 SELinux 下重新启动机器了!

qemu -hda gentoo.img -kernel bzImage -append "ro root=/dev/hda1"

学习 SELinux 策略

SELinux 是基于为进程、文件和其他对象所分配的安全上下文来制定访问决策的。SELinux 为查询这些上下文并对它们进行设置(假设有所需的访问权限)提供了接口。例如,SELinux 通过 procattr 接口来报告进程的上下文。如果输入:

cat /proc/$$/attr/current

就会看到当前进程($$)的上下文。通过使用脚本 pidctx.sh(请参见后文 下载 一节中的 zip 文件),我们可以简单地查看系统中所有进程的上下文。这个脚本会简单地打印系统上每个进程的 /proc/<pid>/attr/current 文件的内容。

SELinux 使用扩展属性来保存文件的内容。Linux 中大部分永久文件系统(ext2、ext3、jfs、xfs 等)都可以支持扩展属性,而 reiserfs 是一个不幸的例外。这些是一些 (name, value) 的数据对,与 inode 关联在一起,其中名字被周期性地划分成名称空间。SELinux 扩展属性在安全性名称空间中,是由 “selinux” 来标识的,因此完整的 xattr 值应该是 “security.selinux”。新的系统调用集允许用户空间查询并设置扩展属性。用来对 xattr 进行查询的系统调用是 getxattr(2)。它接受一个文件名、一个属性名、一个缓冲区(xattr 的返回值就保存在这里)以及所提供的缓冲区的大小作为参数。

showctx.c 文件简单地运行作为命令行提供的所有文件名,并打印 security.selinux 扩展属性的值,这假设它是存在、可读的,并且具有合适的大小。

我们可以从后文 下载 一节的 zip 文件中获得 showctx.tar.gz 文件,并从中解压出 showctx.c 文件。然后将其放入 QEMU 机器。一种方法是关闭 QEMU 映像,然后执行下面的命令:
清单 11. 安装 showctx

(su)
mount -oloop,offset=32256 $ORIG/gentoo.img /mnt
cp showctx.c /mnt/usr/src
umount /mnt
exit

现在如果 QEMU 没在运行,就启动它。要编译 showctx,请输入:

gcc -o showctx showctx.c cp showctx /bin/

现在可以输入:

showctx / /tmp /home /root /usr/src

当然,对每个文件我们都会看到相同的内容。下一节会通过增强策略来让这变得更加有趣。


回页首

使用 SELinux 策略

秘密类型

现在可以创建一个秘密目录 /secret,SELinux 应该不允许其他进程在这个目录下面读取数据。实际上我们可以先在这个目录中创建一个目录和几个文件:

mkdir /secret echo "hello, world" > /secret/helloworld echo "You can't see me" > /secret/dontlook

在 SELinux 策略中,现在创建一个新类型 secret_t,其他类型 base_t 无权访问它。首先,我们可以将下面的内容:

type secret_t;

添加到 policy.conf 中的 base_t 声明之后来定义这个类型。另外,由于 secret_t 类型的文件都是角色 base_r 的,而角色 base_r 必须要允许与 secret_t 关联在一起。因此刚才添加的这行代码要读取:

role base_r types { base_t };

对其进行编辑,使其读取:

role base_r types { base_t secret_t };

现在,要重新编译这个策略,请输入:

checkpolicy -o policy.bin policy.conf
cp policy.bin /etc/

接下来将下面的行添加到 file_contexts 文件中:
清单 12. /secret 中的文件内容

/secret user_u:base_r:secret_t
/secret/helloworld user_u:base_r:base_t
/secret/.* user_u:base_r:secret_t

这告诉系统 /secret 目录及其中的任何文件都是 secret_t类型的,只有 /secret/helloworld 文件例外,它应该是 base_t 类型的。要分配磁盘上的上下文,请使用 setfiles

setfiles file_contexts /

噢不,有错!SELinux 不认识 secret_t 类型。实际上,我们可以动态重新加载 SELinux 策略。不过现在为了简单起见,我们还是重新启动 QEMU 映像好了。当然,由于我们没有安装引导程序,因此简单地重新启动并不能正常工作。因此,我们需要输入 poweroff 关闭 QEMU 映像。如果窗口在打印 “Power down” 之后就没有显示了,请使用 Ctrl-c 来中断 qemu 命令。重新运行 qemu 命令来重新启动它。然后尝试再次运行上面的 setfiles 命令。

验证策略的重新加载正确工作了:

showctx / /secret /secret/helloworld /secret/dontlook cat /secret/dontlook

不过等一会儿!您可以看到秘密类型了。

这是一个编译时内核选项 CONFIG_SECURITY_SELINUX_DEVELOP 的结果。这个选项默认将 SELinux 设置为非强制状态。要验证这个设置,请输入:

cat /selinux/enforce

这应该会返回 0。要将 SELinux 设置成强制模式,请输入:

echo 1 > /selinux/enforce

如果希望,您可以在系统引导时通过 init 脚本来自动实现这种功能,如清单 13 所示;或者简单地编译一个没有 SELINUX_DEVELOP 支持的内核。
清单 13. 在引导时将 SELinux 设置成强制模式

cat >> /etc/rc.d/selinux-enforce << EOF
#!/bin/sh
echo 1 > /selinux/enforce
EOF
chmod ugo+x /etc/rc.d/selinux-enforce
rc-update add default selinux-enforce

现在 SELinux 已经处于强制模式了,请再试图浏览 /secret 下面的内容。注意,尽管进程上下文有权读取 /secret/helloworld,但实际上您并不能真正读取它,因为无法通过 /secret。当然,创建一个到这个文件的硬链接可以跳过这个问题。

下载

描述 名字 大小 下载方法
Scripts and C code described in this article l-selinux.zip 4KB HTTP

参考资料

学习

获得产品和技术

  • QEMU 主页 上具有 QEMU 的最新文档和下载。
  • gentoo.org 是 Gentoo 新闻和代码的主要资源。
  • 从 freshmeat 上下载最新版本的 SysVinit

讨论

关于作者

Serge Hallyn 是 IBM Linux Technology Center 安全团队的一名专职程序员。他从 College of William and Mary 获得了计算机科学博士学位。他实现了几个安全模型 (dte、bsdjail),并对其他的安全模型 (seclvl) 和 SELinux 贡献了自己的力量。他实现了 DTE 及相应的配置文件和分析工具,并在 Type Enforcement(SELinux 中的主要技术)方面具有丰富的经验。

from  http://www.ibm.com/developerworks/cn/linux/l-selinux.html#

发表评论?

0 条评论。

发表评论