[Bug 273626] Memory leak in ioctl nvme passthrough commands

From: <bugzilla-noreply_at_freebsd.org>
Date: Thu, 07 Sep 2023 21:36:02 UTC
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=273626

            Bug ID: 273626
           Summary: Memory leak in ioctl nvme passthrough commands
           Product: Base System
           Version: 13.2-STABLE
          Hardware: Any
                OS: Any
            Status: New
          Severity: Affects Some People
          Priority: ---
         Component: kern
          Assignee: bugs@FreeBSD.org
          Reporter: david.sloan@eideticom.com

When running nvme passthrough commands through the ioctl interface buffers are
never released from the kernel and increase the wired memory count permanently.
This will eventually lead to system lockup if enough buffers are sent through
the passthrough command interface. This can be replicated with C program at the
end of this bug report, which will leak 512 KB of memory per execution. We have
tested this on 13.2 and 15-current with the same result.

On inspection of sys/dev/nvme/nvme_ctrlr.c it appears that
nvme_ctrlr_passthrough_cmd() is missing a call to vunmapbuf() on exit when user
buffers are mapped. Applying the diff:

diff --git a/sys/dev/nvme/nvme_ctrlr.c b/sys/dev/nvme/nvme_ctrlr.c
index c4a75090174..608b738a1ff 100644
--- a/sys/dev/nvme/nvme_ctrlr.c
+++ b/sys/dev/nvme/nvme_ctrlr.c
@@ -1361,8 +1361,9 @@ nvme_ctrlr_passthrough_cmd(struct nvme_controller *ctrlr,
                mtx_sleep(pt, mtx, PRIBIO, "nvme_pt", 0);
        mtx_unlock(mtx);

-err:
        if (buf != NULL) {
+               vunmapbuf(buf);
+err:
                uma_zfree(pbuf_zone, buf);
                PRELE(curproc);
        }

Resolves the issue.

Example program to reproduce the issue:

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

#define SZ_512K (512 * 1024)

int nvme_read(int fd, void *data, size_t len, uint64_t pos) {
        // Assumes device with 512 byte lbas
        struct nvme_pt_command pt = {
                .cmd = {
                        .opc = NVME_OPC_READ,
                        // LBA
                        .cdw10 = (pos / 512) & 0xffffffff,
                        .cdw11 = (pos / 512) >> 32,
                        // nblocks
                        .cdw12 = len / 512 - 1,
                },
                .buf = data,
                .len = len,
                .is_read = 1,
        };
        return ioctl(fd, NVME_PASSTHROUGH_CMD, &pt);
}

int main(int argc, const char **argv) {
        void *buf;
        int rc;
        int fd;

        if (argc != 2) {
                fprintf(stderr, "Expected single nvme namespace argument\n");
                exit(1);
        }

        fd = open(argv[1], O_RDWR);
        if (fd == -1) {
                fprintf(stderr, "error opening device %s: %m\n", argv[1]);
                exit(1);
        }

        rc = posix_memalign(&buf, 4096, SZ_512K);
        if (rc) {
                errno = rc;
                fprintf(stderr, "error allocating buffer %m\n");
                exit(1);
        }

        rc = nvme_read(fd, buf, SZ_512K, 0);
        if (rc) {
                fprintf(stderr, "error reading from nvme device: %m\n");
                exit(1);
        }

        free(buf);
        close(fd);

        return 0;
}

-- 
You are receiving this mail because:
You are the assignee for the bug.