Skip to content

[smart][lwp] validate IPC object ownership in syscalls - #12015

Open
cms19859230182-lang wants to merge 1 commit into
RT-Thread:masterfrom
cms19859230182-lang:fix/lwp-ipc-object-ownership
Open

cms19859230182-lang wants to merge 1 commit into
RT-Thread:masterfrom
cms19859230182-lang:fix/lwp-ipc-object-ownership

Conversation

@cms19859230182-lang

@cms19859230182-lang cms19859230182-lang commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

中文

问题

Smart LWP 的 IPC syscall 曾直接把用户句柄当内核对象指针使用;timer 参数封送、回调信号量生命周期、delete 唤醒和共享句柄关闭也存在后续审稿指出的边界问题。

改动

  • 句柄必须登记在调用 LWP 的对象 AVL 中且类型匹配,syscall 期间持引用;timer control 参数经用户拷贝封送,并拒绝函数地址命令。
  • timer create 与 SET_PARM 只接受本 LWP 的 semaphore,回调数据持有引用并与参数替换、销毁串行化;SET_TIME 拒绝 tick >= RT_TICK_MAX / 2。
  • mq 接收把删除唤醒产生的正错误转换为负返回值,不再将其当作拷贝长度。
  • 分开记录 LWP 所有者与在途引用;仅最后所有者关闭对象并唤醒等待者。closing 状态与阻塞入队检查使用同一把 IPC 锁。
  • 静态和动态对象创建路径均清零 Smart 对象记账字段。

验证

静态逐项核对本轮 P1、P2 与 SET_TIME 意见;clang-format 22.1.8 检查改动区域,git diff --check 通过。未跑板。

剩余约束

现有强杀和 defunct 清理没有逐 syscall 引用归还点,也没有逐线程引用表,因此线程若在 syscall 返回前被销毁,其在途引用仍可能残留。该约束已写入 lwp_user_object_get() 注释,本次未引入新的跟踪框架;正常 delete 唤醒路径会返回并释放引用。

English

Problem

Smart LWP IPC syscalls once treated user handles as raw kernel pointers. Follow-up review also found timer argument marshalling, callback semaphore lifetime, delete wakeup, and shared-handle close edge cases.

Changes

  • Require a handle in the calling LWP object AVL with a matching class and an in-flight reference; marshal timer control arguments and reject function-address commands.
  • Accept only a semaphore owned by the calling LWP for timer create and SET_PARM; hold its reference in serialized callback data until replacement or destruction. Reject SET_TIME when tick >= RT_TICK_MAX / 2.
  • Convert positive delete-wakeup errors to negative mq receive results so they cannot become copy lengths.
  • Track LWP owners separately from in-flight references. Only the final owner closes the object and wakes waiters; closing and the blocking-queue check use the same IPC lock.
  • Initialize Smart object bookkeeping in both static and dynamic creation paths.

Test plan

Statically checked each P1, P2, and SET_TIME review item; checked changed regions with clang-format 22.1.8 and ran git diff --check. No board run.

Remaining constraint

The current force-kill and defunct cleanup paths have no per-syscall release hook or per-thread reference table. A thread destroyed before returning from a syscall can therefore still leave its in-flight reference behind. This is documented at lwp_user_object_get(); no new tracking framework is introduced. The normal delete-wakeup path returns and releases its reference.

Fixes #11960

@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

📌 Code Review Assignment

🏷️ Tag: components

Reviewers: @Maihuanyi

Changed Files (Click to expand)
  • components/lwp/lwp_pid.c
  • components/lwp/lwp_pid.h
  • components/lwp/syscall/syscall_rt_ipc.c

🏷️ Tag: components_lwp

Reviewers: @xu18838022837

Changed Files (Click to expand)
  • components/lwp/lwp_pid.c
  • components/lwp/lwp_pid.h
  • components/lwp/syscall/syscall_rt_ipc.c

🏷️ Tag: kernel

Reviewers: @GorrayLi @ReviewSun @hamburger-os @lianux-mm @wdfk-prog @xu18838022837

Changed Files (Click to expand)
  • src/ipc.c
  • src/object.c
  • src/thread.c

📊 Current Review Status (Last Updated: 2026-11-14 15:40 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

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/lwp-ipc-object-ownership
    Enter PR branch fix/lwp-ipc-object-ownership 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/lwp-ipc-object-ownership 分支,关联的 Pull Request 也会同步更新。
Once completed, commits will be pushed to the fix/lwp-ipc-object-ownership 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 added RT-Smart RT-Thread Smart related PR or issues component: lwp Component labels Nov 10, 2026
@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

MemBrowse Memory Report · Explore what changed →

esp32-c3

  • drom0_0_seg: .eh_frame +64 B (+0.0%, 725,972 B / 8,388,576 B, total: 9% used)

hpmicro-hpm5100evk

  • ILM: .fast +32 B (+0.1%, 42,704 B / 262,144 B, total: 16% used)
  • XPI0: .fast +32 B (+0.0%, 103,012 B / 1,048,576 B, total: 10% used)

hpmicro-hpm5301evklite

  • ILM: .fast +16 B (+0.1%, 23,744 B / 131,072 B, total: 18% used)
  • XPI0: .fast +16 B (+0.0%, 76,804 B / 1,048,576 B, total: 7% used)

k230

  • SRAM: .bss +384 B, .data +48 B, .eh_frame +880 B, .stack +16 B, .text +4,144 B (+0.4%, 1,396,912 B / 60,682,240 B, total: 2% used)

nordic-nrf51822

  • FLASH: .text -16 B (-0.0%, 52,116 B / 262,144 B, total: 20% used)

nuvoton-m487

  • CODE: .text -8 B (-0.0%, 356,904 B / 524,288 B, total: 68% used)

raspberry-pico-rp2040

  • FLASH: .text -24 B (-0.0%, 114,248 B / 2,097,152 B, total: 5% used)

simulator

  • Code: .text +13 B (+0.0%, 2,676,667 B)

wch-ch32v208w-r0

  • FLASH: .text +16 B (+0.0%, 159,992 B / 491,520 B, total: 33% used)

No memory changes detected for:


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

@cms19859230182-lang
cms19859230182-lang marked this pull request as draft November 10, 2026 15:40

@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.

我按 handle 校验、引用计数、删除路径三部分过了一遍,整体方向是对的:把用户传入的裸指针收束为"必须在本 LWP 的对象 AVL 中、类型匹配、调用期间持有引用",补上了 #11960 缺的那一层。delete 路径复用 lwp_user_object_delete(),AVL 查找只比较 key、不解引用,伪造指针进不到 rt_*_delete,这部分没有问题。以下两点建议在标记 ready 前确认。

1. timer 的 arg 仍然是裸用户指针,同类问题没有闭环

sys_rt_timer_control() 现在校验了 timer,但 arg 还是原样传给 rt_timer_control()。后者在 RT_TIMER_CTRL_GET_TIME 时执行 *(rt_tick_t *)arg = timer->init_tick,GET_STATE 也会写回,SET_TIME 则读取 *(rt_tick_t *)arg。用户可以把 arg 指向任意内核地址,效果和 #11960 里的原语同级。

既然 PR 声称覆盖了 timer 家族,建议一并收掉:按 cmd 用 lwp_get_from_user() / lwp_put_to_user() 拷贝参数,或者先做 cmd 白名单,只放行不需要指针的命令。

2. delete 与阻塞等待的语义变化、以及引用可能收不回来

引入 in-flight 引用后,sem_delete / mutex_delete 不再调用 rt_*_delete(),也就不会再唤醒挂在该对象上的等待者。PR 描述里已经写明了这个 trade-off,但对 RT_WAITING_FOREVER 的等待者来说,结果是永久挂起,而以前会以 -RT_ERROR 返回。

更需要留意的是异常路径:线程阻塞在 rt_sem_take_interruptible() 时被 sys_thread_delete() 强杀,或进程退出时线程直接消失,syscall 都回不到 lwp_user_object_put(),这份引用就永远收不回来,内核对象随之泄漏。建议确认这种情况下有兜底(例如 delete 时把对象标记为 dying 并唤醒等待者,或在 defunct 线程清理时归还引用);如果确认这是可接受的约束,至少写进注释里。

小建议(不阻塞)

  • lwp_user_object_get() 里的 found 可以省掉,命中时直接 return object,否则返回 RT_NULL,读起来更直接。
  • 12 个 syscall 的 get/call/put 模板重复度偏高。C 里显式展开没问题,如果想去重,可以抽一个很小的宏;不建议为此引入更重的封装。

Overall the approach is sound: a user handle must now resolve to an object in the calling LWP's AVL, match the expected class, and stay referenced for the duration of the syscall. That closes the gap in #11960. Delete paths are fine as-is, since lwp_user_object_delete() only compares AVL keys and a forged pointer never reaches rt_*_delete().

1. sys_rt_timer_control() still passes arg through unchecked. rt_timer_control() writes *(rt_tick_t *)arg for GET_TIME / GET_STATE and reads it for SET_TIME, so a user can point arg at a kernel address and obtain the same arbitrary read/write primitive. Since the PR claims to cover the timer family, please either copy arg with lwp_get_from_user() / lwp_put_to_user() per command, or restrict the accepted commands to a pointer-free whitelist.

2. Delete vs. blocked waiters changed, and the reference can leak. With the in-flight reference, sem_delete / mutex_delete no longer reach rt_*_delete(), so waiters are no longer resumed (the description already notes this). For RT_WAITING_FOREVER waiters that means a permanent hang instead of -RT_ERROR. More importantly: if a thread is force-deleted through sys_thread_delete() while blocked in rt_sem_take_interruptible(), or the process exits, the syscall never returns and lwp_user_object_put() never runs — the in-flight reference, and with it the kernel object, leaks. Please confirm there is a fallback (e.g. mark the object dying on delete and wake waiters, or drop the reference from defunct-thread cleanup).

Minor, non-blocking: found in lwp_user_object_get() can be dropped; and the 12 get/call/put blocks could optionally be folded into a small helper macro.

@cms19859230182-lang

Copy link
Copy Markdown
Contributor Author

我按 handle 校验、引用计数、删除路径三部分过了一遍,整体方向是对的:把用户传入的裸指针收束为"必须在本 LWP 的对象 AVL 中、类型匹配、调用期间持有引用",补上了 #11960 缺的那一层。delete 路径复用 lwp_user_object_delete(),AVL 查找只比较 key、不解引用,伪造指针进不到 rt_*_delete,这部分没有问题。以下两点建议在标记 ready 前确认。

1. timer 的 arg 仍然是裸用户指针,同类问题没有闭环

sys_rt_timer_control() 现在校验了 timer,但 arg 还是原样传给 rt_timer_control()。后者在 RT_TIMER_CTRL_GET_TIME 时执行 *(rt_tick_t *)arg = timer->init_tick,GET_STATE 也会写回,SET_TIME 则读取 *(rt_tick_t *)arg。用户可以把 arg 指向任意内核地址,效果和 #11960 里的原语同级。

既然 PR 声称覆盖了 timer 家族,建议一并收掉:按 cmd 用 lwp_get_from_user() / lwp_put_to_user() 拷贝参数,或者先做 cmd 白名单,只放行不需要指针的命令。

2. delete 与阻塞等待的语义变化、以及引用可能收不回来

引入 in-flight 引用后,sem_delete / mutex_delete 不再调用 rt_*_delete(),也就不会再唤醒挂在该对象上的等待者。PR 描述里已经写明了这个 trade-off,但对 RT_WAITING_FOREVER 的等待者来说,结果是永久挂起,而以前会以 -RT_ERROR 返回。

更需要留意的是异常路径:线程阻塞在 rt_sem_take_interruptible() 时被 sys_thread_delete() 强杀,或进程退出时线程直接消失,syscall 都回不到 lwp_user_object_put(),这份引用就永远收不回来,内核对象随之泄漏。建议确认这种情况下有兜底(例如 delete 时把对象标记为 dying 并唤醒等待者,或在 defunct 线程清理时归还引用);如果确认这是可接受的约束,至少写进注释里。

小建议(不阻塞)

  • lwp_user_object_get() 里的 found 可以省掉,命中时直接 return object,否则返回 RT_NULL,读起来更直接。
  • 12 个 syscall 的 get/call/put 模板重复度偏高。C 里显式展开没问题,如果想去重,可以抽一个很小的宏;不建议为此引入更重的封装。

Overall the approach is sound: a user handle must now resolve to an object in the calling LWP's AVL, match the expected class, and stay referenced for the duration of the syscall. That closes the gap in #11960. Delete paths are fine as-is, since lwp_user_object_delete() only compares AVL keys and a forged pointer never reaches rt_*_delete().

1. sys_rt_timer_control() still passes arg through unchecked. rt_timer_control() writes *(rt_tick_t *)arg for GET_TIME / GET_STATE and reads it for SET_TIME, so a user can point arg at a kernel address and obtain the same arbitrary read/write primitive. Since the PR claims to cover the timer family, please either copy arg with lwp_get_from_user() / lwp_put_to_user() per command, or restrict the accepted commands to a pointer-free whitelist.

2. Delete vs. blocked waiters changed, and the reference can leak. With the in-flight reference, sem_delete / mutex_delete no longer reach rt_*_delete(), so waiters are no longer resumed (the description already notes this). For RT_WAITING_FOREVER waiters that means a permanent hang instead of -RT_ERROR. More importantly: if a thread is force-deleted through sys_thread_delete() while blocked in rt_sem_take_interruptible(), or the process exits, the syscall never returns and lwp_user_object_put() never runs — the in-flight reference, and with it the kernel object, leaks. Please confirm there is a fallback (e.g. mark the object dying on delete and wake waiters, or drop the reference from defunct-thread cleanup).

Minor, non-blocking: found in lwp_user_object_get() can be dropped; and the 12 get/call/put blocks could optionally be folded into a small helper macro.

这两个问题是我设置draft的原因,我晚上回寝室发现指针有bug,所以就设置了

然后第二个问题我确实没有看出来,感谢指正🙏🙏🙏

@cms19859230182-lang
cms19859230182-lang marked this pull request as ready for review November 11, 2026 06:21
@cms19859230182-lang
cms19859230182-lang force-pushed the fix/lwp-ipc-object-ownership branch from 1c5a9c0 to 4c3811d Compare November 11, 2026 06:31
@cms19859230182-lang

Copy link
Copy Markdown
Contributor Author

@BernardXiong 两点已改,仍是一笔提交(4c3811d9)。

  1. timer arg:sys_rt_timer_control() 不再把用户指针传给 rt_timer_control()。按 cmd 用内核局部变量中转:SET_TIME 先 lwp_get_from_user() 拷入 tick,GET_TIME/GET_REMAIN_TIME/GET_STATE/GET_FUNC/GET_PARM 成功后 lwp_put_to_user() 拷回;SET_PARM 只传值不解引用,SET_ONESHOT/SET_PERIODIC 无参;SET_FUNC(会被当内核回调调用)和不认识的 cmd 直接 -EINVAL。

  2. delete 唤醒:lwp_user_object_delete() 在从本 LWP 摘节点前调用新增的 _object_wakeup_waiters(),按类型对 suspend_thread(mb/mq 连 suspend_sender_thread)做 rt_susp_list_resume_all(..., RT_ERROR),与 rt_sem_delete() 等原有行为一致。等待者拿到错误后正常从 syscall 返回并 lwp_user_object_put(),对象仍在最后一个引用消失时才销毁。

  3. 强杀时的引用:没有接点。现有 _thread_exit() / defunct 清理里没有 per-syscall 的引用回收位置,这次也没有新造一套 per-thread 生命周期记录。delete 唤醒覆盖了常见的阻塞等待路径,但线程在 syscall 里被强杀时那份 in-flight 引用仍收不回来,这条剩余风险已写进 lwp_user_object_get() 的注释。

@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.

复核 4c3811d9,之前提的两点都已落实,可以合并。

已确认的修复

  1. timer arg 已收口:sys_rt_timer_control() 不再把用户指针交给 rt_timer_control()。SET_TIME 经 lwp_get_from_user() 拷入,GET_TIME/GET_REMAIN_TIME/GET_STATE/GET_FUNC/GET_PARM 成功后 lwp_put_to_user() 拷回,SET_FUNC 与未知 cmd 直接 -EINVAL,SET_PARM 只传值不解引用;__exit 统一 lwp_user_object_put()。原先"用户可让内核读写任意地址"的原语已关闭,-EFAULT 路径也处理了。
  2. delete 唤醒:_object_wakeup_waiters() 只在 AVL 命中后调用,伪造句柄仍只作为 key 比较、不会解引用;按类型 resume suspend_thread(mb/mq 含 suspend_sender_thread)。顺序是先唤醒、再 _object_node_delete() 减引用,最后一个引用释放时才 _object_destroy(),因此不会出现"唤醒后对象立刻被销毁"的 UAF,等待者返回后 put() 也是安全的。

非阻塞观察

  • RT_TIMER_CTRL_GET_FUNC 仍会把 timer->timeout_func 的内核地址拷回用户态(src/timer.c:728),等于保留了一个信息泄露面,而用户态对它有合法用途的可能性很低。建议与 SET_FUNC 一样返回 -EINVAL;GET_PARM 返回的是 parameter 值,保持现状即可。
  • _object_wakeup_waiters() 与并发 rt_sem_take() 之间还有窄窗口:某线程已经 lwp_user_object_get() 拿到引用、但尚未挂入 suspend_thread 时,删除路径的唤醒会漏掉它,之后它会永久阻塞并占住引用。相比原实现(在已释放对象上挂起)是改善,但建议把这条与强杀泄漏一起写进 lwp_user_object_delete() 的注释,明确剩余约束。

Re-checked 4c3811d9: both requested changes are in place, no blockers.

1. timer arg is now marshalled. sys_rt_timer_control() copies SET_TIME in via lwp_get_from_user(), copies GET_* results back via lwp_put_to_user(), rejects SET_FUNC and unknown commands with -EINVAL, and passes SET_PARM by value. The arbitrary kernel read/write primitive is closed, and -EFAULT is handled. The reference is released on every exit path.

2. Waiters are resumed on delete. _object_wakeup_waiters() is only reached after the AVL lookup succeeds, so a forged handle is still never dereferenced. It resumes suspend_thread (plus suspend_sender_thread for mb/mq) per type. Waking happens before _object_node_delete() drops the node's reference, and destruction is deferred to the last reference, so there is no use-after-wake UAF.

Non-blocking: RT_TIMER_CTRL_GET_FUNC still copies a kernel text address (timer->timeout_func) back to userspace; consider rejecting it like SET_FUNC. Also, a concurrent rt_sem_take() that has already taken its reference but not yet suspended can slip past the wakeup and block forever — better than the old behaviour, but worth documenting next to the accepted force-kill leak.

@cms19859230182-lang
cms19859230182-lang force-pushed the fix/lwp-ipc-object-ownership branch from 4c3811d to c1228dd Compare November 11, 2026 15:11
@cms19859230182-lang

Copy link
Copy Markdown
Contributor Author

@BernardXiong 已按非阻塞建议更新至 c1228dd1:

  • sys_rt_timer_control() 中 RT_TIMER_CTRL_GET_FUNC 已与 SET_FUNC 一样直接返回 -EINVAL(GET_PARM 保持不变);
  • lwp_user_object_delete() 紧挨 _object_wakeup_waiters() 处已补充注释,写明强杀/进程退出无法回到 lwp_user_object_put() 导致的引用残留,以及已 get 尚未挂入 suspend_thread 时的并发唤醒漏失窗口。

请按新 tip 复看这两项非阻塞点。

@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.

复核 c1228dd1:相对已批准的 4c3811d9,增量只是让 GET_FUNC 与 SET_FUNC 一样返回 -EINVAL,并在 _object_wakeup_waiters() 旁补充强杀/进程退出引用残留的说明,符合上一轮建议。handle 归属/类型/引用校验、timer arg 封送、删除唤醒逻辑静态检查未发现阻塞问题。

非阻塞:RT_TIMER_CTRL_SET_TIME 把用户 tick 直接交给 rt_timer_control(),内核里有 RT_ASSERT((*(rt_tick_t *)arg) < RT_TICK_MAX / 2);开启断言时用户传超大值会触发内核 assert。建议 syscall 侧先做同样的范围检查并返回 -EINVAL。

case RT_TIMER_CTRL_SET_TIME:

CI:29 个失败全部停在 Install Tools / Install ToolChains / Install toolchain 等工具链下载步骤(网络故障),不是代码或测试失败;当前 head 的 CI 实际未跑起来,建议 rerun 后再合并。

@Rbb666 Rbb666 closed this Nov 12, 2026
@Rbb666 Rbb666 reopened this Nov 12, 2026

@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.

静态审查发现两处可触发的安全问题,以及删除和线程退出路径上的三处回归。建议合并前处理,具体场景见行内评论。

Comment thread components/lwp/syscall/syscall_rt_ipc.c Outdated
break;
case RT_TIMER_CTRL_SET_PARM:
/* arg is the parameter value itself, it is not dereferenced */
karg = arg;

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.

[P1] 校验定时器回调使用的信号量

SET_PARM 接受任意指针,sys_rt_timer_create() 也直接保存 data。到期后,timer_timeout_callback() 将它当作 rt_sem_t 传给 rt_sem_release()。用户可以用自己的定时器操作其他 LWP 的信号量,或传入无效指针触发内核故障。请在创建和更新参数时都校验归属和类型,并确保回调结束前信号量仍有有效引用。

Comment thread components/lwp/lwp_pid.c
rt_mq_t mq = (rt_mq_t)object;

level = rt_spin_lock_irqsave(&mq->spinlock);
rt_susp_list_resume_all(&mq->parent.suspend_thread, RT_ERROR);

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.

[P1] 避免把删除错误当作已接收的消息长度

此处写入的正值 RT_ERROR 经 _rt_mq_recv() 原样返回,sys_mq_recv() 则把正返回值用作拷贝长度。启用 libc 时 RT_ERROR 为 255,等待者传 size=1 且提供 255 字节可写的用户缓冲区,就可能从未填充的内核缓冲区拷出 255 字节。原有最终删除路径也有这个问题,新增唤醒还会在 fork() 后仅关闭一个共享句柄时触发。请在接收路径把唤醒错误转换成负返回值。rt_susp_list_resume_all() 会忽略负错误参数。

Comment thread components/lwp/lwp_pid.c Outdated
* in-flight reference.
* 2) Concurrent window: a caller that has called
* lwp_user_object_get() but has not yet queued itself on
* suspend_thread can miss this wakeup, then block forever and

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.

[P2] 防止删除与入队之间丢失唤醒

线程 A 在空信号量上调用 sem_take(RT_WAITING_FOREVER),取得引用后被抢占。线程 B 删除最后一个句柄,此时队列为空。A 恢复后才入队,此后可永久阻塞,且自身引用会阻止对象回收。请让入队检查和关闭状态转换在同一把 IPC 锁下同步。

Comment thread components/lwp/lwp_pid.c
if (lwp_avl_find((avl_key_t)object, lwp->object_root) &&
(rt_object_get_type(object) == type))
{
rt_atomic_add(&object->lwp_ref_count, 1);

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.

[P2] 在线程被强制删除时释放 syscall 引用

sys_thread_delete() 可以直接销毁阻塞在 sem_take() 的线程,syscall 尾部的 lwp_user_object_put() 不会执行。删除最后一个句柄后对象仍然无法回收,重复创建对象、阻塞线程再删除的过程会持续消耗内核堆。请让线程清理路径归还仍持有的 syscall 引用。

Comment thread components/lwp/lwp_pid.c Outdated
* suspend_thread can miss this wakeup, then block forever and
* pin that reference.
*/
_object_wakeup_waiters(object);

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.

[P2] 保留其他 LWP 对共享 IPC 的等待

fork() 会为子进程注册同一个 IPC 对象。子进程在继承的空信号量上等待时,父进程删除自己的句柄会在这里唤醒整个等待队列,导致子进程收到错误,尽管它的句柄仍然有效。请在最后一个所有者释放时才执行全队列唤醒。

@cms19859230182-lang
cms19859230182-lang force-pushed the fix/lwp-ipc-object-ownership branch from c1228dd to f44ffb3 Compare November 12, 2026 04:56
@github-actions github-actions Bot added the Kernel PR has src relate code label Nov 12, 2026
@cms19859230182-lang

Copy link
Copy Markdown
Contributor Author

@Rbb666 @BernardXiong 已按本轮意见更新,tip:f44ffb36。

  • [P1] timer sem:create / SET_PARM 均通过 lwp_user_object_get() 按 RT_Object_Class_Semaphore 校验本 LWP 归属和类型;回调数据持有一份 sem 引用,并与参数替换、timer 销毁串行化。
  • [P1] mq 长度:_rt_mq_recv() 将唤醒后的正错误转换为负返回值,sys_mq_recv() 不会再把它当拷贝长度。
  • SET_TIME:tick >= RT_TICK_MAX / 2 在 syscall 侧返回 -EINVAL。
  • [P2] 删除/入队窗口:最后所有者设置 closing 与唤醒使用 IPC 锁,take/send/recv 在同一把锁内先检查 closing,失败路径由 syscall 归还引用。
  • [P2] fork 共享:新增独立 owner 计数,仅最后所有者释放时关闭并全员唤醒。
  • [P2] 强杀引用:现有 _thread_exit / defunct 路径没有逐 syscall 清理挂钩,本次未新造逐线程引用表;剩余约束已写在 lwp_user_object_get() 注释中。正常 delete 唤醒会返回并 put()。

另已恢复 rtdef.h 的 RT-Thread 风格,静态/动态对象创建均清零新字段;clang-format 22.1.8 改动区域和 git diff --check 通过,未跑板。

@cms19859230182-lang

Copy link
Copy Markdown
Contributor Author

静态审查发现两处可触发的安全问题,以及删除和线程退出路径上的三处回归。建议合并前处理,具体场景见行内评论。

行,让我再看看

@Rbb666

Rbb666 commented Nov 12, 2026

Copy link
Copy Markdown
Member

静态审查发现两处可触发的安全问题,以及删除和线程退出路径上的三处回归。建议合并前处理,具体场景见行内评论。

行,让我再看看

cpp check可以先不管

@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.

复核 f44ffb3。MQ 正错误码被当作拷贝长度、非最后所有者关闭时提前唤醒其他 LWP 的问题已经修正。初次入队也增加了同锁下的 closing 检查。仍有定时器回调上下文释放竞态、成功唤醒后再次入队绕过 closing 检查,以及非 Smart BSP ROM 超限回归,见本轮行内评论。此前指出的强杀线程在途引用泄漏仍保留在当前实现中。此次验证包含源码调用路径和当前、基线 CI 日志对照。

Comment thread components/lwp/lwp_pid.c
ret = rt_timer_delete((rt_timer_t)object);
if (ret == RT_EOK)
{
lwp_timer_callback_data_free(callback_data);

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.

[P1] 释放回调上下文前需要同步已派发的回调

rt_timer_delete() 只在 timer-list 锁下移除定时器,没有等待已派发回调结束。_timer_check() 在 src/timer.c:534 释放该锁后才调用回调。SMP 下,回调可以已经拿到 parameter,但尚未执行 lwp_timer_callback() 中的 data->lock,另一 CPU 就在这里释放了上下文和其信号量引用。回调继续后会对已释放的 data->lock 加锁,并读取 data->sem。可被抢占的软定时器回调也有这个窗口。请让派发路径持有有效生命周期引用,并在回调退出后再释放上下文。

Comment thread src/ipc.c

level = rt_spin_lock_irqsave(&(mq->spinlock));

if (_ipc_object_is_closing(&mq->parent.parent))

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.

[P2] 每次准备入队前都需要检查 closing

这里仅在首次加锁时检查关闭状态。接收线程 A 被发送操作以 RT_EOK 唤醒后,线程 B 可以先取走消息,再关闭最后一个句柄。关闭操作看到 A 已离开等待队列,随后 A 才在第 3983 行重新加锁。队列此时为空,A 会直接进入下一轮并以 RT_WAITING_FOREVER 再次入队,永久阻塞并持有对象引用。Mailbox 收发和 MQ 等待发送也有相同结构。请在每次入队前、同一 IPC 锁下检查 closing,覆盖成功唤醒后的重试。

Comment thread src/ipc.c

#define GET_MESSAGEBYTE_ADDR(msg) ((struct rt_mq_message *)msg + 1)

static rt_bool_t _ipc_object_is_closing(rt_object_t object)

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.

[P2] 在预处理阶段移除非 Smart 构建的 closing 检查

关闭 RT_USING_SMART 时,此函数及新增调用仍会参与编译。hk32/hk32f030c8-mini 使用 -O0,恒假的函数调用和分支仍有代码开销。准确基线 611d7c07 的 devices.soft_i2c 配置使用 65,472 B ROM,本 PR 的相同配置使用 65,552 B,导致 64 KB ROM 超限 16 B。请将 Smart 专用检查在非 Smart 构建中编译掉,并确认该 BSP 构建恢复。全局 MQ 错误符号转换也有代码增量,80 B 总增量需要通过构建继续确认。

PR 失败日志 · 基线成功日志

@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.

CI 结果(head f44ffb36):两项代码侧失败,本轮不做完整 review。

  1. 代码格式:Scan code format and license 的 Check clang-format 失败,报错集中在 include/rtdef.h:307-316,即本 PR 新增的 lwp_ref_count / lwp_owner_count / lwp_flags 字段以及 #ifdef RT_USING_SMART 边界。请用仓库固定的 clang-format 版本重新格式化。
  2. BSP 链接失败:others_ft32_mm32_acm32 中 bsp/hk32/hk32f030c8-mini 链接报 .text will not fit in region ROM,ROM overflowed by 16 bytes(65552 B / 64 KB,100.02%)。这是该 BSP 已接近 ROM 上限,本 PR 在通用 kernel 路径增加的代码把它推过了边界;请确认能否精简,或由 BSP 维护者调整配置。

另外已确认上一轮的非阻塞建议已落实:sys_rt_timer_control() 在 SET_TIME 前增加了 tick >= RT_TICK_MAX / 2 的 -EINVAL 检查。CI 通过后再做完整复核。

Summary (EN): Two code-side CI failures: clang-format violations in the new include/rtdef.h fields, and a 16-byte ROM overflow when linking hk32/hk32f030c8-mini. The previous non-blocking SET_TIME range-check suggestion is now implemented. Full re-review will follow a green CI.

@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.

复核当前提交 f44ffb3,上一轮行内指出的阻塞问题仍存在,需要修改后再合并:

  • [P1] 定时器销毁未同步已派发回调,释放 callback_data 后回调仍可能访问 data->lock/data->sem。请建立派发到回调退出期间的有效生命周期保护,见原行内意见。
  • [P2] Mailbox/MQ 成功唤醒后的重试路径会绕过 closing 检查,可能在最后所有者关闭后再次永久入队。请在每次准备入队时持同一 IPC 锁检查,见原行内意见。
  • [P2] 强制删除阻塞线程仍不会归还在途对象引用,重复执行会持续泄漏内核堆;注释不能替代回收路径,见原行内意见。

此外,当前 CI 的格式检查和 hk32f030c8-mini 链接仍失败(ROM 超出 16 字节);请处理非 Smart 构建增量,再补充删除与重试、回调与销毁并发的回归验证。为避免重复讨论,本轮沿用已有行内位置。

@cms19859230182-lang
cms19859230182-lang force-pushed the fix/lwp-ipc-object-ownership branch from f44ffb3 to aa236a6 Compare November 13, 2026 11:27
@cms19859230182-lang

Copy link
Copy Markdown
Contributor Author

@Rbb666 @BernardXiong 已按意见改完,tip:。

  1. timer回调与释放通过全局注册表+引用计数严格同步;2) 入队前同锁复核closing且非Smart预处理编译掉,rtdef.h格式CI已过。
    未烧板/未开 VM;单提交已 force-with-lease。

@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.

结论:格式 CI 在当前 head 仍失败。 / Verdict: format CI still fails at this head.

Scan code format and license 在 aa236a60b3 失败,日志报告 include/rtdef.h 的枚举与结构体区域 clang-format 违规;diff 里也能看到 struct rt_object 被整段改成 8 空格缩进(rtdef.h:293-318),与仓库格式不一致。上一条评论说“rtdef.h 格式 CI 已过”,但该 head 的 CI 结果并非如此。

失败 job:https://github.com/RT-Thread/rt-thread/actions/runs/43417230407/job/131858086043

建议用仓库固定的 clang-format 22.1.8(tools/ci/requirements-format.txt)对本次改动的 6 个文件重跑,只提交格式差异,避免夹带整段缩进变化。

另外,Rbb666 在 f44ffb36 上提出的两条行内意见(timer 回调生命周期保护、成功唤醒后重试绕过 closing 检查)本轮没有做完整复核;格式 CI 修好后需要一起确认。

Format CI still fails on include/rtdef.h at this head, including an unintended whole-struct reindent. Please run the pinned clang-format 22.1.8 and re-submit only the real formatting delta.

Fix RT-Thread#11960

Resolve user-supplied IPC handles through the calling LWP's object AVL,
require the expected object class, and hold an in-flight reference until
the syscall returns. Marshal timer control arguments between user and
kernel memory, and reject callback-function commands and unknown commands.

Validate the semaphore used by timer creation and SET_PARM. Keep one
semaphore reference in the timer callback data until parameter replacement
or timer destruction, and serialize callback release with those paths.
Reject SET_TIME values greater than or equal to RT_TICK_MAX / 2 before the
kernel timer assertion can be reached.

Return negative message-queue receive errors after deletion wakeups so a
positive RT_ERROR is never treated as a received message length. Track LWP
owners separately from in-flight references, and close and wake waiters
only when the final owner releases an object. Set the closing flag under
the object's IPC lock and check it under the same lock before blocking,
closing the delete-versus-suspend window.

Initialize Smart object bookkeeping in both static and dynamic object
creation paths. The remaining force-kill limitation is documented at
lwp_user_object_get(): without per-thread syscall reference bookkeeping,
a thread destroyed before returning from its syscall cannot release that
reference. No new tracking framework is introduced for that case.
@cms19859230182-lang
cms19859230182-lang force-pushed the fix/lwp-ipc-object-ownership branch from aa236a6 to 8fc1380 Compare November 14, 2026 07:40
@cms19859230182-lang
cms19859230182-lang marked this pull request as draft November 14, 2026 09:22
@cms19859230182-lang

Copy link
Copy Markdown
Contributor Author

@BernardXiong @Rbb666 rtdef.h 已按仓库 clang-format 重整;timer 生命周期和 closing 重试、强删归还也补上了。

@cms19859230182-lang
cms19859230182-lang marked this pull request as ready for review November 14, 2026 10:01

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

Labels

component: lwp Component Kernel PR has src relate code RT-Smart RT-Thread Smart related PR or issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] RT-Smart mailbox: unchecked fake object enables arbitrary kernel read/write

3 participants