fix(os): backport upstream DMA fixes for confidential guests - #1194
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
It changes guest-kernel DMA/SWIOTLB behavior without boot/exhaustion validation, so it needs human review and runtime testing before merge.
Pull request overview
This PR backports four upstream DMA/SWIOTLB fixes into the guest kernel build used for dstack confidential guests, addressing panics triggered by freeing dynamic SWIOTLB pools from atomic/RCU context and fixing atomic DMA pool bookkeeping under CONFIG_DMA_DIRECT_REMAP.
Changes:
- Add Yocto
SRC_URIentries for four upstream backport patches (0003–0006) covering dma-direct, dma pool freeing, and swiotlb dynamic pool handling. - Add the same patch set to the mkosi kernel build patch loop to keep both backends aligned.
- Extend the mkosi kernel component cache key to include the new patch files (preventing stale cached kernels).
File summaries
| File | Description |
|---|---|
| os/yocto/layers/meta-dstack/recipes-kernel/linux/linux-yocto%.bbappend | Wires in the four upstream backport patches via SRC_URI with detailed rationale/comments. |
| os/yocto/layers/meta-dstack/recipes-kernel/linux/files/0003-dma-direct-return-struct-page-from-alloc-from-pool.patch | Backport: restores dma_direct_alloc_from_pool() returning struct page * and threads CPU addr via out-param. |
| os/yocto/layers/meta-dstack/recipes-kernel/linux/files/0004-dma-pool-free-atomic-pool-pages-by-physical-address.patch | Backport: introduces page-based atomic-pool free helper keyed by physical address. |
| os/yocto/layers/meta-dstack/recipes-kernel/linux/files/0005-swiotlb-preserve-allocation-virtual-address.patch | Backport: preserves allocator-provided virtual address for dynamic swiotlb pools. |
| os/yocto/layers/meta-dstack/recipes-kernel/linux/files/0006-swiotlb-free-dynamic-pools-from-process-context.patch | Backport: switches dynamic pool teardown from RCU callback to queue_rcu_work() process context. |
| os/mkosi/components/kernel/kernel.sh | Updates mkosi kernel cache key to include the new patch files. |
| os/mkosi/components/kernel/kernel-build.sh | Applies the new patch set during mkosi kernel source preparation. |
Review details
- Files reviewed: 7/7 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A CVM built from current
nextpanics under bursty DMA:with
swiotlb_dyn_free→set_memory_encrypted→mutex/scheduleon the stack.#1192 (
CONFIG_SWIOTLB_DYNAMIC=y) is what makes this reachable —swiotlb_dyn_free()isonly compiled in under that symbol. But the defect it exposes is older and wider than
swiotlb, and it originates in our own
0001-x86-tdx-select-dma-direct-remap.patch.atomic_pool_expand()registers every atomic DMA pool chunk in the gen_pool under theaddress returned by
dma_common_contiguous_remap():With
CONFIG_DMA_DIRECT_REMAP=ythat is a vmap alias, not the direct-map address. Threecall sites reconstruct a direct-map address from a
struct pageand hand it to the poolrelease path, which cannot match the registration:
dma_direct_alloc_pages()dma_direct_alloc_from_pool()'svoid *asstruct page *dma_direct_free_pages()page_address(page)dma_free_from_pool()misses, so the page is re-encrypted and returned to the page allocator while the pool still owns itswiotlb_init_io_tlb_pool()phys_to_virt(start)stored inpool->vaddrswiotlb_free_tlb()misses, falls through toset_memory_encrypted(), which takesvmap_purge_lockfrom an RCU callback — the panic above0001is what puts x86 into this configuration; upstreamINTEL_TDX_GUESTdoes not selectDMA_DIRECT_REMAP. arm64 selects it unconditionally (arch/arm64/Kconfig), which is whythe upstream fixes come from Arm and are
Tested-bypKVM and Hyper-V.The first two rows do not involve
CONFIG_SWIOTLB_DYNAMICat all. They have been live inevery image built since
0001landed.Fix
Backport the four upstream commits that fix these, from the dma-mapping tree merged for
v7.3. None is in
linux-6.18.y, so a stable bump does not pick them up.000394a04ad732c9(Cc: stable)dma_direct_alloc_from_pool()returnsstruct page *again; CPU address moves to an out-parameter00048a9dc4a028e7dma_direct_free_pages()000557d29044d0f2pool->vaddrinstead of recomputing it0006e13d4d9a4915queue_rcu_work()Applied in upstream series order — these are patches 2, 5, 6 and 22 of a 32-patch series.
Only
0006needed rebasing: upstream context carriesstruct io_tlb_pool.cc_shared, addedlater in the same series by
d5fd03cdd6c0. The rebase is context-only; the hunks areotherwise identical to upstream.
CONFIG_SWIOTLB_DYNAMICis deliberately left=y. #1192's reasoning still holds — thisfixes the defect it exposed rather than hiding it again. The kernel command line is
untouched.
Wired into both backends exactly as
0001/0002already are:SRC_URIin the bbappend,the patch loop in
kernel-build.sh, and the cache key inkernel.sh— omitting the lastone would serve a stale kernel out of the component cache.
Verification
Build
Full kernel build through the repo's own script against 6.18.40, with the current
kernel.configin place (i.e.CONFIG_SWIOTLB_DYNAMIC=yfrom #1192 still on):All six patches applied at
--fuzz=0,check-kernel-config.shandcheck-lxc-kernel-config.shpassed,bzImageand modules built and installed.Resulting config:
swiotlb_dyn_free— the symbol on the panic stack — no longer exists inSystem.map;swiotlb_dyn_free_work(0006) anddma_free_from_pool_page(0004) do.Applied at
--fuzz=0against both backends' real source trees:component-work--fuzz=0work-shared/dstack/kernel-source--fuzz=0kernel/dma/swiotlb.c,include/linux/swiotlb.handkernel/dma/pool.care byte-identicalbetween 6.18.24 and 6.18.40, so one patch set covers both backends.
kernel/dma/direct.cdiffers by a single unrelated hunk (
break→fallthroughin the P2PDMA switch), not inany region these patches touch.
All four also apply under
git apply(strict, no fuzz), which is what Yocto'sdo_patchuses.
reuse lintandprek run --from-ref origin/next --to-ref HEADboth pass.A/B reproduction on real TDX hardware
Both defects need
force_dma_unencrypted()to be true, so neither is reachable outside areal TD — a plain guest cannot exercise them. Two kernels were built from the same script,
differing only in whether
0003-0006are applied (0001+0002kept in both, so thebaseline is exactly the kernel that ships today), and each was booted as a TD directly via
-kernelwith a minimal initramfs, isolating the kernel as the only variable.The baseline source confirms the
0003defect verbatim — astruct page *functionreturning the pool's CPU address:
Load was generated by a small out-of-tree module driving the two paths directly. Note that
CONFIG_DEBUG_FSis off inkernel.config, soio_tlb_transient_nslabsdoes not exist;the module walks
dev->dma_io_tlb_poolsand countspool->transientitself. Without thatcount a "no crash" result would prove nothing, because it would not show the dynamic path
was reached at all.
0001+0002only)0003-0006dma_alloc_pages(GFP_ATOMIC)/dma_free_pagesff505e40c0169000, a remap address whose PFN is invalidswiotlb=2048,1,force(4 MiB) with 256 KiB maps, then unmapWARNING ... smp.c:809withswiotlb_dyn_freeon the stacktransient_pools=6WARNING(it isWARN_ON_ONCE)BUG: scheduling while atomic, guest wedges and has to be killed at the 300 s timeoutBoth sides of row B allocated the same six transient pools before unmapping, so the two
runs did the same work; only the teardown differs.
The reproduced stack matches the path described in the report:
which is literally what
0006's commit message describes ("may allocate page tables or takesleeping locks").
Two details worth noting for anyone triaging this in the field:
WARN_ON_ONCE(!in_task()), so a production guest emits one warning andevery later transient-pool free takes the same path silently. The observed failure mode is
"the CVM disappeared", not "the log filled with warnings".
free (the
0004defect) would have exhausted it long before the end.Guest image and CVM boot
A full guest image was built from this branch with the mkosi backend and deployed as a real
TDX CVM through
dstack-vmm. All nine components built cold;kernelwas a cache miss,which exercises the
kernel.shcache-key hookup described above — had it been omitted, theimage would have shipped a stale cached kernel and every check in this PR would still pass.
The CVM reached
Boot Progress: done(app keys → data disk → gateway setup → docker →containers) with no kernel warnings on the console, and
/proc/kallsymsinside the runningguest confirms the shipped kernel carries the fix:
Not verified
static bounce pool is 256 MiB; boot plus one container comes nowhere near exhausting it,
so no transient pool is ever allocated and the fixed teardown path never runs. An
unpatched image boots identically. The fixed paths were exercised only in the directed
harness above, never inside the production image.
exercised end to end.
test-suites/full-stack-composeis the suite that would cover this.WARN_ON_ONCE; 1000 cycles on 16 vCPUs wedged the guest. The claim is "the unpatchedkernel enters the unsafe path under identical load and hangs under pressure", not "it
hangs every time".
0004was not isolated from0003. No third kernel with only0003applied wasbuilt, so the atomic-pool result covers the pair together.
BUG: scheduling while atomicfrom the same call path ratherthan a byte-identical copy of the originally reported panic text.
Note for reviewers
This changes the guest kernel and therefore the OS image hash and RTMRs. Deployments need
the new image registered in the KMS whitelist; it is not a drop-in for running CVMs.