LWN:新增ioctl来检测内存被写入过!

共 4167字,需浏览 9分钟

 ·

2023-08-25 04:18

关注了就能看到更多这么棒的文章哦~

An ioctl() call to detect memory writes

By Jonathan Corbet
August 10, 2023
ChatGPT assisted translation
https://lwn.net/Articles/940704/

kernel 需要知道一个进程的内存是何时被写入的,这个用处很多,其中之一就是可以用来确定哪些页面(page)可以立即回收或者该要将脏页(dirty page)写入后备存储(backing store)。然而,有时用户空间也需要以可靠且快速的方式访问这些信息。这个来自 Muhammad Usama Anjum 的补丁就为此目的添加了一个新的 ioctl()调用;尽管使用它时需要以不寻常的方式重用一个现有的系统调用。

这个特性的目的似乎是为了高效地实现对 Windows GetWriteWatch() 系统调用的模拟,这显然对那些希望防范某些类型作弊的游戏开发人员非常有用。一个能够访问(并修改)游戏内存的玩家可以以不受开发人员或其他玩家欢迎的方式来改变游戏功能。通过使用 GetWriteWatch(),游戏能够检测到关键数据结构被外部参与者修改的情况,展示一个时尚的“Tilt”提示,并终止游戏会话。

实际上,Linux 现在通过/proc 中的 pagemap 文件提供了这个功能。可以从这个文件中读取到一系列页面的当前是否 dirty 的状态,并写入关联的 clear_refs 文件来将 dirty 状态重置(例如,在游戏自身写入所需内存后非常有用)。然而,从用户空间访问此文件时速度较慢,这与大多数游戏的需求背道而驰。新的 ioctl()调用旨在更高效地实现这个功能。在用户空间中使用 Checkpoint/Restore In Userspace (CRIU)项目也可以利用更高效的机制来检测写入;在这种情况下,目标是识别在开始了 checkpoint 过程后已被修改的页面。

Soft dirty deemed insufficient

内核的“soft dirty”机制,提供了 pagemap 文件,看起来是实现这个特性的最合适的基础。只需要一个更高效的机制来查询数据并为特定范围的 page 重置 soft dirty 信息。然而,根据 patch 封面邮件中的描述,这个方法最终效果不佳。还有其他各种操作比如虚拟内存区域合并或 mprotect()调用,也都可能会导致页面被报告为 dirty,哪怕它们没有被真正写入。这反过来可能会导致游戏错误地得出结论,以为自己内存已被篡改。

这可能会导致不良的结果。当游戏玩家在玩到一个任务的关键点时被告知检测到了作弊行为并且游戏立即结束了,那么可想而知玩家会有多么愤怒。

显然,修复这个误报问题不是一个可有可无的选项,所以人们决定要通过一个意想不到的路径来解决它。userfaultfd()系统调用允许一个进程负责给定内存范围的自己的 page fault 的处理工作。相关 patch set 为 userfaultfd()添加了一个新的操作(UFFD_FEATURE_WP_ASYNC),以更改如何处理 write-protect fault。内核将不会把这些错误传递给用户空间,而是会简单地为相关 page 恢复写权限,让出现 fault 的进程继续运行。

因此,userfaultfd()原本设计用于在用户空间处理错误,现在被用于直接在内核内部来修改 fault 处理逻辑,而无需用户空间参与。然而,对于这个用例来说,这种方法确实有一些优势:它可以针对特定内存范围使用写保护方式来捕获写操作,从而更可靠地报告内存写入动作。要查看哪些页面已被写入了的话,只需要查询写保护状态;如果页面已被设置为可写,那么它就已被写入过了。

The ioctl() interface

有了这个功能之后,就可以创建一个用于查询结果的接口。这是通过打开与所关注的进程相关的 pagemap 文件,然后发起新增的 PAGEMAP_SCAN ioctl()调用来实现的。这个调用可以接受一个相对复杂的结构作为参数:

struct pm_scan_arg {
__u64 size;
__u64 flags;
__u64 start;
__u64 end;
__u64 walk_end;
__u64 vec;
__u64 vec_len;
__u64 max_pages;
__u64 category_inverted;
__u64 category_mask;
__u64 category_anyof_mask;
__u64 return_mask;
};

size 参数包含结构本身的大小;它存在的目的是为了实现对未来添加更多字段时的向后兼容。有两个 flags 值将在下面描述。要查询的地址范围由 start 和 end 指定;walk_end 字段将由内核更新,从而指示 page scan 实际结束的位置。vec 指向一个数组,其中包含了 vec_len 个结构(如下所述),用于填充想要的信息。

最后四个字段描述了调用者要查找的信息。有六个 page “类别(category)”可以报告出来:

  • PAGE_IS_WPALLOWED:页面保护允许写入。

  • PAGE_IS_WRITTEN:页面已被写入。

  • PAGE_IS_FILE:页面由文件支持(is backed by a file)。

  • PAGE_IS_PRESENT:页面存在于 RAM 中。

  • PAGE_IS_SWAPPED:(匿名)页面已被写入交换空间。

  • PAGE_IS_PFNZERO:页表项指向零页(zero page)。

每个页面都属于一些组合类别;调用这个 API 的程序会希望知晓这些范围内属于一些(或不属于)这些类别的子集的页面。mask 的用法在相关 patch 中有所描述,尽管读起来挺困难。代码本身通过以下方式来确定根据 categories 所描述的特定页面是否关注:

categories ^= p->arg.category_inverted;
if ((categories & p->arg.category_mask) != p->arg.category_mask)
return false;
if (p->arg.category_anyof_mask && !(categories & p->arg.category_anyof_mask))
return false;
return true;

或者换成文字描述:首先,使用 category_inverted 这个 mask 来翻转所选择的任何 category 的含义;这相当于是选择了不是指定 category set 的 page。然后,得到的就是所有具有由 category_mask 描述的 category 集合了,并且如果 category_anyof_mask 不为零,则还必须设置其中至少一个 category。如果所有这些检测都成功了,这个 page 就是感兴趣的;否则将被跳过。

在扫描之后,这些页面会在 vec 中返回给用户空间,vec 是一个包含这种结构的数组:

struct page_region {
__u64 start;
__u64 end;
__u64 categories;
};

这个请求中的 return_mask 字段用于将感兴趣的 page 来合并(collapse)成具有相同 category 的区域;对于每个这样的区域,所返回的结构都描述了其包含的地址范围和实际的 category set。

最后,回到 flags 参数,它有两种取值。如果设置了 PM_SCAN_WP_MATCHING,会在注意到它们的状态后,对所有选择的页面进行写保护;这是为了允许检查已被写入的页面,并为下次检查重置状态。如果设置了 PM_SCAN_CHECK_WPASYNC,如果内存区域没有像上面描述的那样通过 userfaultfd()进行设置,整个操作将被中止。

Checking for cheaters

为了实现最初的目标,也就是确定指定范围内的页面是否已被修改,第一步就是调用 userfaultfd()来设置写保护处理。然后,应用程序可以偶尔使用上述两个 flag 来发起这个新增的 ioctl()调用,并且将 category_mask 和 return_mask 都设置为 PAGE_IS_WRITTEN。如果没有页面被写入的话,将不会返回任何结果;否则,返回的结构会指向修改发生的位置。与此同时,已被写入的 page 将重置其写保护状态,以备下一次扫描。

截至撰写本文时,这个系列已经有令人印象深刻的第 27(28)个修订版本。它最初于 2022 年中期提出,作为一个名为 process_memwatch()的新系统调用,最终变成了一个 ioctl()调用。显然有人有动力要将这个功能合并到内核中,但内存管理社区有多感兴趣就不完全清楚了。这项工作尚未进入 linux-next,最近的帖子也没有引出很多 review 意见。然而,似乎确实存在这个功能的使用案例,因此总需要在某个时候做出决定。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~



浏览 513
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报