Arch 打包准则/安全
32 位 – 安全 – CLR – CMake – DKMS – Eclipse – Electron – Free Pascal – GNOME – Go – Haskell – Java – 交叉编译工具 – KDE – Lisp – Meson – MinGW – 内核模块 – Node.js – Nonfree – OCaml – Perl – PHP – Python – R – Ruby – Rust – Shell – VCS – Web – Wine – 字体
本文描述了适用于 Arch Linux 软件包的打包安全指引。对于 C/C++ 项目,可以为编译器和链接器应用安全加固选项。Arch Linux 默认会启用 PIE、FORTIFY_SOURCE、栈保护、nx 和 relro。
用法
可以使用 checksec包 检查强化加固保护选项:
$ checksec --file=/usr/bin/cat
RELRO
RELRO 是用于加固 ELF 二进制文件和进程的一种通用缓解技术。在程序加载时,链接器需要写入几个 ELF 内存区段,但在将控制权交给程序前,可以将这些区段设为只读,由此防止攻击者覆盖掉部分 ELF 区段。有两种 RELRO 模式:
- 部分 RELRO(
-Wl,-z,relro):程序加载后部分区段被设为只读,但 GOT(.got.plt)仍为可写。 - 完全 RELRO(
-Wl,-z,now):程序加载时解析所有动态符号,使得整个 GOT 都可被设为只读。
如果应用汇报使用了部分 RELRO,需检查构建工具是否使用了我们传递的 LDFLAGS,以及是否允许覆写 LDFLAGS。对于 Go 软件包,需检查构建方法是否将 build.go 作为了纯 golang Makefile 替代使用,该方法不允许传递 LDFLAGS。
Haskell
暂不清楚如何为 Haskell 实现完全 RELRO。
Go
参见 Go 语言软件打包准则#Flags and build options。
Stack canary
Stack canary(又称栈警惕标志)由编译器添加在栈的缓冲区和控制数据之间。如果该已知值被破坏,就代表出现了缓冲区溢出,运行中的程序就会报出段错误(segfaults),以防止执行潜在的错误代码。
gcc包 包默认通过 --enable-default-ssp 编译选项启用了栈保护。
NX
C/C++
可执行空间保护会将内存区域标记为非可执行,使得在该区域执行机器码时会出现报错。该功能使用了类似 NX 位(No-eXecute bit,禁止执行位)的硬件功能,有时也会使用软件模拟这些特性。
PIE
C/C++
gcc包 包默认通过 --enable-default-pie 选项为 C/C++ 启用了该特性。
Golang
将以下标志传递给 go build:
export GOFLAGS='-buildmode=pie' export CGO_CPPFLAGS="-D_FORTIFY_SOURCE=3" export CGO_LDFLAGS="-Wl,-z,relro,-z,now"
Haskell
将以下标志传递给 runhaskell Setup.hs configure:
--ghc-option='-pie'
RPATH/RUNPATH
RUNPATH/RPATH 为包含该参数的对象提供了更多的搜索路径(适用于可执行文件和共享对象):
$ objdump -x /usr/bin/perl | grep -E 'RPATH|RUNPATH'
如果 RPATH 的值包含了攻击者可控制的路径,那就有可能通过对应目录下的恶意库执行代码(例如 CVE-2006-1566 和 CVE-2005-4280)。更多信息请参考 Debian:RpathIssue。
RPATH 是通过向 LDFLAGS 传递类似 -Wl,-rpath -Wl,/usr/local/lib 的参数,然后由链接器负责设置的。如果要设置 RUNPATH,请将 --enable-new-dtags 添加到链接器标志中。
FORTIFY
Fortify source 是一个宏,可以为执行内存和字符串操作的函数添加缓冲区溢出保护。它会检测是否有攻击者尝试通过负责更多的字节来溢出缓冲区,并停止程序的运行。该保护项已在默认 CPPFLAGS 中启用:
makepkg.conf
CPPFLAGS="-D_FORTIFY_SOURCE=3"
更多信息请参考 makepkg#配置。
systemd 服务
如果上游不提供 systemd 服务文件,使得需随软件包附带,请检查是否需要使用以下 systemd 服务加固功能。Systemd 提供并为服务启用了分析安全特性的方法:
$ systemd-analyze security reflector.service
文件访问
可以通过限制文件系统访问来加固服务。
为执行的进程配置一个新的文件系统命名空间,并在内部挂载私有 /tmp 和 var/tmp 目录,使得其不与命名空间外部的进程共享。该方法适用于会向 /tmp 目录写入数据的程序:
PrivateTmp=true
ProtectSystem 参数对于执行中进程有三种只读挂载方式,其中 “full” 选项会将 /usr、/boot 和 /etc 目录挂载为只读。ProtectHome 会使进程无法访问 /home、/root 和 /run/user 目录:
ProtectSystem=strict ProtectHome=true
PrivateDevices 会为执行中的进程配置新的 /dev 命名空间,其中只包含类似 /dev/null、/dev/zero 和 /dev/random 的 API 伪设备,不包含任何物理设备、系统内存、系统端口和其它设备。该选项可以防止进程直接向物理设备进行写入,systemd 还会为 @raw-io 集添加系统调用过滤器:
PrivateDevices=true
以下选项会使执行中进程无法修改可通过 /proc/sys 和 /sys 等路径访问的内核变量。ProtectControlGroups 会使 /sys/fs/cgroup 层次结构变为只读:
ProtectKernelTunables=true ProtectControlGroups=true
以下方法可禁止访问部分文件路径:
InaccessiblePaths=/etc
更多信息请参考 systemd.exec(5)。
用户
确保执行中的进程和其子进程无法通过 execve(2) 获得新权限:
NoNewPrivileges=true
内存
禁止创建同时为可写和可执行的内存映射、将映射修改为可执行和创建可执行共享内存。该方法会将进程沙箱化,防止攻击者向可执行的内存部分写入数据。注意,该方法与使用 JIT 的应用不兼容:
MemoryDenyWriteExecute=true
系统调用
锁定 personality(2) 系统调用,使内核执行域无法被修改:
LockPersonality=true
还可以限制服务的系统调用,以下命令将显示 systemd 可以过滤的系统调用:
$ systemd-analyze syscall-filter
可以使用预定义组,以下示例为使用推荐的系统服务调用白名单配置起点:
SystemCallFilter=@system-service
还可以按架构来限制系统调用,例如限制在 64 位设备上执行 32 位二进制文件(即非原生架构二进制文件):
SystemCallArchitectures=native
网络
如果进程不需要访问网络,可以为进程配置一个新的网络命名空间,并只配置回环接口,以此完全禁用网络访问:
PrivateNetwork=true
如果需要网络,还可以限制 socket(2) 系统调用可使用的地址族类型,以只允许 UNIX socket 为例:
RestrictAddressFamilies=AF_UNIX
也可以对只需连接到 localhost(本机)或特定 IP 段的情况进行限制,以只允许 localhost 为例:
IPAddressAllow=localhost IPAddressDeny=any
更多网络过滤相关的信息请参考 systemd.resource-control(5)。
其它
为执行的进程配置新的 UTS 命名空间,并禁止修改主机名或域名:
ProtectHostname=true