Skip to content

[dfs][9pfs] fix: clamp server-controlled lengths to the received reply - #11918

Open
usualtreee wants to merge 1 commit into
RT-Thread:masterfrom
usualtreee:fix/9pfs-bounds
Open

usualtreee wants to merge 1 commit into
RT-Thread:masterfrom
usualtreee:fix/9pfs-bounds

Conversation

@usualtreee

@usualtreee usualtreee commented Oct 22, 2026 •

Copy link
Copy Markdown

拉取/合并请求描述:(PR description)

为什么提交这份PR (why to submit this PR)

修复 issue #11289:9PFS 客户端在 dfs_9pfs_read() / dfs_9pfs_getdents() 中信任服务器可控的长度字段,造成越界读(ASAN use-after-poison,读取越过接收缓冲区 8.3 MB)和目录信息泄露。

p9_transaction() 的返回字节数被丢弃(RT_NULL),随后代码直接使用报文里的 count、每条记录的 stat_size 和 name_len,没有任何与实际收到数据量的比对;当服务器返回一个 count 巨大但实际很短的报文时,解析器会一路读越接收缓冲区。正常情况下,被跳过的 . 记录不会走到 fd->pos >= end 退出条件,解析器可持续前进。

你的解决方案是什么 (what is your solution)

  1. dfs_9pfs_read() / dfs_9pfs_getdents() 均把实际收到的字节数传给 p9_transaction()(&rx_size),并校验 rx_size >= P9_MSG_READ_DATA(否则返回 P9_READ_UNEXPECTED_DATA / -EIO);
  2. count 收敛到 rx_size - P9_MSG_READ_DATA(不信任报文值);
  3. getdents 逐记录校验:剩余字节不足 sizeof(rt_uint16_t) 时停止;stat_size 不得超出剩余数据;记录至少能容纳到 name 长度字段;name_len 不得越过当前记录;
  4. read 路径同样收敛 got(got <= set 并不充分,因为 set 本身可能超过接收容量)。

修改文件:components/dfs/dfs_v1/filesystems/9pfs/dfs_9pfs.c(+58/-4)

请提供验证的bsp和config (provide the config and bsp)

  • BSP: bsp/simulator(x86-64,Linux,ASAN + -fsanitize=address)
  • .config: CONFIG_RT_USING_MEMHEAP=y(必需,RT_USING_DFS_9PFS 的 Kconfig 依赖它)、CONFIG_RT_USING_DFS_V1=y、CONFIG_RT_USING_DFS_9PFS=y、CONFIG_RT_USING_ADT=y、CONFIG_RT_USING_ADT_BITMAP=y(模拟器 BSP 的 9PFS 验证配置)

注:Linux 下 bsp/simulator 编译 9PFS 时,drivers/misc.h 会无条件 include <cpuport.h>,而 libcpu/sim/ 没有该头文件。本地验证时临时补了一个空的 libcpu/sim/posix/cpuport.h 绕过(这是仓库本身的缺口,建议单独提 PR 补 sim 版 cpuport.h,不在本 PR 范围内)。

验证方法:注册一个假的内存 9P 服务器(transport 回调),正常完成握手(Tversion/Tattach/Tstat/Topen/Tclunk),但对 Tread 返回 11 字节报文且 count 字段谎报约 2 GB 目录数据;接收缓冲区后方铺满伪造的 . 记录并用 ASAN poison 做守卫带。

  • 修复前(master ceef6e0,同一文件内容与 master 一致):evil9p_test 触发 AddressSanitizer: use-after-poison,读取位置位于 8 MB 堆区之内 8334515 字节处(get_rx_value16_of ← dfs_9pfs_getdents),进程 ABORT;
  • 修复后:恶意回复被拒绝(listing done (survived), errno=0,无 ASAN 报告,进程存活);正常目录解析不受影响(entry: hello.txt、entry: subdir)。

证据日志(附在 issue 评论中):asan-report-run9-clean.txt(修复前)、fixed_evil.log / fixed_normal.log(修复后)。补丁在最新 master 上 git apply --check 通过(dfs_9pfs.c 自补丁基点未变化)。

  • action: (本地 ASAN 构建验证;CI 编译由仓库 workflow 执行)

当前拉取/合并请求的状态 Intent for your PR

必须选择一项 Choose one (Mandatory):

  • 本拉取/合并请求是一个草稿版本 This PR is for a code-review and is intended to get feedback
  • 本拉取/合并请求是一个成熟版本 This PR is mature, and ready to be integrated into the repo

代码质量 Code Quality:

我在这个拉取/合并请求中已经考虑了 As part of this pull request, I've considered the following:

  • 已经仔细查看过代码改动的对比 Already check the difference between PR and old code
  • 代码风格正确,包括缩进空格,命名及其他风格 Style guide is adhered to, including spacing, naming and other styles
  • 没有垃圾代码,代码尽量精简,不包含#if 0代码,不包含已经被注释了的代码 All redundant code is removed and cleaned up
  • 所有变更均有原因及合理的,并且不会影响到其他软件组件代码或BSP All modifications are justified and not affect other components or BSP
  • 对难懂代码均提供对应的注释 I've commented appropriately where code is tricky
  • 代码是高质量的 Code in this PR is of high quality
  • 已经使用clang-format 源码格式化工具确保格式符合RT-Thread代码规范 This PR has been formatted with clang-format and complies with RT-Thread code specification
  • 如果是新增bsp, 已经添加ci检查到.github/ALL_BSP_COMPILE.json 详细请参考链接BSP自查

The 9PFS client trusted server-controlled length fields:

- dfs_9pfs_read() copied READ3resok.count bytes into the caller's buffer
  without comparing count with the requested length or the decoded
  data.data_len, and advanced the buffer by the request length instead of
  the copied byte count.
- dfs_9pfs_getdents() discarded the number of bytes actually received and
  then trusted the in-packet count plus every per-record stat_size and
  name length, so a crafted reply made the parser walk past the receive
  buffer (ASan use-after-poison, read 8.3 MB into an 8 MB heap region).

Pass the actually-received byte count to p9_transaction() and clamp every
wire-supplied length against it; reject replies shorter than the count
field.  In getdents, stop when the remaining bytes cannot hold the next
record, when a record overruns the received data, or when a name overruns
its record.

Verified on bsp/simulator (x86-64, ASan) with a fake 9P server replying
to Tread with an 11-byte packet whose count field claims ~2 GB: before
the fix the reader aborts inside get_rx_value16_of()/dfs_9pfs_getdents();
after the fix the reply is rejected and the process survives (no ASan
report, empty listing), while a well-formed listing still parses
correctly.

Refs: RT-Thread#11289
@github-actions

Copy link
Copy Markdown

👋 感谢您对 RT-Thread 的贡献!Thank you for your contribution to RT-Thread!

为确保代码符合 RT-Thread 的编码规范,请在你的仓库中执行以下步骤运行代码格式化工作流(如果格式化CI运行失败)。
To ensure your code complies with RT-Thread's coding style, please run the code formatting workflow by following the steps below (If the formatting of CI fails to run).


🛠 操作步骤 | Steps

  1. 前往 Actions 页面 | Go to the Actions page
    点击进入工作流 → | Click to open workflow →

  2. 点击 Run workflow | Click Run workflow

  • Use workflow from 保持默认分支(通常为 master)
    Keep the default branch (usually master) in Use workflow from
  • 在 branch 输入框填写 PR 分支 fix/9pfs-bounds
    Enter PR branch fix/9pfs-bounds in the branch field
  • 设置需排除的文件/目录(目录请以"/"结尾)
    Set files/directories to exclude (directories should end with "/")
  1. 等待工作流完成 | Wait for the workflow to complete
    格式化后的代码将作为独立提交推送至你的分支。
    The formatting changes will be pushed to your branch as a separate commit.

完成后,提交将自动更新至 fix/9pfs-bounds 分支,关联的 Pull Request 也会同步更新。
Once completed, commits will be pushed to the fix/9pfs-bounds branch automatically, and the related Pull Request will be updated.

如有问题欢迎联系我们,再次感谢您的贡献!💐
If you have any questions, feel free to reach out. Thanks again for your contribution!

@github-actions

Copy link
Copy Markdown

📌 Code Review Assignment

🏷️ Tag: components

Reviewers: @Maihuanyi

Changed Files (Click to expand)
  • components/dfs/dfs_v1/filesystems/9pfs/dfs_9pfs.c

📊 Current Review Status (Last Updated: 2026-10-22 19:36 CST)


📝 Review Instructions

  1. 维护者可以通过单击此处来刷新审查状态: 🔄 刷新状态
    Maintainers can refresh the review status by clicking here: 🔄 Refresh Status

  2. 确认审核通过后评论 LGTM/lgtm
    Comment LGTM/lgtm after confirming approval

  3. PR合并前需至少一位维护者确认
    PR must be confirmed by at least one maintainer before merging

ℹ️ 刷新CI状态操作需要具备仓库写入权限。
ℹ️ Refresh CI status operation requires repository Write permission.

@Yunlk

Yunlk commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

我在尝试独立复现并验证这个 PR,环境已经备好,但缺一份东西,希望能补一下。

环境已就绪

  • WSL2 Arch,gcc 16.2.1,/usr/lib/libasan.so.8.0.0 可用(用一个故意的 use-after-free 探针确认过 ASAN 能正常报错并给出调用栈)
  • �sp/simulator 在 Linux 下 ASAN 是默认开启的——
    tconfig.py 里:

python if sys.platform == 'win32': CROSS_TOOL='msvc' else: CROSS_TOOL='gcc' ASAN = True

一个配置上的坑,建议补进 PR 描述

PR 里给的配置清单是 CONFIG_RT_USING_DFS_V1 / CONFIG_RT_USING_DFS_9PFS / CONFIG_RT_USING_ADT / CONFIG_RT_USING_ADT_BITMAP。照着做的人会发现在 �sp/simulator 的 menuconfig 里根本搜不到 RT_USING_DFS_9PFS,原因是它的 Kconfig 有隐藏依赖:

config RT_USING_DFS_9PFS select RT_USING_ADT_BITMAP depends on RT_USING_MEMHEAP ← simulator 默认没有开

必须先打开 RT_USING_MEMHEAP,这个选项才会出现。我在这一步卡了一会儿,建议在 PR 描述的配置清单里补一条 CONFIG_RT_USING_MEMHEAP=y。

缺的是测试用例

PR 只改了 components/dfs/dfs_v1/filesystems/dfs_9pfs.c 一个文件;#11289 那条评论里有完整补丁、有 ASAN 报告、有根因分析,但没有测试代码。评论里提到的 �vil9p_test、�san-report-run9-clean.txt、 ixed_evil.log 在这些地方都找不到。

能不能把那个假 9P 服务器的用例源码提供一下(贴评论、gist、或直接放进 �sp/simulator/ 下的测试目录都行)?理由不只是为了我复现:

  1. 别人的复现会卡在那几个细节上——你说过接收缓冲区后方要铺伪造的 . 记录(stride 65536,避开
    x + 0x80000 的 tx header),并加 ASAN 毒化守卫带;否则越界读会在 8 MB 堆块内穿行,ASAN 抓不到。这些参数自己猜很难和你的基线对上。
  2. 这个漏洞目前没有任何自动化覆盖,用例进仓库后可以接进 CI,长期看比一次性验证更有价值。

另一条环境限制,也许影响 CI 方案

Windows 上的 MinGW 不带 libasan:

$ gcc -fsanitize=address -o t.exe t.c ld.exe: cannot find -lasan: No such file or directory $ gcc -print-file-name=libasan.a libasan ← 只回显文件名,表示路径不存在

也就是说这个用例在 Windows 贡献者机器上跑不了,只能 Linux/WSL。如果打算纳入 CI,跑在 Linux runner 上是前提。

拿到用例后我就做 before/after 双跑(master 与你的分支各一次),把两侧完整日志贴上来。如果维护者更希望先看代码评审、用例以后再补,也请说明。

@Yunlk

Yunlk commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

补一条刚才编译时发现的问题:照 PR 里给的 BSP + 配置,9PFS 在当前 master 上编译不过。

复现

�sp/simulator,按上一条评论的方式补齐 RT_USING_MEMHEAP + RT_USING_DFS_9PFS + RT_USING_ADT + RT_USING_ADT_BITMAP,scons -j8:

CC build/kernel/components/dfs/dfs_v1/filesystems/9pfs/dfs_9pfs.o In file included from .../9pfs/dfs_9pfs.c:20: .../components/drivers/include/drivers/misc.h:15:10: fatal error: cpuport.h: No such file or directory 15 | #include <cpuport.h> compilation terminated. scons: *** [build/kernel/components/dfs/dfs_v1/filesystems/9pfs/dfs_9pfs.o] Error 1

原因

c /* components/drivers/include/drivers/misc.h:14-15 */ #include <rtdef.h> #include <cpuport.h> /* 无条件包含 */

而 dfs_9pfs.c:20 又 include 了 <drivers/misc.h>。

但 sim 架构没有这个头文件。libcpu/sim/ 下的全部内容:

libcpu/sim/SConscript libcpu/sim/posix/cpu_port.c ← 注意是 cpu_port,且只有 .c libcpu/sim/posix/SConscript libcpu/sim/posix/startup.c libcpu/sim/win32/cpu_port.c libcpu/sim/win32/cpu_port.h ← 只有 win32 有,且文件名是 cpu_port.h 不是 cpuport.h libcpu/sim/win32/SConscript libcpu/sim/win32/startup_msvc.c

全树共 22 个 cpuport.h(libcpu/aarch64/common/include/、libcpu/arm/*、部分 BSP 里),没有任何一个属于 sim 架构。

所以 Linux 侧的 simulator 构建里,凡是 include <drivers/misc.h> 的源文件都会失败——9PFS 是其中之一。

想确认的事

  1. 你验证时是怎么解决这个的?是本地补了一个 cpuport.h(或者改过 include 路径),还是用了另一个 BSP?如果需要在树内补一个 sim 版的 cpuport.h,那就不是本 PR 的范围了,可能需要单独一条。
  2. 如果确实是本地临时补的,能否把这个改动也一并说明?否则别人照 PR 描述的步骤走,第一步就编译不过,自然跑不出你贴的那两份日志。

这条不影响我对补丁本身的判断(补丁逻辑我看过,收敛思路是对的),但它挡住了独立复现这条路。等这两点清楚了,加上上一条提到的用例,我就能把 before/after 双跑做出来。

@usualtreee

Copy link
Copy Markdown
Author

我在尝试独立复现并验证这个 PR,环境已经备好,但缺一份东西,希望能补一下。

环境已就绪

  • WSL2 Arch,gcc 16.2.1,/usr/lib/libasan.so.8.0.0 可用(用一个故意的 use-after-free 探针确认过 ASAN 能正常报错并给出调用栈)
  • �sp/simulator 在 Linux 下 ASAN 是默认开启的——
    tconfig.py 里:

python if sys.platform == 'win32': CROSS_TOOL='msvc' else: CROSS_TOOL='gcc' ASAN = True

一个配置上的坑,建议补进 PR 描述

PR 里给的配置清单是 CONFIG_RT_USING_DFS_V1 / CONFIG_RT_USING_DFS_9PFS / CONFIG_RT_USING_ADT / CONFIG_RT_USING_ADT_BITMAP。照着做的人会发现在 �sp/simulator 的 menuconfig 里根本搜不到 RT_USING_DFS_9PFS,原因是它的 Kconfig 有隐藏依赖:

config RT_USING_DFS_9PFS select RT_USING_ADT_BITMAP depends on RT_USING_MEMHEAP ← simulator 默认没有开

必须先打开 RT_USING_MEMHEAP,这个选项才会出现。我在这一步卡了一会儿,建议在 PR 描述的配置清单里补一条 CONFIG_RT_USING_MEMHEAP=y。

缺的是测试用例

PR 只改了 components/dfs/dfs_v1/filesystems/dfs_9pfs.c 一个文件;#11289 那条评论里有完整补丁、有 ASAN 报告、有根因分析,但没有测试代码。评论里提到的 �vil9p_test、�san-report-run9-clean.txt、 ixed_evil.log 在这些地方都找不到。

能不能把那个假 9P 服务器的用例源码提供一下(贴评论、gist、或直接放进 �sp/simulator/ 下的测试目录都行)?理由不只是为了我复现:

  1. 别人的复现会卡在那几个细节上——你说过接收缓冲区后方要铺伪造的 . 记录(stride 65536,避开
    x + 0x80000 的 tx header),并加 ASAN 毒化守卫带;否则越界读会在 8 MB 堆块内穿行,ASAN 抓不到。这些参数自己猜很难和你的基线对上。
  2. 这个漏洞目前没有任何自动化覆盖,用例进仓库后可以接进 CI,长期看比一次性验证更有价值。

另一条环境限制,也许影响 CI 方案

Windows 上的 MinGW 不带 libasan:

$ gcc -fsanitize=address -o t.exe t.c ld.exe: cannot find -lasan: No such file or directory $ gcc -print-file-name=libasan.a libasan ← 只回显文件名,表示路径不存在

也就是说这个用例在 Windows 贡献者机器上跑不了,只能 Linux/WSL。如果打算纳入 CI,跑在 Linux runner 上是前提。

拿到用例后我就做 before/after 双跑(master 与你的分支各一次),把两侧完整日志贴上来。如果维护者更希望先看代码评审、用例以后再补,也请说明。

感谢准备环境,两个问题都成立,逐条回复:

1. 配置缺 MEMHEAP — 你说得对。
我验证时用的是本地已有的 .config(里面恰好带了 MEMHEAP),所以没注意到这个隐藏依赖。RT_USING_DFS_9PFS select RT_USING_ADT_BITMAP 依赖 RT_USING_MEMHEAP,没有它 menuconfig 里确实看不到 9PFS。PR 描述已更新,补上 CONFIG_RT_USING_MEMHEAP=y。

2. 测试用例 — 见附件 test_9pfs_evil.c.txt。
下载后改回 test_9pfs_evil.c,放在 bsp/simulator/applications/ 下即可,scons 会把它编进去。要点和你列的一致:伪造 transport 回 Tread 时发 11 字节报文、count 谎报 ~2GB;接收缓冲区后方铺 stride 65536 的 . 记录(避开 tx header),末尾加 ASAN 守卫带。ASAN 只有 Linux 可用,Windows 跑不了,CI 需要 Linux runner。

3. 关于进 CI — 同意你的判断。
这个用例依赖 ASAN,进 CI 需要 Linux runner;如果维护者觉得合适,我可以把用例整理成正式 utest 再提,但那应该是单独的 PR,不混在本次修复里。
test_9pfs_evil.c.txt

@usualtreee

Copy link
Copy Markdown
Author

补一条刚才编译时发现的问题:照 PR 里给的 BSP + 配置,9PFS 在当前 master 上编译不过。

复现

�sp/simulator,按上一条评论的方式补齐 RT_USING_MEMHEAP + RT_USING_DFS_9PFS + RT_USING_ADT + RT_USING_ADT_BITMAP,scons -j8:

CC build/kernel/components/dfs/dfs_v1/filesystems/9pfs/dfs_9pfs.o In file included from .../9pfs/dfs_9pfs.c:20: .../components/drivers/include/drivers/misc.h:15:10: fatal error: cpuport.h: No such file or directory 15 | #include <cpuport.h> compilation terminated. scons: *** [build/kernel/components/dfs/dfs_v1/filesystems/9pfs/dfs_9pfs.o] Error 1

原因

c /* components/drivers/include/drivers/misc.h:14-15 */ #include <rtdef.h> #include <cpuport.h> /* 无条件包含 */

而 dfs_9pfs.c:20 又 include 了 <drivers/misc.h>。

但 sim 架构没有这个头文件。libcpu/sim/ 下的全部内容:

libcpu/sim/SConscript libcpu/sim/posix/cpu_port.c ← 注意是 cpu_port,且只有 .c libcpu/sim/posix/SConscript libcpu/sim/posix/startup.c libcpu/sim/win32/cpu_port.c libcpu/sim/win32/cpu_port.h ← 只有 win32 有,且文件名是 cpu_port.h 不是 cpuport.h libcpu/sim/win32/SConscript libcpu/sim/win32/startup_msvc.c

全树共 22 个 cpuport.h(libcpu/aarch64/common/include/、libcpu/arm/*、部分 BSP 里),没有任何一个属于 sim 架构。

所以 Linux 侧的 simulator 构建里,凡是 include <drivers/misc.h> 的源文件都会失败——9PFS 是其中之一。

想确认的事

  1. 你验证时是怎么解决这个的?是本地补了一个 cpuport.h(或者改过 include 路径),还是用了另一个 BSP?如果需要在树内补一个 sim 版的 cpuport.h,那就不是本 PR 的范围了,可能需要单独一条。
  2. 如果确实是本地临时补的,能否把这个改动也一并说明?否则别人照 PR 描述的步骤走,第一步就编译不过,自然跑不出你贴的那两份日志。

这条不影响我对补丁本身的判断(补丁逻辑我看过,收敛思路是对的),但它挡住了独立复现这条路。等这两点清楚了,加上上一条提到的用例,我就能把 before/after 双跑做出来。

确认,这是我本地的一份临时绕行,不是 PR 的一部分:

解决方式:在本地加了 libcpu/sim/posix/cpuport.h(空头文件,只有 include guard + 注释),没有改 include 路径,也没有换 BSP。

你的判断是对的:drivers/misc.h 无条件 include <cpuport.h>,而 libcpu/sim/ 没有这个头文件(Linux 下任何 include drivers/misc.h 的源文件都会挂,9PFS 只是其中之一)。这是仓库本身的一个缺口。

关于范围:同意你的看法,给 sim 补一个 cpuport.h 应该是单独的一个 PR,不放进本次修复。PR 描述里我会补上"Linux 验证需临时补 libcpu/sim/posix/cpuport.h"的说明。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants