写给linux系统管理员看的systemd 六 chroot 和防越狱(systemd作

http://0pointer.de/blog/projects/changing-roots.html

chroot

作为系统管理员或者开发者你迟早碰到chroot()环境。chroot()系统调用只是改变进程和子进程的根目录/, 由此来限制进程能看到的文件目录树结构。chroot()有两种主要应用:

    安全性:一个隔离的守护进程被chroot()到一个私有的子目录下, 当其暴露时,攻击者只能看到这个子目录而非整个OS文件结构:他就陷入了chroot()监狱。debug,测试,编译,安装和恢复OS的镜像时能搭建环境,控制结果:整个guest操作系统mount或者启动到主OS的一个子目录下,然后shell(或者其它什么程序)在子目录下启动,把这个子目录设置成根目录。对shell来讲,它运行在一个能与主OS非常不同的系统下。比如,它可以运行一个不同的发行版或者甚至一个不同的硬件构建(比如:主系统x86_64, 客户系统i386)。它无法看到主OS的整个层次结构。

经典的基于System-V的操作系统中chroot()环境相当容易.比如,因为测试或其它目的,在基于chroot()的guest OS树下面启动某个特定后台进程, mount /proc, /sys和其它API文件系统到目录树, 然后用chroot()进入chroot, 最后通过chroot后的/sbin/service运行SysV init脚本

在基于systemd的OS中,事情就没那么简单了. systemd的一个优点就是保证启动所有后台进程运行时,上下文是干净,无依赖性的,不与用户启动系统服务的当时环境有任何关联.而在基于sysV init的系统中,大部分的执行上下文(像资源限制,环境变量等等)都继承至启动init脚本的用户shell, systemd中用户只是通知init守护进程, 然后init守护进程将fork一个具有稳健的, 无污染的执行上下文的守护进程. 不继承用户上下文的参数. 尽管这是一个强大的特性, 它却破坏了在chroot()环境下启动一个服务的这种传统方法:因为实际的守护进程总是衍生于PID 1, 并由此继承chroot()设置, 这与client端无关,无论client要不要求守护进程启动chroot(). 在此之上, 因为systemd实际上把本地通信sockets放在/run/systemd, chroot()环境下的进程将无法与init系统通话了(这可能是件好事,当然胆大的人能突破这个限制,使用mount –bind就行了)

这样当然带来了一个问题:systemd环境中如何正确使用chroot(). 下面,我们将给出方案,希望能完全,清晰的回答这一问题.

我们先来解决第一个应用例子: 因为安全目的,把守护进程锁在chroot()监狱里. 把chroot()用做安全工具其实十分不可靠, 因为chroot不只是一条单向路.逃脱chroot()环境挺容易的, 即使用户文档里都有说明.只有与其它一些技术结合它才能某种程度上保证安全. 因此对应用程序以防破坏的方式chroot()自己时通常有些特殊的要求.基于其上,要正确搭建chroot()环境,需要有对chroot()后的服务有深刻理解, 比如, 为了确保服务chroot()后需要的所有沟通渠道都可获得,需要清楚哪些目录需从主目录树bind mount. 为了安全,需要chroot()最好在守护进程的c代码中实现. 开发者最了解(或者至少应该最了解)如何正确安全的chroot(), chroot()后,守护进程需要的最小文件集, 文件系统和目录是什么. 进日来有些守护进程已经能这么做了, 不幸的是通用Fedora安装版里默认运行的那些守护进程里只有两个能这么做:Avahi和RealtimeKit. 显然两者都是由同一个真正聪明的伙计写的.脱帽致敬!(要想验证它很容易: ls -l /proc/*/root)

systemd当然提供了方法来chroot()给定守护进程并管理它们,就像其它通常的工具一样. 这是由systemd服务文件中的RootDirectory=选项来实现的.给个例子:

[Unit]http://0pointer.de/public/systemd-man/systemd.servDescription=A chroot()ed Service[Service]RootDirectory=/srv/chroot/foobarExecStartPre=/usr/local/bin/setup-foobar-chroot.shExecStart=/usr/bin/foobardRootDirectoryStartOnly=yes

本例中, 在用ExecStart=开关开始执行守护进程的二进制文件以前, RootDirectory=开关用来配置chroot()到哪里. 需要注意的是ExecStart=开关需要指明chroot()后的二进制文件位置, 而非之前的主目录树中的位置(例如, 本例中执行的二进制文件在主目录树中的位置是/srv/chroot/foobar/usr/bin/foobard). 守护进程启动前,脚本setup-foobar-chroot.sh被调用, 目的是设置必须的chroot环境, 例如根据服务需要mount /proc和类似文件系系.RootDirectoryStartOnly=开关确保只有被ExecStart=开关指定的守护进程被chroot,ExecStartPre=开关指定的脚本则不会, 它需要访问整个OS文件结构bind mount目录(更多开关信息见man pages).如果你把这样一个单元文件放到/etc/systemd/system/foobar.service,你就能通过systemctl start foobar.service命令来启动你的被chroot的服务了. 你然后还可以通过systemctl status foobar.service来监视它, 它像其它服务那样是能被系统管理员访问的, 实际上,不像SysV, 被chroot并不改变你的监视器和控制工具与之交互的方式.

新的Linux内核支持文件系统名字空间. 这与chroot()类似但更强大, 没有chroot()的那些安全问题. systemd 在单元文件中提供了正确使用文件系统名字空间的一个子集. 在子目录中设置全chroot()环境通常有一个有效的,更简单的替代方案. 使用ReadOnlyDirectories=InaccessibleDirectories=这两个开关你也可以为你的服务设置一个文件系统名字空间监狱. 初始时, 它跟你的主OS文件系统名字空间一样. 在这两个开关后列出目录后, 这些目录对守护进程来说就变成了只读或者完全不能访问的了.例如:

[Unit]Description=A Service With No Access to /home[Service]ExecStart=/usr/bin/foobardInaccessibleDirectories=/home

这个服务有权访问整个主OS系统文件,只有一个目录例外:/home对其不可见, 从而保护了用户数据(详情见用户手册)

从许多方面看来,文件系统空间都是chroot()更好的接替者. 最终,Avahi 和 RealtimeKit应该有可能用名字空间来取代chroot().

太多安全方面的例子了. 现在,让我们看看其它应用吧: 搭建和控制OS镜像, 用于debug, 测试, 编译, 安装和恢复.

chroot() 环境是十分简单的事:只是虚拟的文件系统的层次结构. chroot()进一个子目录的进程仍然有所有系统调用的访问权, 能杀死主系统的所有进程,分享主系统的所有资源. 因此, 靠chroot()来运行一个OS(或者一个OS的一部分)是件十分危险的事:主客之间的隔离仅限于文件系统, 在chroot()内能访问其它所有东西. 例如, 如果你在chroot()内升级一个版本, 包中的脚本文件发了个SIGTERM信号给PID 1, 以此重启initxit,这将实际上在主系统中发生!在此之上, SysV 共享内存, 抽象名字空间socket和其它IPC原语在主客间共享. 完全隔离主客看起来不大可能,避免在chroot内对主系统的意外修改,这样的基本隔离是可取的: 你永远也不可能保证package中的所有代码都不去招惹主OS.

systemd提供了一些功能来解决这样的chroot()问题.

首先,systemctl是chroot敏感的, 在chroot中, 它的大部分操作都变成空操作,systemctl enable和systemctl disable除外.如果一个包的安装脚本调用了这两个命令, 服务将在客户OS中被enable. 不过, 像"包更新过程中包的安装脚本是否应该包括systemctl restart这样的命令"这类问题的答案不会在chroot()环境内产生影响.

更重要的是,对chroot()需求, systemd有开箱即用,而且更爽的工具systemd-nspawn: 它利用文件系统和PID名字空间,在系统树上boot一个简单的轻量级的容器,. 它能就像chroot那样被使用,但对主OS的隔离更彻底,更安全更容易使用. 实际上, systemd-nspawn能就用一个简单的命令在容器内启动整个systemd或sysvinit的OS. 由于它的虚拟PID, 容器中的init系统跟PID 1一样. 与chroot()想反,这个工具将自动帮您mount上/proc,/sys.

下例是三条命令在Fedora机器的nspawn容器中启动Debian OS:

# yum install debootstrap# debootstrap --arch=amd64 unstable debian-tree/# systemd-nspawn -D debian-tree/

这将启动挂载OS目录树然后简单的调用其中的shell. 如果你要在容器内启动整个系统, 用这条命令:

# systemd-nspawn -D debian-tree/ /sbin/init

快速启动之后,你将看到shell提示符, 它运行在你的容器内部的一个完整的OS上. 容器内无法看到它外面的任何进程. 它共享网络配置,但无法更改(因而启动时会有些EPERMs, 这并不致命). /sys, /proc/, /sys在容器内可见, 但只读mount, 是为了避免容器内能修改内核或硬件配置. 值得注意的是,这只能保护主OS不被意外修改. 容器内的一个进程能手动remount文件系统为可读写,然后再修改它想修改的东西.

那么,systemd-nspawn到底有多棒?

    真容易用. 无需手动 mount/proc/sys到你的 chroot() 环境. 工具自动完成,退出容器时内核自动清理.隔离更彻底,保护主OS不被意外修改.好到你能在容器内启动整个OS.小,systemd安装到哪它就安装到哪. 无复杂安装和配置.

systemd 本身已经被修改到能很好的在这样一个容器内工作. 例如, 当运行shut down时,如果发现是在容器中, 最后一步,它只运行exit(), 而非reboot().

注意systemd-nspawn不是一个容器的完全解决方案.LXC是更好的选择. 它应用同样的底层内核技术但提供的功能更多,包括网络虚拟化. systemd-nspawn就像容器解决方案中的GNOME 3, 小巧易用–但配置选项少. LXC OTOH更像KDE: 配置项更多. 我写systemd-nspawn的目的是为了测试,debug,编译,按照和恢复. 这些是你真正应该用它做的,它也对做这些很在行, 比chroot()有好太多了.

那么, 本文结束的时间到了,已经很长了. 本文需要记住的是:

    安全 chroot() 最好做在你程序的c代码里.ReadOnlyDirectories=,InaccessibleDirectories=也许听合适替代全chroot()环境.RootDirectory=是你的朋友, 如果你要 chroot() 某一特定服务的话.systemd-nspawn挺棒.chroot()太弱, 文件系统名字空间才是王道.

Fedora 15 里所有这些都是ready的.

就这些了,下次见

如果说对云南有进一步的了解的话就是鲜花。

写给linux系统管理员看的systemd 六 chroot 和防越狱(systemd作

相关文章:

你感兴趣的文章:

标签云: