Skip to content

[dfs][9pfs] fix unaligned access in P9_OF_OPS_GET/PUT - #11876

Open
cms19859230182-lang wants to merge 1 commit into
RT-Thread:masterfrom
cms19859230182-lang:fix/9pfs-unaligned
Open

cms19859230182-lang wants to merge 1 commit into
RT-Thread:masterfrom
cms19859230182-lang:fix/9pfs-unaligned

Conversation

@cms19859230182-lang

Copy link
Copy Markdown
Contributor

问题

components/dfs/dfs_v1/filesystems/9pfs/dfs_9pfs.c 里 9P 报文的访问器把
rx_buffer / tx_buffer 里的字节指针直接强转成 rt_uintN_t * 再解引用:

#define P9_OF_OPS_GET(width, dir)                                  \
    rt_inline rt_uint##width##_t get_##dir##_##value##width##_of(  \
        struct p9_connection *conn, unsigned idx)                  \
    {                                                              \
        rt_uint##width##_t *vp = (void *)&conn->dir##_buffer[idx]; \
        return rt_le##width##_to_cpu(*vp);                         \
    }

9P 报文里各字段是紧凑排列的,64 位字段几乎总是 8 字节不对齐:

  • sizeof(struct p9_connection) == 292,conn 来自 rt_malloc()(8 字节对齐),
    所以 rx_buffer 恒为 ≡ 4 (mod 8);
  • P9_MSG_STAT_LEN == 42,&rx_buffer[42] 恒为 ≡ 6 (mod 8)。

ARMv7-A 上 GCC 把这种解引用编译成 LDRD,LDRD 要求 8 字节对齐,
于是任何应答 Rstat 的 9P 服务端都会在 dfs_9pfs_stat() / dfs_9pfs_open() 里触发 data abort。
put_value16/32/64() 以及 16/32 位访问器是同一个写法,属于同类问题。

修复

三个宏都改成「先通过 rt_memcpy 把字段拷到对齐的局部变量,再做字节序转换」:

#define P9_OF_OPS_GET(width, dir)                                   \
    rt_inline rt_uint##width##_t get_##dir##_##value##width##_of(   \
        struct p9_connection *conn, unsigned idx)                   \
    {                                                               \
        rt_uint##width##_t value;                                   \
                                                                    \
        rt_memcpy(&value, &conn->dir##_buffer[idx], sizeof(value)); \
                                                                    \
        return rt_le##width##_to_cpu(value);                        \
    }

写方向对称处理(先 rt_cpu_to_leN,再 rt_memcpy 写回)。另外把兜底宏
#define rt_le8_to_cpu / #define rt_cpu_to_le8(空展开)改成函数式
#define rt_le8_to_cpu(x) (x),否则 width == 8 的实例化会展开成 return ; / value = ;。

函数签名、调用点、代码风格都不变,diff 只有 30+/17-,一个文件。

验证

bsp/qemu-vexpress-a9(ARMv7-A,QEMU 10.0.13)+ lwIP over lan9118 + 一个最小 9P2000.u
TCP 服务端(应答完全合法),guest 里注册 9p transport、dfs_mount("/mnt", "9p", 0, tag),
然后 stat/open 一个文件。

修复前(stat("/mnt") 就崩,gdb 不用就能看出来):

data abort:Execption:
r00:0x600f6ee8 r01:0x0000002a r02:0x600f700c r03:0x600f7036
...
lr :0x60059424 pc :0x60057154
shutdown...

0x60057154  get_rx_value64_of  dfs_9pfs.c:84
0x60059424  dfs_9pfs_stat      dfs_9pfs.c:1113

6005714c:  e1c320d0  ldrd r2, [r3]      ; r03 = &rx_buffer[42], 42 % 8 == 6

修复后(同一个用例):

[9p] mounted on /mnt, sizeof(struct p9_connection)=292 bytes
[9p] stat(/mnt) ok: mode=40000 size=0 atime=1792204313
[9p] stat(/mnt/hello.txt) ok: mode=100000 size=1260 atime=1792204313
[9p] open(/mnt/hello.txt) fd=4 read 31 bytes: 9pfs-unaligned-repr

mode 是 S_IFDIR / S_IFREG,size 与服务端报告的 1260 字节一致(说明字节序也正确),
msh 仍可正常交互,不再有 data abort。

编译产物对比:修复前 ldrd r2, [r3](r3 = 报文缓冲地址),修复后 8 字节先 memcpy 到
8 字节对齐的栈槽,ldrd 只作用在栈上。

兼容性

  • 纯 C 改动,不引入新依赖,rt_memcpy() 在所有平台都有;
  • 与大端/小端无关,字节序转换位置和以前一致(只是作用在局部变量上);
  • 未改任何函数签名和调用点。

另外发现(本次 PR 不改,建议另开 issue)

  1. p9_walk_path_raw() 忘记 put_tx_value32_of(conn, P9_MSG_SIZE, size),
    Twalk 的长度字段带的是上一条消息的残值(实测第一次发出 00000000),
    标准服务端会直接丢包,导致「能 mount 但 stat/open 全部失败」。
  2. Rstat 解析的字段偏移整体偏 +2 字节(P9_MSG_STAT_SIZE == 9,而 9P2000.u 是 7),
    导致 mode/atime/len/uid/gid 读到错位数据。
  3. put_header() / put_bytes() / put_string() 里还有对 tx_buffer + 1、+ 3 的
    16 位非对齐写,实测在 ARMv7-A 上没崩,但同样不合规。

致谢

本次问题的复现、定位与修复由 Grok(AI 助手)参与完成,已在 commit 中标注
Co-authored-by: Grok <grok@x.ai>。

Fixes #11868

@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-unaligned
    Enter PR branch fix/9pfs-unaligned 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-unaligned 分支,关联的 Pull Request 也会同步更新。
Once completed, commits will be pushed to the fix/9pfs-unaligned 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

github-actions Bot commented Oct 17, 2026 •

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-17 14:09 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.

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

The 9P accessors cast a byte pointer inside rx_buffer/tx_buffer to a
rt_uintN_t * and dereference it.  The 9P messages are packed at arbitrary
offsets, so a 64 bit field is usually not 8 byte aligned: the length field
of an Rstat reply always starts at P9_MSG_STAT_LEN == 42, and with
sizeof(struct p9_connection) == 292 in an rt_malloc()ed block rx_buffer is
always 4 (mod 8), so &rx_buffer[42] is always 6 (mod 8).  On ARMv7-A GCC
emits LDRD/STRD for those accesses, which requires 8 byte alignment, so
every Rstat reply (and every Tread/Twrite request that carries a 64 bit
offset) takes a data abort in dfs_9pfs_stat()/dfs_9pfs_open() on 32 bit
ARM targets.

Copy the field through an aligned local variable with rt_memcpy() and only
then apply the byte order conversion, for both the get and the put side.
The rt_le8_to_cpu()/rt_cpu_to_le8() fallbacks also became function like
macros, they are used as such by the width == 8 instantiations.

Fixes RT-Thread#11868

Co-authored-by: Grok <grok@x.ai>
Signed-off-by: seteiro <cms19859230182@gmail.com>
@BernardXiong

Copy link
Copy Markdown
Member

9pfs 会用于什么场合啊 😬

@cms19859230182-lang

Copy link
Copy Markdown
Contributor Author

9pfs 会用于什么场合啊 😬

熊老师好呀~9pfs 就是让 QEMU 里跑的 RT-Thread 直接读写电脑上的文件夹,方便进行调试;然后我这个 PR 修的是 [#11868](#11868) 那个 32 位 ARM 上一用就崩的 bug。

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.

[Bug] 9PFS: unaligned 64-bit loads in Rstat parsing fault on ARMv7-A — any 9P server reply to Tstat causes a kernel data abort (remote DoS)

2 participants