[Bug]: Linux guests sometimes hang at boot: RCU nesting underflow in rtR0MemObjLinuxVirtToPage (fix included)

未關閉
#940 1 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 6 天內回覆

還沒有人認領這個 Issue。

評估

難度
4/5
預估耗時
3-5 天
新手友好度
45/100
Issue 類型
缺陷
描述清晰度
描述清楚
活躍度
活躍
技術堆疊
c, linux

研究方向

Start in src/VBox/Runtime/r0drv/linux/memobj-r0drv-linux.c at rtR0MemObjLinuxVirtToPage(), where the issue identifies the PTE mapping and unmapping mismatch. Read the Linux version conditionals and compare the suggested fix with vmalloc_to_page() behavior. Done means the guest module no longer causes RCU nesting underflow or boot stalls on the described kernels; the issue gives a deterministic reproduction using vboxguest reload and repeated unshare -m true.

由索引模型根據 Issue 內容生成。

描述

bug
Version

7.2.22

Host OS Type

Windows

Host OS name + version

Windows / Windows 11 Build 26300 / x86

Host Architecture

x86

Guest OS Type

Linux

Guest Architecture

x86

Guest OS name + version

Ubuntu 24.04, kernel 6.17.0-40-generic, Guest Additions 7.2.6

Component

Guest Additions/Other

What happened?

Linux guests with kernel 6.6+ sometimes hang at boot with rcu_preempt detected expedited stalls. The root cause is the RCU mismatch reported in #359, and it very likely also explains #861 and #227. The code is unchanged in 7.2.22 and on main.

Cause: Since Linux 6.6, pte_unmap() calls rcu_read_unlock(). On 6.5+ and RHEL 9.4+, rtR0MemObjLinuxVirtToPage() reads the PTE with __pte_map(), which takes no RCU lock, and then calls pte_unmap(). That leaves the caller's RCU nesting at −1. vboxguest hits this at module init, when VGDrvCommonProcessOptionsFromHost makes an HGCM call with a buffer on the (vmalloc'ed) stack. Usually this only causes the warning from #227. If an expedited RCU IPI reaches the loading task first, rcu_exp_handler() never reports a quiescent state, and the boot hangs:

WARNING: CPU: 3 PID: 443 at kernel/rcu/tree_exp.h:799 rcu_exp_handler
rcu: INFO: rcu_preempt detected expedited stalls on CPUs/tasks: { 3-...D } ...

Fix: pv is a kernel address, so read the PTE with pte_offset_kernel() and skip pte_unmap(), as vmalloc_to_page() has done since 6.5. E.g.:

--- a/src/VBox/Runtime/r0drv/linux/memobj-r0drv-linux.c
+++ b/src/VBox/Runtime/r0drv/linux/memobj-r0drv-linux.c
@@ -1310,7 +1310,13 @@ RTDECL(struct page *) rtR0MemObjLinuxVirtToPage(void *pv)
 # endif
 
 # if RTLNX_VER_MIN(6,5,0) || RTLNX_RHEL_RANGE(9,4, 9,99)
-    pEntry = __pte_map(&u.Middle, ulAddr);
+    /* pv is a kernel address, so the page table is never in highmem and
+     * pte_offset_kernel() will do, like in vmalloc_to_page().  Don't pair
+     * __pte_map() with pte_unmap() here: since 6.6 (and in newer RHEL 9
+     * kernels) pte_unmap() does rcu_read_unlock() for the rcu_read_lock()
+     * taken by pte_offset_map().  __pte_map() doesn't take it, so that would
+     * leave the caller's RCU read-side nesting at -1. */
+    pEntry = pte_offset_kernel(&u.Middle, ulAddr);
 # elif RTLNX_VER_MIN(2,5,5) || defined(pte_offset_map) /* As usual, RHEL 3 had pte_offset_map earlier. */
     pEntry = pte_offset_map(&u.Middle, ulAddr);
 # else
@@ -1319,7 +1325,9 @@ RTDECL(struct page *) rtR0MemObjLinuxVirtToPage(void *pv)
     if (RT_UNLIKELY(!pEntry))
         return NULL;
     u.Entry = *pEntry;
-# if RTLNX_VER_MIN(2,5,5) || defined(pte_offset_map)
+# if RTLNX_VER_MIN(6,5,0) || RTLNX_RHEL_RANGE(9,4, 9,99)
+    /* pte_offset_kernel() mapped nothing, so there is nothing to unmap. */
+# elif RTLNX_VER_MIN(2,5,5) || defined(pte_offset_map)
     pte_unmap(pEntry);
 # endif
 
How can we reproduce this?

Guest kernel 6.6+ with CONFIG_PREEMPT_RCU=y and CONFIG_VMAP_STACK=y, e.g. Ubuntu 24.04 with the HWE kernel:

  • nm -u $(modinfo -n vboxguest) | grep rcu_read lists __rcu_read_unlock but not __rcu_read_lock.
  • Most boots log WARNING ... tree_plugin.h ... rcu_sched_clock_irq in (udev-worker) when vboxguest loads. Some boots hang as above.
  • Deterministic: unload vboxguest and load it again with finit_module() from a process that is pinned to one CPU and keeps busy-looping. Then run unshare -m true 60 times. Each mount-namespace teardown waits for an expedited grace period, and the loop stalls permanently (0 of 60 after 65 s; 0.09 s without the reload).
Did you upload all of your necessary log files, screenshots, etc.?
  • Yes, I've uploaded all pertinent files to this issue.
主要語言
C
星號
1.4k
分支
219
PR 合併指標
30 天內沒有已合併 PR

環境準備

  • 沒有 Dockerfile 或 Docker Compose 檔案
  • 沒有 Pull Request 範本
  • 閱讀貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

VirtualBox/virtualbox 的其他 Issue

查看 VirtualBox/virtualbox 的全部 Issue

相似的 Issue

更多 C Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。