Skip to content

Free dirty_bitmap when unmapping DMA region to fix memory leak#847

Merged
jlevon merged 1 commit into
nutanix:masterfrom
Hooollin:fix-dma-unmap-free-dirty-bitmap
Apr 16, 2026
Merged

Free dirty_bitmap when unmapping DMA region to fix memory leak#847
jlevon merged 1 commit into
nutanix:masterfrom
Hooollin:fix-dma-unmap-free-dirty-bitmap

Conversation

@Hooollin

Copy link
Copy Markdown
Contributor

The dirty bitmap was allocated when dirty tracking is enabled for a DMA region, but was not released during region teardown, may lead to memory leaks over time.

Comment thread lib/dma.c Outdated
@jlevon

jlevon commented Apr 15, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the fix; please add a test - something like:

jlevon@sent:~/src/libvfio-user$ git diff test/py/test_dirty_pages.py 
diff --git a/test/py/test_dirty_pages.py b/test/py/test_dirty_pages.py
index 6abea1c..8fa35c6 100644
--- a/test/py/test_dirty_pages.py
+++ b/test/py/test_dirty_pages.py
@@ -412,4 +412,15 @@ def test_dirty_pages_uninitialised_dma():
 
     vfu_destroy_ctx(ctx)
 
+
+def test_dirty_pages_ctx_no_stop():
+    test_dirty_pages_setup()
+    vfu_setup_device_migration_callbacks(ctx)
+    start_logging()
+
+    test_dirty_pages_get_modified()
+
+    vfu_destroy_ctx(ctx)
+
+
 # ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab:

@Hooollin Hooollin force-pushed the fix-dma-unmap-free-dirty-bitmap branch from 708acab to 5c3e52c Compare April 16, 2026 09:37
Comment thread test/py/test_dirty_pages.py Outdated
@Hooollin Hooollin force-pushed the fix-dma-unmap-free-dirty-bitmap branch from 5c3e52c to 4a050d1 Compare April 16, 2026 12:07
@Hooollin

Copy link
Copy Markdown
Contributor Author
======================================================================================================================= FAILURES =======================================================================================================================
________________________________________________________________________________________________________________ test_shadow_ioeventfd _________________________________________________________________________________________________________________

    def test_shadow_ioeventfd():
        """Configure a shadow ioeventfd, have the client trigger it, confirm that
        the server receives the notification and can see the value."""
    
        # server setup
        ctx = vfu_create_ctx(flags=LIBVFIO_USER_FLAG_ATTACH_NB)
        assert ctx is not None
        ret = vfu_setup_region(ctx, index=VFU_PCI_DEV_BAR0_REGION_IDX, size=0x1000,
                               flags=VFU_REGION_FLAG_RW)
        assert ret == 0
        fo = tempfile.TemporaryFile(dir="/dev/shm")
        fo.truncate(0x1000)
    
        # FIXME
        # Use pip install eventfd?
        #   $ grep EFD_NONBLOCK -wr /usr/include/
        #   /usr/include/bits/eventfd.h:    EFD_NONBLOCK = 00004000
        EFD_NONBLOCK = 0o00004000
    
        efd = eventfd(flags=EFD_NONBLOCK)
        ret = vfu_create_ioeventfd(ctx, VFU_PCI_DEV_BAR0_REGION_IDX, efd, 0x8,
                                   0x16, 0, 0, fo.fileno(), 0x10)
>       assert ret == 0
E       assert -1 == 0

test_shadow_ioeventfd.py:86: AssertionError
----------------------------------------------------------------------------------------------------------------- Captured stdout call -----------------------------------------------------------------------------------------------------------------
DEBUG: shadow ioeventfd not compiled
=============================================================================================================== short test summary info ================================================================================================================
FAILED test_shadow_ioeventfd.py::test_shadow_ioeventfd - assert -1 == 0
============================================================================================================ 1 failed, 223 passed in 4.32s =============================================================================================================
==62607== 
==62607== HEAP SUMMARY:
==62607==     in use at exit: 732,459 bytes in 352 blocks
==62607==   total heap usage: 49,528 allocs, 49,176 frees, 179,602,701 bytes allocated
==62607== 
==62607== 15,166 (14,624 direct, 542 indirect) bytes in 1 blocks are definitely lost in loss record 91 of 96
==62607==    at 0x488C0AC: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-arm64-linux.so)
==62607==    by 0x7BAC6E7: vfu_create_ctx (libvfio-user.c:1905)
==62607==    by 0x7B16623: ??? (in /usr/lib/aarch64-linux-gnu/libffi.so.8.1.4)
==62607==    by 0x7B1383B: ??? (in /usr/lib/aarch64-linux-gnu/libffi.so.8.1.4)
==62607==    by 0x7AE2AB7: ??? (in /usr/lib/python3.12/lib-dynload/_ctypes.cpython-312-aarch64-linux-gnu.so)
==62607==    by 0x7ADF863: ??? (in /usr/lib/python3.12/lib-dynload/_ctypes.cpython-312-aarch64-linux-gnu.so)
==62607==    by 0x4C3F37: _PyObject_MakeTpCall (in /usr/bin/python3.12)
==62607==    by 0x564803: _PyEval_EvalFrameDefault (in /usr/bin/python3.12)
==62607==    by 0x4C5B03: _PyObject_Call_Prepend (in /usr/bin/python3.12)
==62607==    by 0x529DFF: ??? (in /usr/bin/python3.12)
==62607==    by 0x4C3FEF: _PyObject_MakeTpCall (in /usr/bin/python3.12)
==62607==    by 0x564803: _PyEval_EvalFrameDefault (in /usr/bin/python3.12)
==62607== 
==62607== LEAK SUMMARY:
==62607==    definitely lost: 14,624 bytes in 1 blocks
==62607==    indirectly lost: 542 bytes in 3 blocks
==62607==      possibly lost: 0 bytes in 0 blocks
==62607==    still reachable: 717,293 bytes in 348 blocks
==62607==         suppressed: 0 bytes in 0 blocks
==62607== Reachable blocks (those to which a pointer was found) are not shown.
==62607== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==62607== 
==62607== For lists of detected and suppressed errors, rerun with: -s
==62607== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

One test case failed, but it appears unrelated to my changes.

@Hooollin Hooollin force-pushed the fix-dma-unmap-free-dirty-bitmap branch from 4a050d1 to 0cded66 Compare April 16, 2026 12:53
@jlevon

jlevon commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator

you have some flake8 nits

@Hooollin Hooollin force-pushed the fix-dma-unmap-free-dirty-bitmap branch from 0cded66 to aa99282 Compare April 16, 2026 13:05
@Hooollin

Copy link
Copy Markdown
Contributor Author

you have some flake8 nits

Just notice that, fixed.

Comment thread lib/dma.c
Comment thread test/py/test_dirty_pages.py
@Hooollin Hooollin force-pushed the fix-dma-unmap-free-dirty-bitmap branch from aa99282 to 0d26421 Compare April 16, 2026 13:19
Comment thread test/py/test_dirty_pages.py
@jlevon

jlevon commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator

sorry, flake8 is annoying :)

…ix#852)

The dirty bitmap was allocated when dirty tracking is enabled for a DMA
region, but was not released during region teardown.

This could lead to memory leaks over time with repeated DMA map/unmap
operations while dirty page logging is active.

Signed-off-by: liuhaolin <hollinisme@gmail.com>
@Hooollin Hooollin force-pushed the fix-dma-unmap-free-dirty-bitmap branch from 0d26421 to c5f6aef Compare April 16, 2026 15:53
@Hooollin

Hooollin commented Apr 16, 2026

Copy link
Copy Markdown
Contributor Author

sorry, flake8 is annoying :)

It found another one, just fixed it.

@jlevon jlevon merged commit 4d9f663 into nutanix:master Apr 16, 2026
12 of 13 checks passed
@jlevon

jlevon commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator

thanks!

@Hooollin Hooollin deleted the fix-dma-unmap-free-dirty-bitmap branch April 17, 2026 03:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants