[PATCH] nvmet-tcp: add bounds checks in nvmet_tcp_build_pdu_iovec

yunje shin yjshin0438 at gmail.com
Mon Feb 2 21:20:17 PST 2026


Hi everyone,

I have additional crash logs from testing on Kernel 6.19-rc8 that show
the same vulnerable code path being hit. The crash is consistently
reproducible by sending malformed PDUs.

Test environment:

Hardware/Hypervisor: QEMU (i440FX + PIIX) on Ubuntu 24.04
System Info (uname -a): Linux (none) 6.19.0-rc8 #2 SMP PREEMPT_DYNAMIC
Tue Feb 3 13:37:27 KST 2026 x86_64 GNU/Linux
Config: SMP, PREEMPT(voluntary), SLUB_DEBUG enabled
Crash log (6.19-rc8 - General Protection Fault via Controlled OOB Read):

[    7.626066] Oops: general protection fault, probably for
non-canonical address 0xdeadbeefdeadbef8: 0000 [#1] SMP NOPTI
[    7.626954] CPU: 0 UID: 0 PID: 42 Comm: kworker/0:1H Not tainted
6.19.0-rc8 #2 PREEMPT(voluntary)
[    7.627648] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX,
1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[    7.628389] Workqueue: nvmet_tcp_wq nvmet_tcp_io_work
[    7.628771] RIP: 0010:nvmet_tcp_build_pdu_iovec+0x61/0xc0
[    7.629160] Code: 48 03 47 30 85 f6 75 22 eb 74 48 8b 50 20 48 8d
48 20 48 89 d0 48 83 e0 fc 83 e2 01 48 0f 44 c1 49 83 c1 10 31 c9 85
f6 74 32 <8b> 50 0c 4c 8b 00 29 ca 39 f2 0f 47 d6 03 48 08 49 83 e0 fc
4d 89
[    7.630423] RSP: 0018:ffffc90000167cd8 EFLAGS: 00010202
[    7.630784] RAX: deadbeefdeadbeec RBX: ffff888100349800 RCX: 0000000000000000
[    7.631256] RDX: 0000000000000001 RSI: 0000000000000008 RDI: ffff888101740000
[    7.631739] RBP: ffff888101740000 R08: 0000000000000000 R09: ffff88810148b280
[    7.632221] R10: 0000000000000001 R11: 0000000000000008 R12: ffffc90000167d00
[    7.632710] R13: ffff888100349918 R14: ffffc90000167dd7 R15: ffff888100349810
[    7.633190] FS:  0000000000000000(0000) GS:ffff8881b84b1000(0000)
knlGS:0000000000000000
[    7.633779] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[    7.634191] CR2: 00000000004c6000 CR3: 0000000100cd6000 CR4: 00000000000006f0
[    7.634710] Call Trace:
[    7.634906]  <TASK>
[    7.635072]  nvmet_tcp_try_recv_pdu+0x576/0xa50
[    7.635401]  nvmet_tcp_io_work+0x69/0x720
[    7.635724]  process_one_work+0x15b/0x380
[    7.636016]  worker_thread+0x2a5/0x3c0
[    7.636634]  kthread+0xf6/0x1f0
[    7.637463]  ret_from_fork+0x131/0x190
[    7.638067]  ret_from_fork_asm+0x1a/0x30
[    7.638336]  </TASK>
Analysis: The crash occurs in nvmet_tcp_build_pdu_iovec() during an
out-of-bounds read of the scatterlist. In our controlled test, the RAX
register correctly reflects the injected pattern 0xdeadbeefdeadbeec,
and the kernel attempts to dereference 0xdeadbeefdeadbef8 (calculated
from our input), leading to a General Protection Fault.

This confirms that by sending a malformed PDU with a large data_length
or data_offset, an attacker can force the kernel to walk past the
allocated cmd->req.sg pointers and treat adjacent heap data as a
struct scatterlist.

Proposed Mitigation: The proposed patch adds critical bounds checks on
sg_idx and sg_remaining within nvmet_tcp_build_pdu_iovec(). These
checks ensure the loop terminates correctly even when provided with
inconsistent PDU metadata.

I would appreciate any feedback or suggestions on this patch.

Thanks, YunJe Shin

On Tue, Feb 3, 2026 at 12:47 PM yunje shin <yjshin0438 at gmail.com> wrote:
>
> Just a gentle ping on this patch.
>
> This fixes a crash/GPF observed in _copy_to_iter() when
> nvmet_tcp_build_pdu_iovec() walks past cmd->req.sg or uses invalid
> sg->length/offset, triggered by malformed PDU length/offset. The patch
> adds bounds checks on sg_idx/remaining entries and validates
> sg->length/offset before building the bvec.
>
> Also, the Reviewed-by tags in the original posting were obtained via
> private discussion; I’d appreciate an on-list re-review (or I can
> resend a v2 without those tags if preferred).
>
> Thanks,
> YunJe Shin
>
> On Fri, Jan 30, 2026 at 12:44 PM yunje shin <yjshin0438 at gmail.com> wrote:
> >
> > The Reviewed-by tags mentioned in the earlier thread were obtained
> > through private discussion, which kbusch likely wasn't aware of. Could
> > the maintainers please re-review this patch?
> >
> > On Fri, Jan 30, 2026 at 12:43 PM yunje shin <yjshin0438 at gmail.com> wrote:
> > >
> > > The Reviewed-by tags mentioned in the earlier thread were obtained
> > > through private discussion, which Keith likely wasn't aware of. Could
> > > the maintainers please re-review this patch?
> > >
> > > On Fri, Jan 30, 2026 at 12:39 PM yunje shin <yjshin0438 at gmail.com> wrote:
> > > >
> > > > [  104.528480] BUG: unable to handle page fault for address: ffff888002b42660
> > > > [  104.528773] #PF: supervisor write access in kernel mode
> > > > [  104.528938] #PF: error_code(0x0003) - permissions violation
> > > > [  104.529151] PGD 3a01067 P4D 3a01067 PUD 3a02067 PMD 4f24063 PTE
> > > > 8000000002b42121
> > > > [  104.529476] Oops: Oops: 0003 [#1] SMP NOPTI
> > > > [  104.529817] CPU: 0 UID: 0 PID: 63 Comm: kworker/0:1H Not tainted
> > > > 6.19.0-rc7 #1 PREEMPT(voluntary
> > > > [  104.530090] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX,
> > > > 1996), BIOS 1.16.3-debian-1.16.34
> > > > [  104.530440] Workqueue: nvmet_tcp_wq nvmet_tcp_io_work
> > > > [  104.530849] RIP: 0010:_copy_to_iter+0x27e/0x7a0
> > > > [  104.531077] Code: 01 48 2b 05 a4 2a 2f 01 48 c1 f8 06 48 c1 e0 0c
> > > > 48 03 05 a5 2a 2f 01 48 01 f0 7
> > > > [  104.531620] RSP: 0018:ffffc9000021bba8 EFLAGS: 00000246
> > > > [  104.531805] RAX: ffff888002b42660 RBX: 0000000000000008 RCX: 305c782f706d742f
> > > > [  104.532019] RDX: 0000000000000008 RSI: ffff88800515c078 RDI: 00000000000009a0
> > > > [  104.532243] RBP: ffff888005070218 R08: 0000000000000000 R09: ffff888004ed5300
> > > > [  104.532428] R10: 0000000000001000 R11: ffff888003e032e0 R12: ffff88800515c078
> > > > [  104.532615] R13: 0000000000000000 R14: 0000000000000008 R15: 0000000000004080
> > > > [  104.532821] FS:  0000000000000000(0000) GS:ffff8880fa4a4000(0000)
> > > > knlGS:0000000000000000
> > > > [  104.533048] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> > > > [  104.533222] CR2: ffff888002b42660 CR3: 0000000004fec000 CR4: 00000000000006f0
> > > > [  104.533483] Call Trace:
> > > > [  104.533957]  <TASK>
> > > > [  104.534195]  ? __pfx_simple_copy_to_iter+0x10/0x10
> > > > [  104.534381]  __skb_datagram_iter+0x1a7/0x2e0
> > > > [  104.534541]  ? __pfx_simple_copy_to_iter+0x10/0x10
> > > > [  104.534671]  skb_copy_datagram_iter+0x2f/0x90
> > > > [  104.534811]  tcp_recvmsg_locked+0x784/0x9d0
> > > > [  104.534936]  tcp_recvmsg+0x81/0x1e0
> > > > [  104.535045]  inet_recvmsg+0x50/0x130
> > > > [  104.535166]  ? security_socket_recvmsg+0x43/0x60
> > > > [  104.535294]  sock_recvmsg+0xa1/0xc0
> > > > [  104.535381]  nvmet_tcp_io_work+0xdb/0x720
> > > > [  104.535476]  process_one_work+0x15b/0x380
> > > > [  104.535570]  worker_thread+0x2a5/0x3c0
> > > > [  104.535653]  ? __pfx_worker_thread+0x10/0x10
> > > > [  104.535747]  kthread+0xf6/0x1f0
> > > > [  104.535839]  ? __pfx_kthread+0x10/0x10
> > > > [  104.535949]  ? __pfx_kthread+0x10/0x10
> > > > [  104.536060]  ret_from_fork+0x131/0x190
> > > > [  104.536182]  ? __pfx_kthread+0x10/0x10
> > > > [  104.536296]  ret_from_fork_asm+0x1a/0x30
> > > > [  104.536451]  </TASK>
> > > > [  104.536545] Modules linked in:
> > > > [  104.536764] CR2: ffff888002b42660
> > > > [  104.537046] ---[ end trace 0000000000000000 ]---
> > > > [  104.537291] RIP: 0010:_copy_to_iter+0x27e/0x7a0
> > > > [  104.537412] Code: 01 48 2b 05 a4 2a 2f 01 48 c1 f8 06 48 c1 e0 0c
> > > > 48 03 05 a5 2a 2f 01 48 01 f0 7
> > > > [  104.537848] RSP: 0018:ffffc9000021bba8 EFLAGS: 00000246
> > > > [  104.538005] RAX: ffff888002b42660 RBX: 0000000000000008 RCX: 305c782f706d742f
> > > > [  104.538229] RDX: 0000000000000008 RSI: ffff88800515c078 RDI: 00000000000009a0
> > > > [  104.538416] RBP: ffff888005070218 R08: 0000000000000000 R09: ffff888004ed5300
> > > > [  104.538617] R10: 0000000000001000 R11: ffff888003e032e0 R12: ffff88800515c078
> > > > [  104.538825] R13: 0000000000000000 R14: 0000000000000008 R15: 0000000000004080
> > > > [  104.538999] FS:  0000000000000000(0000) GS:ffff8880fa4a4000(0000)
> > > > knlGS:0000000000000000
> > > > [  104.539209] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> > > > [  104.539340] CR2: ffff888002b42660 CR3: 0000000004fec000 CR4: 00000000000006f0
> > > > [  104.539553] note: kworker/0:1H[63] exited with irqs disabled
> > > > [  104.540201] kworker/0:1H (63) used greatest stack depth: 12976 bytes left
> > > > ~ $ uname -a
> > > > Linux (none) 6.19.0-rc7 #1 SMP PREEMPT_DYNAMIC Fri Jan 30 09:36:44 KST
> > > > 2026 x86_64 GNU/Linux
> > > >
> > > > On Wed, Jan 28, 2026 at 9:41 AM YunJe Shin <yjshin0438 at gmail.com> wrote:
> > > > >
> > > > > nvmet_tcp_build_pdu_iovec() could walk past cmd->req.sg when a PDU
> > > > > length or offset exceeds sg_cnt and then use bogus sg->length/offset
> > > > > values, leading to _copy_to_iter() GPF/KASAN. Guard sg_idx, remaining
> > > > > entries, and sg->length/offset before building the bvec.
> > > > >
> > > > > Fixes: 872d26a391da ("nvmet-tcp: add NVMe over TCP target driver")
> > > > > Signed-off-by: YunJe Shin <ioerts at kookmin.ac.kr>
> > > > > Reviewed-by: Sagi Grimberg <sagi at grimberg.me>
> > > > > Reviewed-by: Joonkyo Jung <joonkyoj at yonsei.ac.kr>
> > > > > ---
> > > > >  drivers/nvme/target/tcp.c | 17 +++++++++++++++++
> > > > >  1 file changed, 17 insertions(+)
> > > > >
> > > > > diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
> > > > > index 15416ff0eac4..1a62b405d8e6 100644
> > > > > --- a/drivers/nvme/target/tcp.c
> > > > > +++ b/drivers/nvme/target/tcp.c
> > > > > @@ -349,11 +349,14 @@ static void nvmet_tcp_free_cmd_buffers(struct nvmet_tcp_cmd *cmd)
> > > > >         cmd->req.sg = NULL;
> > > > >  }
> > > > >
> > > > > +static void nvmet_tcp_fatal_error(struct nvmet_tcp_queue *queue);
> > > > > +
> > > > >  static void nvmet_tcp_build_pdu_iovec(struct nvmet_tcp_cmd *cmd)
> > > > >  {
> > > > >         struct bio_vec *iov = cmd->iov;
> > > > >         struct scatterlist *sg;
> > > > >         u32 length, offset, sg_offset;
> > > > > +       unsigned int sg_remaining;
> > > > >         int nr_pages;
> > > > >
> > > > >         length = cmd->pdu_len;
> > > > > @@ -361,9 +364,22 @@ static void nvmet_tcp_build_pdu_iovec(struct nvmet_tcp_cmd *cmd)
> > > > >         offset = cmd->rbytes_done;
> > > > >         cmd->sg_idx = offset / PAGE_SIZE;
> > > > >         sg_offset = offset % PAGE_SIZE;
> > > > > +       if (!cmd->req.sg_cnt || cmd->sg_idx >= cmd->req.sg_cnt) {
> > > > > +               nvmet_tcp_fatal_error(cmd->queue);
> > > > > +               return;
> > > > > +       }
> > > > >         sg = &cmd->req.sg[cmd->sg_idx];
> > > > > +       sg_remaining = cmd->req.sg_cnt - cmd->sg_idx;
> > > > >
> > > > >         while (length) {
> > > > > +               if (!sg_remaining) {
> > > > > +                       nvmet_tcp_fatal_error(cmd->queue);
> > > > > +                       return;
> > > > > +               }
> > > > > +               if (!sg->length || sg->length <= sg_offset) {
> > > > > +                       nvmet_tcp_fatal_error(cmd->queue);
> > > > > +                       return;
> > > > > +               }
> > > > >                 u32 iov_len = min_t(u32, length, sg->length - sg_offset);
> > > > >
> > > > >                 bvec_set_page(iov, sg_page(sg), iov_len,
> > > > > @@ -371,6 +387,7 @@ static void nvmet_tcp_build_pdu_iovec(struct nvmet_tcp_cmd *cmd)
> > > > >
> > > > >                 length -= iov_len;
> > > > >                 sg = sg_next(sg);
> > > > > +               sg_remaining--;
> > > > >                 iov++;
> > > > >                 sg_offset = 0;
> > > > >         }
> > > > > --
> > > > > 2.43.0
> > > > >



More information about the Linux-nvme mailing list