FreeBSD 中文社区 2025 第二季度问卷调查
FreeBSD 中文社区(CFC)
VitePress 镜像站QQ 群 787969044视频教程Ⅰ视频教程Ⅱ
  • FreeBSD 从入门到追忆
  • 中文期刊
  • 状态报告
  • 发行说明
  • 手册
  • 网络文章集锦
  • 笔记本支持报告
  • Port 开发者手册
  • 架构手册
  • 开发者手册
  • 中文 man 手册
  • 文章与书籍
  • UNIX 四分之一世纪
  • Unix 痛恨者手册
  • FreeBSD 中文期刊
  • 编辑日志
  • 2025-123 下游项目
    • FreeBSD 发布工程:新主管上任
    • GhostBSD:从易用到挣扎与重生
    • BSD Now 与将来
    • 字符设备驱动教程(第三部分)
    • 学会走路——连接 GPIO 系统
    • FreeBSD 中对 SYN 段的处理
    • FreeBSD 2024 年秋季峰会
  • 2024-1112 虚拟化
    • 字符设备驱动程序教程(第二部分)
    • 面向 Linux 和 Windows 用户的 bhyve
    • Xen 与 FreeBSD
    • Wifibox:一种嵌入式虚拟化无线路由器
    • 嵌入式 FreeBSD:Fabric——起步阶段
    • DGP:一种新的数据包控制方法
    • 会议报告:我在都柏林的 EuroBSDCon 体验
  • 2024-0910 内核开发
    • 字符设备驱动程序教程
    • VPP 移植到了 FreeBSD:基础用法
    • 利用 Kyua 的 Jail 功能提升 FreeBSD 测试套件的并行效率
    • FreeBSD 上的 Valgrind
    • 嵌入式 FreeBSD:探索 bhyve
    • TCP/IP 历险记:FreeBSD TCP 协议栈中的 Pacing
    • 实用软件:实现无纸化(Paperless)
  • 2024-0708 存储与文件系统
    • FreeBSD 中的 NVMe-oF
    • FreeBSD iSCSI 入门
    • 使用 ZFS 原生加密保护数据
    • 嵌入式 FreeBSD:打造自己的镜像
    • TCP LRO 简介
    • 基于 Samba 的时间机器备份
  • 2024-0506 配置管理对决
    • 基本系统中的 mfsBSD
    • rdist
    • Hashicorp Vault
    • 在 GitHub 上向 FreeBSD 提交 PR
    • 悼念 Mike Karels
    • 2024 年 5-6 月来信
    • 嵌入式 FreeBSD 面包板
    • TCP/IP 历险记:TCP BBLog
    • 实用软件:开发定制 Ansible 模块
  • 2024-0304 开发工作流与集成
    • FreeBSD 内核开发工作流程
    • FreeBSD 与 KDE 持续集成(CI)
    • 更现代的内核调试工具
    • 从零开始的 ZFS 镜像及 makefs -t zfs
    • 提升 Git 使用体验
  • 2024-0102 网络(十周年)
    • FreeBSD 中的 RACK 栈和替代 TCP 栈
    • FreeBSD 14 中有关 TCP 的更新
    • if_ovpn 还是 OpenVPN
    • SR-IOV 已成为 FreeBSD 的重要功能
    • FreeBSD 接口 API(IfAPI)
    • BATMAN:更优的可移动热点网络方式
    • 配置自己的 VPN——基于 FreeBSD、Wireguard、IPv6 和广告拦截
    • 实用软件:使用 Zabbix 监控主机
  • 2023-1112 FreeBSD 14.0
    • LinuxBoot:从 Linux 启动 FreeBSD
    • FreeBSD 容器镜像
    • 现在用 Webhook 触发我
    • 新的 Ports 提交者:oel Bodenmann (jbo@freebsd.org)
  • 2023-0910 Port 与软件包
    • 回忆录:与 Warner Losh(@imp)的访谈
    • 在你自己的仓库中定制 Poudriere 源
    • Wazuh 和 MITRE Caldera 在 FreeBSD Jail 中的使用
    • PEP 517
    • CCCamp 2023 旅行报告
  • 2023-0708 容器与云
    • 在 Firecracker 上的 FreeBSD
    • 使用 pot 和 nomad 管理 Jail
    • 会议报告:C 与 BSD 正如拉丁语与我们——一位神学家的旅程
    • 抒怀之旅:与 Doug Rabson 的访谈
    • 基于 Jail 的广告拦截教程
    • 我们收到的来信
  • 2023-0506 FreeBSD 三十周年纪念特刊
    • CheriBSD 近十多年的历程
    • AArch64:成为 FreeBSD 新的一级架构
    • 岁月如梭:我个人的时间线
    • 安装 FreeBSD 1.0:回顾 30 年前
    • ZFS 是如何进入 FreeBSD 的呢?
    • 我不是来自约克郡的,我保证!
    • 回忆录:采访 David Greenman Lawrence
    • FreeBSD 和早期的 Unix 社区
    • 早期的 FreeBSD 移植
    • FreeBSD 30 周年:成功的秘诀
    • FreeBSD 在日本:回忆之旅与今日之实
  • 2023-0304 嵌入式
    • CheriBSD port 和软件包
    • 让我们来试试 ChatGPT
    • GPU 直通
  • 2023-0102 构建 FreEBSD Web 服务器
    • ZFS 的原子 I/O 与 PostgreSQL
    • 虚拟实验室——BSD 编程研讨会
    • ZFS 简介
    • 会议报告:落基山庆祝女性计算机科学家
    • 进行中的工作/征求反馈:数据包批处理
    • 基金会与 FreeBSD 桌面
  • 2022-1112 可观测性和衡量标准
    • 在 FreeBSD 的 DDB 内核调试器中编写自定义命令
    • DTrace:老式跟踪系统的新扩展
    • 基于证书的 Icinga 监控
    • 活动监控脚本(activitymonitor.sh)
    • 实用 IPv6(第四部分)
    • EuroBSDCon 会议报道
    • 实用 Port:Prometheus 的安装与配置
    • 书评:《用火解决问题:管理老化的计算机系统(并为现代系统保驾护航)》Kill It with Fire: Manage Aging Computer Systems (and Future Proof Modern Ones)
  • 2022-0910 安全性
    • CARP 简介
    • 重构内核加密服务框架
    • PAM 小窍门
    • SSH 小窍门
    • 实用 IPv6(第三部分)
    • 书评:Understanding Software Dynamics(深入理解软件性能——一种动态视角)—— Richard L. Sites 著
    • 访谈:保障 FreeBSD 安全性
    • MCH 2022 会议报告
  • 2022-0708 科研、系统与 FreeBSD
    • 在 FreeBSD 上构建 Loom 框架
    • 教授本科生 Unix 课程
    • FreeBSD 入门研讨会
    • 实用 IPv6(第二部分)
    • 在 2022 年及以后推广 FreeBSD
    • 进行中的工作/征求反馈:Socket 缓冲区
    • FreeBSD 开发者峰会报告
    • 支持 Electromagnetic Field 2022
  • 2022-0506 灾难恢复
    • 使用 FreeBSD 构建高弹性的私有云
    • LLDB 14 —— FreeBSD 新调试器
    • 实用 IPv6(第一部分)
    • 利用 netdump(4) 进行事后内核调试
    • 进行中的工作/征求反馈:FreeBSD 启动性能
    • 实用 Port:在 OpenZFS 上设置 NFSv4 文件服务器
  • 2022-0304 ARM64 是一级架构
    • FreeBSD/ARM64 上的数据科学
    • Pinebook Pro 上的 FreeBSD
    • 嵌入式控制器的 ACPI 支持
    • 进行中的工作/征求反馈:Lumina 桌面征集开发人员
    • 实用 Port:如何设置 Apple 时间机器
  • 2022-0102 软件与系统管理
    • 为 FreeBSD Ports 做贡献
    • 使用 Git 贡献到 FreeBSD Ports
    • CBSD:第一部分——生产环境
    • 将 OpenBSD 的 pf syncookie 代码移植到 FreeBSD 的 pf
    • 进行中的工作/征求反馈:mkjail
    • 《编程智慧:编程鬼才的经验和思考》(The Kollected Kode Vicious)书评
    • 会议报告:EuroBSDCon 2021 我的第一次 EuroBSDCon:一位新组织者的视角
  • 2021-1112 存储
    • 开放通道 SSD
    • 构建 FreeBSD 社区
    • 与完美操作系统同行 27 年
    • 进行中的工作/征求反馈:OccamBSD
    • 通过 iSCSI 导入 ZFS ZIL——不要在工作中这样做——就像我做的那样
  • 2021-0910 FreeBSD 开发
    • FreeBSD 代码审查与 git-arc
    • 如何为 FreeBSD 实现简单的 USB 驱动程序
    • 内核开发技巧
    • 程序员编程杂谈
  • 2021-0708 桌面/无线网
    • 通往 FreeBSD 桌面的直线路径
    • FreeBSD 13 中的人机接口设备 (HID) 支持
    • Panfrost 驱动程序
    • 用 Git 更新 FreeBSD
    • FreeBSD 的新面孔
    • 想给你的桌面加点佐料?
  • 2021-0506 安全
    • 七种提升新安装 FreeBSD 安全性的方法
    • copyinout 框架
    • 使用 TLS 改善 NFS 安全性
    • Capsicum 案例研究:Got
    • 对 Jail 进行安全扫描
  • 2021-0304 FreeBSD 13.0
    • 展望未来
    • FreeBSD 13.0 工具链
    • FreeBSD 13.0 中有新加载器吗?
    • TCP Cubic 准备起飞
    • OpenZFS 中的 Zstandard 压缩
    • 会议报告:FreeBSD 供应商峰会
    • Git 不够吗?
  • 2021-0102 案例研究
    • Tarsnap 的 FreeBSD 集群
    • BALLY WULFF
    • Netflix Open Connect
    • FreeBSD 的新面孔
    • 写作学者的 FreeBSD
    • 在世界之巅
  • 2020-1112 工作流/持续集成(CI)
    • FreeBSD Git 快速入门
    • 使用 syzkaller 进行内核 Fuzzing
    • Mastering Vim Quickly 书评
    • 线上会议实用技巧
    • 在控制台上进行网络监控
  • 2020-0910 贡献与入门
    • 采访:Warner Losh,第 2 部分
    • 代码审查
    • 撰写良好的提交消息
    • 如何在不是程序员的情况下做出贡献——成为 FreeBSD 译者
    • 如何成为文档提交者
    • 谷歌编程之夏
    • 为 FreeBSD 期刊撰写文章
    • 你为什么使用 FreeBSD
    • FreeBSD 的新面孔
  • 2020-0708 基准测试/调优
    • FreeBSD Friday
    • 采访:Warner Losh,第 1 部分
    • 构建和运行开源社区
    • 在 FreeBSD 上轻松搭建我的世界(Minecraft)服务器
    • FreeBSD 的新面孔
  • 2020-0506 网络性能
    • 内核中的 TLS 卸载
    • 访谈:Michael W Lucas
    • FreeBSD 桌面发行版
    • 使用 Poudriere 进行 Port 批量管理
    • FreeBSD 的新面孔
由 GitBook 提供支持
LogoLogo

FreeBSD 中文社区(CFC) 2025

在本页
  • FreeBSD 中的内存复制函数
  • ABI 支持
  • 系统调用处理程序
  • 兼容层
  • copyinout API
  • copyinout 框架
  • 使用 copyinout API 进行内存拷贝
  • 结论
  • 后续工作
  • 参考文献
在GitHub上编辑
导出为 PDF
  1. 2021-0506 安全

copyinout 框架

上一页七种提升新安装 FreeBSD 安全性的方法下一页使用 TLS 改善 NFS 安全性

最后更新于2个月前

——FreeBSD 和 CheriBSD 中的 ABI 独立、类型感知、能力感知、copyin 和 copyout API

  • 原文链接:

  • 作者:KONRAD WITASZCZYK

在用户空间和内核空间之间的内存复制是系统调用中的一个关键操作。它用于复制系统调用的参数以及系统调用的结果。目前的复制函数原型是与类型无关的,复制一定数量的字节从任意缓冲区。当内核将其内存复制到用户空间时,必须确保不会泄露任何可能包含机密数据的内核数据。本文概述了 FreeBSD 和 CheriBSD 中当前复制函数的局限性,并提出了一个框架,可以提高系统调用处理程序的安全性和代码质量。

所述的 copyinout 框架是作为题为《能力感知内存复制在地址空间之间》的硕士论文的一部分实现的,该论文在哥本哈根大学的 Ken Friis Larsen 和微软研究院的 David Chisnall 的指导下完成。类型感知的 copyin 和 copyout API 的最初构想是由 David Chisnall 提出的。

FreeBSD 中的内存复制函数

FreeBSD 包含两个主要函数,用于在地址空间之间复制内存:copyin() 和 copyout()(见清单 1)。这两个函数接受三个参数:源地址、目标地址和要复制的字节数。copyin() 将 len 字节从用户空间地址 udaddr 复制到内核空间地址 kaddr。copyout() 方向相反,将 len 字节从内核空间地址 kaddr 复制到用户空间地址 udaddr。这些函数在成功时返回 0,如果传递了无效地址,则返回 EFAULT。

int copyin(const void * __restrict udaddr, void * _Nonnull __restrict kaddr, size_t len);
int copyout(const void * _Nonnull __restrict kaddr, void * __restrict udaddr, size_t len);

清单 1.** FreeBSD 13.0-RELEASE 中的 copyin() 和 copyout() 函数原型

从函数原型中,我们可以识别出一个潜在的安全问题。复制函数操作的是任意缓冲区。如果缓冲区包含一个结构体对象,而该结构体字段之间有填充,那么填充部分也会被复制。包含敏感信息的填充泄露通常被称为内核内存泄露或内核内存泄漏。这类漏洞可能导致特权提升。它们不仅仅是 FreeBSD 特有的,许多操作系统中都可以发现类似问题 [3] [4] [5],并且它们已成为广泛的检测 [6] 和缓解 [7] [8] [9] 技术研究的主题。

复制函数被系统调用处理程序使用,用于将系统调用参数从用户空间复制到内核空间,并将系统调用结果从内核空间复制到用户空间。由于系统调用非常频繁地执行,内核开发人员必须提供最低开销的复制函数实现。这可以通过提供与机器相关的汇编语言实现来实现。根据 CPU 架构,复制函数还可以利用 CPU 模型提供的安全功能。例如,针对 amd64(见 amd64/amd64/support.S)的实现支持监督模式访问防护(SMAP)。

ABI 支持

FreeBSD 支持多种 ABI。具体而言:

  • 针对与内核编译目标相同的程序的本地 ABI;

  • 针对内核编译的架构的 32 位版本的 32 位 ABI;

  • 针对 Linux 用户空间程序的 Linux ABI

这些 ABI 作为兼容层实现。每个兼容层提供其系统调用处理程序,这些处理程序实现了在内核进入或返回内核例程时,必须执行的额外逻辑。这包括在地址空间之间复制和转换对象。例如,对于 32 位 ABI,系统调用参数或系统调用结果中的指针必须从 32 位指针转换或转换为 32 位指针。

系统调用处理程序

每当用户空间程序发出系统调用时,内核会调用一个特定的系统调用处理程序,并将已复制的系统调用参数传入。对于每个支持的 ABI,内核保持一个 sysentvec 结构对象(见清单 2),描述 ABI 特定的属性和在程序执行时内核将使用的函数。该结构包括包含系统调用处理程序函数指针的 sv_table 数组,这些指针存储在由相应的系统调用编号指定的位置,以及指向一个架构特定函数的 sv_fetch_syscall_args 函数指针,该函数用于复制系统调用参数。

作为示例,我们来考虑 jail(2) 系统调用。这个系统调用有一个参数:指向 jail 结构对象的指针(见清单 3)。jail 结构包含描述 jail 的参数(见清单 4)。一旦用户空间程序执行 jail 系统调用并进入特权模式,内核将调用 jail 系统调用处理程序——sys_jail() 函数(见清单 5),并将已经复制进来的 jail 系统调用参数——jail_args 结构对象传入。系统调用处理程序复制一个 jail 结构,并调用实现 jail 系统调用逻辑的 kern_jail 内核例程。

struct sysentvec {
 (...)
 struct sysent *sv_table;
 (...)
 int (*sv_fetch_syscall_args)(struct thread *);
 (...)
}

清单 2. 描述 ABI 特定属性和函数的 sysentvec 结构。

struct jail_args {
 char jail_l_[PADL_(struct jail *)];
 struct jail * jail;
 char jail_r_[PADR_(struct jail *)];
};

清单 3. 本地 ABI 的 jail 系统调用参数结构。

struct jail {
 uint32_t version;
 char *path;
 char *hostname;
 char *jailname;
 uint32_t ip4s;
 uint32_t ip6s;
 struct in_addr *ip4;
 struct in6_addr *ip6;
};

清单 4. 本地 ABI 的 jail 结构。

int
sys_jail(struct thread *td, struct jail_args *uap)
{
 (...)
 int error;
 struct jail j;
 (...)
 error = copyin(uap->jail, &j, sizeof(struct jail));
 if (error)
 return (error);
 (...)
 return (kern_jail(td, &j));
}

清单 5. 本地 ABI 的 jail 系统调用处理程序。

兼容层

兼容层为非本地 ABI 提供系统调用实现。特别地,32 位 ABI 被实现为 freebsd32 兼容层。让我们考虑 32 位 ABI 下相同的 jail 系统调用。32 位版本的 jail 参数结构称为 freebsd32_jail_args(见清单 6),它包括指向 32 位版本的 jail 结构(称为 jail32,见清单 7)的指针。jail 和 jail32 结构之间的唯一区别是架构无关的字段类型。每个指针都被替换为 32 位无符号整数。由于指针在 32 位架构中是 32 位无符号整数,这一变化保证了为 64 位内核编译的 jail32 结构与为 32 位内核编译的 jail 结构具有相同的布局。

32 位 ABI 的 jail 系统调用处理程序实现为 freebsd32_jail 函数(见清单 8)。该函数复制一个 32 位的 jail 对象,使用宏(见清单 9)为其本地 ABI 版本翻译每个字段,并调用与本地 ABI 情况下相同的 kern_jail 内核例程。这意味着本地 ABI 和 32 位 ABI 在 jail 系统调用处理程序中的唯一区别是将用户空间的 jail 对象转换为可以被内核使用的本地 ABI 版本。

struct freebsd32_jail_args {
 char jail_l_[PADL_(struct jail32 *)];
 struct jail32 * jail;
 char jail_r_[PADR_(struct jail32 *)];
};

清单 6. 32 位 ABI 的 jail 系统调用参数结构。

struct jail32 {
 uint32_t version;
 uint32_t path;
 uint32_t hostname;
 uint32_t jailname;
 uint32_t ip4s;
 uint32_t ip6s;
 uint32_t ip4;
 uint32_t ip6;
};

清单 7. 32 位 ABI 的 jail 结构。

int
freebsd32_jail(struct thread *td, struct freebsd32_jail_args *uap)
{
 (...)
 int error;
 struct jail j;
 (...)
 struct jail32 j32;
 error = copyin(uap->jail, &j32, sizeof(struct jail32));
 if (error)
 return (error);
 CP(j32, j, version);
 PTRIN_CP(j32, j, path);
 PTRIN_CP(j32, j, hostname);
 PTRIN_CP(j32, j, jailname);
 CP(j32, j, ip4s);
 CP(j32, j, ip6s);
 PTRIN_CP(j32, j, ip4);
 PTRIN_CP(j32, j, ip6);
 (...)
 return (kern_jail(td, &j));
}

清单 8. 32 位 ABI 的 jail 系统调用处理程序。

#define CP(src, dst, fld) do { \
 (dst).fld = (src).fld; \
} while (0)
#define PTRIN(v) (void *)(uintptr_t)(v)
#define PTRIN_CP(src, dst, fld) do { \
 (dst).fld = PTRIN((src).fld); \
} while (0)

清单 9. 32 位 ABI 的 jail 系统调用处理程序使用的辅助宏

copyinout API

前面描述的内核内存泄漏和代码重复问题是由于 copy 函数缺乏类型意识引起的。如果 copyin() 和 copyout() 函数能理解存储在复制缓冲区中的底层对象的结构,它们就能复制对象的字段并在用户进程 ABI 与内核 ABI 不同的情况下进行转换。

为了解决这些问题,我们引入了 copyinout API。对于每个在用户空间和内核空间之间复制的类型 foo,我们引入了类型感知的 copy 函数变体(见清单 10),它们会复制类型 foo 的每个字段,并根据源 ABI 转换为目标 ABI,例如,将 32 位指针转换为 64 位指针,适用于 64 位架构上的 32 位进程。此外,copy 函数还可以根据 CPU 型号执行额外的操作,例如,针对 CHERI CPU 编译的内核可以创建 CHERI 能力来为字段设置边界或权限。与原始 copy 函数不同,类型感知的 copy 函数只需要两个参数:源地址和目标地址。copyin_foo() 将存储在 uaddr 用户空间地址的对象复制到存储在 kaddr 内核空间地址的对象。copyout_foo() 以相反的方式工作,将存储在 kaddr 内核空间地址的对象复制到存储在 uaddr 用户空间地址的对象。例如,前面描述的 jail 结构可以使用以下函数调用进行复制:

copyin_jail(uap->jail, &j);
int copyin_foo(const void *uaddr, struct foo *kaddr);
int copyout_foo(const struct foo *kaddr, const void *uaddr);

清单 10. 类型 foo 的 copyin() 和 copyout() 函数变体

copyinout 框架

类型感知的 copy 函数的实现与 copyinout API 本身是独立的。为了提供这些实现,我们引入了 copyinout 框架,该框架由以下部分组成:

  • 描述应该为某个类型生成哪些 copy 函数的类型注解;

  • 每个 ABI 的 copy 函数指针表;

  • 一个内核接口,用于动态注册 copy 函数并调用它们;

  • 一个代码生成工具,用于根据类型注解生成 copy 函数

内核定义了类型注解,指示应该为某个类型生成什么样的 copy 函数:

  • __copyin 生成 copyin();

  • __copyout 生成 copyout();

  • __copyinout 生成 copyin() 和 copyout()。

此外,内核定义了字段注解,描述字段中存储的值:

  • __uaddr_array(bar) 用于存储指向数组的指针,该数组的元素数量存储在字段 bar 中;

  • __uaddr_bounded(bar) 用于存储指向缓冲区的指针,该缓冲区的字节数存储在字段 bar 中;

  • __uaddr_code 用于存储代码指针的字段;

  • __uaddr_object 用于存储指向对象的指针的字段;

  • __uaddr_unbounded 用于存储指向具有未知字节数的缓冲区的指针的字段;

  • __uaddr_str 用于存储指向字符串的指针的字段,因此可以通过 strlen() 来计算其边界。

字段注解可以用于生成利用安全相关机制的 copy 函数,例如构建 CHERI 能力。通过集成 copyinout 框架,内核开发者只需为清单 11 中定义的类型 foo 添加适当的注解并运行代码生成工具,就可以生成新的 copy 函数。在这种情况下,生成的 copy 函数复制字段 len 并翻译存储在指针数组中的指针。此外,在 CheriBSD 中,它们构建了带有边界的能力数组,边界值设置为存储在字段 len 中的值,作为字段翻译的一部分。

struct foo {
size_t len;
int *array;
};
struct foo {
size_t len;
__uaddr_array(len) int * __capability array;
} __copyinout;

清单 11. 类型 foo 在 copyinout 变更前后的定义。

为了为不同的 ABIs 提供单独的 copy 函数,copyinout 框架实现了 copyinout 表,该表包含特定类型的 copyin() 和 copyout() 函数指针(见清单 12),作为 sysentvec 结构的一部分(见清单 13,与清单 2 对比)。每个 ABI 分配自己的 copyinout 表,并通过 SYSINIT 框架使用 Linker Set 技术动态填充条目 [10]。这些 ABIs 的 SYSINIT() 和 SYSUNINIT() 宏调用与生成的 copy 函数一起由代码生成工具生成。这允许生成用于内核模块中的 copy 函数,并在模块加载和卸载时注册和注销。通过 copyinout 表,copyinout API 可以定义为内核中的宏,这些宏会强制转换指针并调用与当前正在运行的线程 ABI 对应的 copyinout 表中的函数(见清单 14)。特定函数的 copyinout 表条目索引是一个全局变量,在 SYSINIT() 调用过程中初始化。例如,类型 foo 及其生成的函数具有一个关联变量 copyinout_foo_idx,该变量可以通过 COPYIN_CALL() 和 COPYOUT_CALL() 宏间接使用,以确定函数地址并进行函数调用(见清单 15)

typedef int copyin_t(const void * __capability uaddr, void * __capability kaddr);
typedef int copyout_t(const void * __capability kaddr, void * __capability uaddr);
struct copyinout {
 copyin_t *ce_copyin;
 copyout_t *ce_copyout;
};

清单 12. 包含特定类型的 copy 函数指针的 copyinout 结构。

struct sysentvec {
 (...)
 struct sysent *sv_table;
 (...)
 int (*sv_fetch_syscall_args)(struct thread *);
 (...)
 const struct copyinout *sv_copyinout;
}

清单 13. 带有 copyinout 表的 sysentvec 结构。

#define THREAD_COPYINOUT(thread, type) \
 (thread)->td_proc->p_sysent->sv_copyinout[copyinout_##type##_idx]
#define COPYIN_FUN(type) \
 THREAD_COPYINOUT(curthread, type).ce_copyin
#define COPYIN_CALL(type, uaddr, kaddr) \
 ((int (*)(const void * __capability, \
 struct type * __capability))COPYIN_FUN(type)) \
 (uaddr, kaddr)
#define COPYOUT_FUN(type) \
 THREAD_COPYINOUT(curthread, type).ce_copyout
#define COPYOUT_CALL(type, kaddr, uaddr) \
 ((int (*)(const struct type * __capability, \
 void * __capability))COPYOUT_FUN(type)) \
 (kaddr, uaddr)

清单 14. 用于 copyinout API 调用的内核宏。

#define copyin_foo(uaddr, kaddr) \
 COPYIN_CALL(foo, uaddr, kaddr)
#define copyout_foo(kaddr, uaddr) \
 COPYOUT_CALL(foo, kaddr, uaddr)

清单 15. 类型 foo 的 copyinout API 函数调用宏。

copyinout 框架的主要部分是一个用 C++ 编写的代码生成工具,使用 libclang 库进行代码分析。它遍历包含所有应生成 copy 函数的类型定义的头文件输入 Clang AST 树,并打印 copy 函数的原型、定义或实现(见清单 16)。AST 树可以通过以下命令生成:

clang -Xclang -ast-dump -fsyntax-only -c header-file

copyinout 框架包含一个辅助脚本,该脚本为输入的基础源代码树生成 AST 树,运行 copyinout 工具,并将生成的函数放入基础源代码树中。这个脚本可以被添加到 FreeBSD 构建系统中,以实现代码生成的自动化。

目前,函数实现可以为任何架构(通用)生成 C 语言代码,或者为 MIPS 和 CHERI-MIPS 架构生成汇编语言代码。例如,清单 17 展示了由 copyinout 工具为类型 foo(见清单 11)和原生 ABI(一个混合的 CheriBSD 内核)生成的汇编语言版本的 copyin() 函数。

$ ./copyinout
用法:copyinout 原型 kernel-space-ast
 copyinout 定义 native|freebsd32|cheri kernel-space-ast
 copyinout 实现 native|freebsd32|cheri generic|mips user-space-ast
kernel-space-ast

清单 16. copyinout 工具的用法。

struct foo {
 size_t len;
 __uaddr_array(len) int * __capability array; |
} __copyinout;
LEAF(native_copyin_foo)
 cgetbase t0 , $c3
 blt t0, zero, _C_LABEL(copyerr)
 nop
 GET_CPU_PCPU(v1)
 PTR_L t0, PC_CURPCB(v1)
 PTR_LA t1 , copyerr
PTR_S t1, U_PCB_ONFAULT(t0)
cld v0, zero, 0($c3)
 PTR_S zero, U_PCB_ONFAULT(t0)
 csd v0, zero, 0($c4)

PTR_S t1, U_PCB_ONFAULT(t0)
 cld v0, zero, 8($c3)
 cfromptr $c5 , $ddc , v0
 cld v0, zero, 0($c3)
 li a0, 96
 multu a0, v0
 mflo v0
 csetbounds $c5 , $c5 , v0
 PTR_S zero, U_PCB_ONFAULT(t0)
 csc $c5, zero, 16($c4)

 j ra
 move v0, zero
END(native_copyin_foo)

清单 17. 为类型 foo 和本地 ABI(混合 CheriBSD 内核)生成的 copyin() 函数。

使用 copyinout API 进行内存拷贝

有了 copyinout 框架,我们可以简化系统调用结构和处理程序。对于之前讨论的 jail 系统调用,我们可以用一个单一的结构体替代 jail(见 清单 4)和 jail32(见 清单 7)结构体(见 清单 18)。对于本地 ABI 的 jail 系统调用(见 清单 5),我们可以修改为使用类型感知的 copyin() 函数变体 copyin_jail()(见 清单 19)。由于 copyin_jail() 函数也是 ABI 独立的,并且可以自动将 32 位 ABI 对象转换为其本地 ABI 版本,我们还可以修改 32 位 ABI 的 jail 系统调用处理程序(见 清单 8),直接调用 sys_jail() 函数(见 清单 20)。

struct jail {
 uint32_t version;
 __uaddr_str char * __capability path;
 __uaddr_str char * __capability hostname;
 __uaddr_str char * __capability jailname;
 uint32_t ip4s;
 uint32_t ip6s;
 __uaddr_array(ip4s) struct in_addr * __capability ip4;
 __uaddr_array(ip6s) struct in6_addr * __capability ip6;
} __copyinout;

清单 18. 对所有 ABI 应用 copyinout 变更后的 jail 结构。

int
sys_jail(struct thread *td, struct jail_args *uap)
{
 (...)
 int error;
 struct jail j;
 (...)
 error = copyin_jail(uap->jail, &j);
 if (error)
 return (error);
 (...)
 return (kern_jail(td, &j));
}

清单 19. 对本地 ABI 应用 copyinout 变更后的 jail 系统调用处理程序。

int
freebsd32_jail(struct thread *td, struct freebsd32_jail_args *uap)
{
 struct jail_args args;
 args.jailp = uap->jailp;
 return (sys_jail(td, &args));
}

清单 20. 对 32 位 ABI 应用 copyinout 变更后的 jail 系统调用处理程序。

结论

copyinout 框架的初步实现表明,生成拷贝函数可以提高系统调用处理程序的代码质量,并消除填充中的潜在内核内存泄漏。然而,代码生成工具必须改进,以支持更复杂的数据类型,并为所有支持的平台生成汇编拷贝函数实现。虽然框架的工作已经暂停了一段时间,但我们希望能够尽快恢复并实施这些改进。

后续工作

除了 copyinout 框架的基本功能外,还有几个想法可以在未来改进或应用它:

  • 汇编拷贝函数实现优化;

    当前的汇编实现没有使用任何优化技术来最小化在拷贝过程中使用的寄存器数量或周期。例如,两个相邻的半字字段可以通过一条加载指令和一个寄存器加载为一个字,而不是使用两条加载指令。

  • 系统调用处理程序的简化;

    兼容层实现了许多系统调用处理程序,这些处理程序只是将对象在 ABI 之间转换,并没有为它们的本地 ABI 对应项引入任何额外的逻辑。将 copyinout API 应用于这些处理程序后,系统调用处理程序似乎是多余的。例如,32 位 ABI 的 jail 系统调用处理程序(见 清单 20)仅将指向 jail 对象的指针从 jail 系统调用参数结构中复制,并调用本地 ABI 的 jail 系统调用处理程序。将 copyinout 框架应用于系统调用参数结构并从兼容层中删除这些系统调用处理程序将是一个有趣的尝试。

  • 面向仿真器的跨平台兼容层。

    在 QEMU 中,用户模式仿真允许在与主机架构不同的架构上运行程序,而不需要完全的系统仿真。每次遇到系统调用时,QEMU 会将系统调用参数从仿真 ABI 版本转换为主机用户空间 ABI 版本,在主机上执行系统调用,并将结果转换回其仿真 ABI 版本。我们可以研究是否可以实现一个兼容层,包含针对仿真平台的系统调用处理程序,并通过 copyinout 框架直接从仿真用户空间复制和翻译系统调用对象。

参考文献

[1] CTSRD. CheriBSD. FreeBSD 适配 CHERI-MIPS、CHERI-RISC-V 和 Arm Morello. https://www.cl.cam.ac.uk/research/security/ctsrd/cheri/cheribsd.html.

[2] Konrad Witaszczyk. 跨地址空间的能力感知内存拷贝。哥本哈根大学,2019 年。

[3] The FreeBSD Project. FreeBSD-EN-18:12.mem. https://www.freebsd.org/security/advisories/FreeBSD-EN-18:12.mem.asc.

[4] The MITRE Corporation. CVE-2017-16994. https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16994.

[5] The MITRE Corporation. CVE-2010-4082. https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-4082.

[6] Mateusz Jurczyk. 使用 x86 仿真和污点追踪检测内核内存泄漏。https://googleprojectzero.blogspot.com/2018/06/detecting-kernel-memory-disclosure.html.

[7] Alexander Popov. 引入 STACKLEAK 功能并为其添加测试。https://lwn.net/Articles/735584/.

[8] Kees Cook. mm: 强化用户拷贝。https://lwn.net/Articles/693745/.

[9] Thomas Barabosch, Maxime Villard. KLEAK: 实用的内核内存泄漏检测。https://www.netbsd.org/gallery/presentations/maxv/kleak.pdf.

[10] The FreeBSD Project. FreeBSD 架构手册,第 5 章。SYSINIT 框架。https://docs.freebsd.org/en/books/arch-handbook/sysinit/.

[11] Robert N. M. Watson 等人。能力硬件增强 RISC 指令:CHERI 指令集架构(第 8 版)。技术报告 UCAM-CL-TR-951,剑桥大学计算机实验室,2020 年。

[12] Robert N. M. Watson 等人。CHERI 简介。技术报告 UCAM-CL-TR-941,剑桥大学计算机实验室,2019 年。


KONRAD WITASZCZYK 是剑桥大学的研究员,参与 CHERI 项目。他毕业于雅盖隆大学,取得理论计算机科学学士学位,取得哥本哈根大学计算机科学硕士学位,并在 Fudo Security 工作了近 7 年,致力于 FreeBSD 及其与安全相关的技术。目前,Konrad 住在波兰华沙。

The copyinout Framework