* [PATCH v4] nvme: bound FDP config log reads to the received buffer
@ 2026-10-13 10:28 ` Yehyeong Lee
2026-10-13 13:00 ` Kanchan Joshi
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Yehyeong Lee @ 2026-10-13 10:28 UTC (permalink / raw)
To: Keith Busch, Jens Axboe, Christoph Hellwig, Sagi Grimberg
Cc: Nitesh Shetty, Hannes Reinecke, Kanchan Joshi, gliuxen,
linux-nvme, linux-kernel, stable
nvme_query_fdp_granularity() parses the FDP Configurations log using
sizes the controller returns, without checking that they fit the buffer
it was given. The log size is only bounded from above before
kvmalloc(), so a size below the log header makes
le16_to_cpu(h->numfdpc) read past the allocation, and a size of zero
yields ZERO_SIZE_PTR and faults. Each configuration descriptor is then
dereferenced (dsze while walking, and nrg and runs on the selected one)
without checking that the whole descriptor lies within the buffer, so a
descriptor at or past the end is read out of bounds. The out-of-bounds
runs value is then exposed to userspace through
/sys/block/nvmeXnY/queue/write_stream_granularity.
A device-supplied dsze that is not a multiple of eight also leaves the
following descriptors unaligned, and the structure is not packed, so
reading nrg and runs from one can fault on architectures without
efficient unaligned access.
A malicious or buggy controller triggers this during namespace scan with a
short FDP Configurations log:
[ 1.104828] BUG: KASAN: slab-out-of-bounds in nvme_alloc_ns+0x39a6/0x4480
[ 1.104848] Read of size 8 at addr ffff888009fd8fe0 by task kworker/u8:3/52
[ 1.104852]
[ 1.104857] CPU: 1 UID: 0 PID: 52 Comm: kworker/u8:3 Not tainted 7.3.0-rc6-00063-g0c2669a9f4a1 #1 PREEMPT(lazy)
[ 1.104862] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org 04/01/2014
[ 1.104866] Workqueue: async async_run_entry_fn
[ 1.104873] Call Trace:
[ 1.104876] <TASK>
[ 1.104878] dump_stack_lvl+0x66/0xa0
[ 1.104886] print_report+0xd0/0x630
[ 1.104893] ? nvme_alloc_ns+0x39a6/0x4480
[ 1.104897] ? __virt_addr_valid+0x209/0x3f0
[ 1.104903] ? nvme_alloc_ns+0x39a6/0x4480
[ 1.104907] kasan_report+0xe4/0x120
[ 1.104913] ? nvme_alloc_ns+0x39a6/0x4480
[ 1.104919] nvme_alloc_ns+0x39a6/0x4480
[ 1.104926] ? __pfx_nvme_alloc_ns+0x10/0x10
[ 1.104931] ? check_prev_add+0xfd/0xe80
[ 1.104942] ? lock_acquire+0x18d/0x300
[ 1.104948] ? nvme_find_get_ns+0xbb/0x2e0
[ 1.104952] ? find_held_lock+0x2b/0x80
[ 1.104956] ? nvme_find_get_ns+0x221/0x2e0
[ 1.104962] ? nvme_find_get_ns+0x231/0x2e0
[ 1.104967] ? nvme_scan_ns+0x2e1/0x8d0
[ 1.104971] ? kfree+0x2e5/0x510
[ 1.104976] ? __lock_acquire+0x570/0x1b00
[ 1.104982] nvme_scan_ns+0x56a/0x8d0
[ 1.104987] ? __pfx_nvme_scan_ns+0x10/0x10
[ 1.104994] ? lockdep_hardirqs_on_prepare+0xdc/0x190
[ 1.104999] ? trace_hardirqs_on+0x18/0x160
[ 1.105005] ? kvm_clock_get_cycles+0x19/0x40
[ 1.105011] ? __pfx_nvme_scan_ns_async+0x10/0x10
[ 1.105015] async_run_entry_fn+0x8d/0x290
[ 1.105020] process_one_work+0x8ac/0x1b00
[ 1.105029] ? __pfx_process_one_work+0x10/0x10
[ 1.105034] ? lock_acquire+0x18d/0x300
[ 1.105040] ? lock_is_held_type+0x8f/0x100
[ 1.105046] ? __pfx_async_run_entry_fn+0x10/0x10
[ 1.105050] worker_thread+0x4ee/0xe70
[ 1.105056] ? lockdep_hardirqs_on_prepare+0xdc/0x190
[ 1.105061] ? trace_hardirqs_on+0x18/0x160
[ 1.105066] ? __pfx_worker_thread+0x10/0x10
[ 1.105071] ? __pfx_worker_thread+0x10/0x10
[ 1.105076] kthread+0x2ce/0x3b0
[ 1.105080] ? __pfx_kthread+0x10/0x10
[ 1.105085] ret_from_fork+0x52e/0x780
[ 1.105090] ? __pfx_ret_from_fork+0x10/0x10
[ 1.105094] ? __switch_to+0x572/0xde0
[ 1.105101] ? __pfx_kthread+0x10/0x10
[ 1.105105] ret_from_fork_asm+0x1a/0x30
[ 1.105114] </TASK>
[ 1.105116]
[ 1.105117] Allocated by task 52:
[ 1.105119] kasan_save_stack+0x33/0x60
[ 1.105123] kasan_save_track+0x14/0x30
[ 1.105126] __kasan_kmalloc+0x8f/0xa0
[ 1.105129] __kvmalloc_node_noprof+0x2e1/0x820
[ 1.105134] nvme_alloc_ns+0x1097/0x4480
[ 1.105138] nvme_scan_ns+0x56a/0x8d0
[ 1.105142] async_run_entry_fn+0x8d/0x290
[ 1.105145] process_one_work+0x8ac/0x1b00
[ 1.105148] worker_thread+0x4ee/0xe70
[ 1.105152] kthread+0x2ce/0x3b0
[ 1.105155] ret_from_fork+0x52e/0x780
[ 1.105159] ret_from_fork_asm+0x1a/0x30
[ 1.105163]
[ 1.105164] The buggy address belongs to the object at ffff888009fd8fc0
[ 1.105164] which belongs to the cache kmalloc-32 of size 32
[ 1.105167] The buggy address is located 8 bytes to the right of
[ 1.105167] allocated 24-byte region [ffff888009fd8fc0, ffff888009fd8fd8)
[ 1.105171]
[ 1.105172] The buggy address belongs to the physical page:
[ 1.105175] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x9fd8
[ 1.105179] flags: 0x100000000000000(node=0|zone=1)
[ 1.105183] page_type: f5(slab)
[ 1.105188] raw: 0100000000000000 ffff888008c41780 dead000000000100 dead000000000122
[ 1.105192] raw: 0000000000000000 0000000000400040 00000000f5000000 0000000000000000
[ 1.105194] page dumped because: kasan: bad access detected
[ 1.105195]
[ 1.105196] Memory state around the buggy address:
[ 1.105198] ffff888009fd8e80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 1.105201] ffff888009fd8f00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 1.105203] >ffff888009fd8f80: fc fc fc fc fc fc fc fc 00 00 00 fc fc fc fc fc
[ 1.105205] ^
[ 1.105207] ffff888009fd9000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fc
[ 1.105210] ffff888009fd9080: fc fc fc fc fc fc fc fc 00 00 00 00 00 00 00 00
[ 1.105212] ==================================================================
Reject a log smaller than its header plus one configuration descriptor,
zero the buffer so a short transfer leaves no uninitialized tail to parse,
require each descriptor's declared size to be at least a descriptor and to
leave room for the next one before walking it, and read the descriptor
fields with the unaligned accessors.
Fixes: 30b5f20bb2dda ("nvme: register fdp parameters with the block layer")
Cc: stable@vger.kernel.org
Suggested-by: Kanchan Joshi <joshi.k@samsung.com>
Signed-off-by: Yehyeong Lee <yhlee@isslab.korea.ac.kr>
---
v4: per Kanchan's review - require header plus one descriptor, fold the
per-descriptor bounds check into the walk (reject dsze < sizeof(desc),
covering the 1..63 values the old !dsze test allowed, and require the
next descriptor to fit).
v3: read the descriptor fields with get_unaligned_le() (strict-align
hardening; verified via codegen, not on strict-align HW).
v2: allocate the log buffer with kvzalloc() so a short Get Log Page
transfer leaves a zeroed, rejected tail.
v1: https://lore.kernel.org/linux-nvme/20261008132604.1168105-1-yhlee@isslab.korea.ac.kr/
drivers/nvme/host/core.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 9bcab3dc4c118..389ef1ae53f17 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -2272,13 +2272,18 @@ static int nvme_query_fdp_granularity(struct nvme_ctrl *ctrl,
}
size = le32_to_cpu(hdr.sze);
+ if (size < sizeof(*h) + sizeof(*desc)) {
+ dev_warn(ctrl->device, "FDP config size too small:%zu\n",
+ size);
+ return 0;
+ }
if (size > PAGE_SIZE * MAX_ORDER_NR_PAGES) {
dev_warn(ctrl->device, "FDP config size too large:%zu\n",
size);
return 0;
}
- h = kvmalloc(size, GFP_KERNEL);
+ h = kvzalloc(size, GFP_KERNEL);
if (!h)
return -ENOMEM;
@@ -2304,9 +2309,9 @@ static int nvme_query_fdp_granularity(struct nvme_ctrl *ctrl,
desc = log;
end = log + size - sizeof(*h);
for (i = 0; i < fdp_idx; i++) {
- u16 dsze = le16_to_cpu(desc->dsze);
+ u16 dsze = get_unaligned_le16(&desc->dsze);
- if (!dsze || log + dsze > end) {
+ if (dsze < sizeof(*desc) || log + dsze + sizeof(*desc) > end) {
dev_warn(ctrl->device,
"FDP invalid config descriptor at index %d\n", i);
ret = 0;
@@ -2316,13 +2321,13 @@ static int nvme_query_fdp_granularity(struct nvme_ctrl *ctrl,
desc = log;
}
- if (le32_to_cpu(desc->nrg) > 1) {
+ if (get_unaligned_le32(&desc->nrg) > 1) {
dev_warn(ctrl->device, "FDP NRG > 1 not supported\n");
ret = 0;
goto out;
}
- info->runs = le64_to_cpu(desc->runs);
+ info->runs = get_unaligned_le64(&desc->runs);
out:
kvfree(h);
return ret;
base-commit: 0c2669a9f4a1d607e7591ae50ccf3c432a0aff08
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v4] nvme: bound FDP config log reads to the received buffer
2026-10-13 10:28 ` [PATCH v4] nvme: bound FDP config log reads to the received buffer Yehyeong Lee
@ 2026-10-13 13:00 ` Kanchan Joshi
2026-10-13 14:20 ` Christoph Hellwig
2026-10-13 15:45 ` Keith Busch
2 siblings, 0 replies; 5+ messages in thread
From: Kanchan Joshi @ 2026-10-13 13:00 UTC (permalink / raw)
To: Yehyeong Lee, Keith Busch, Jens Axboe, Christoph Hellwig,
Sagi Grimberg
Cc: Nitesh Shetty, Hannes Reinecke, gliuxen, linux-nvme, linux-kernel,
stable
On 10/13/2026 3:58 PM, Yehyeong Lee wrote:
> A malicious or buggy controller triggers this during namespace scan with a
> short FDP Configurations log:
Reviewed-by: Kanchan Joshi <joshi.k@samsung.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4] nvme: bound FDP config log reads to the received buffer
2026-10-13 10:28 ` [PATCH v4] nvme: bound FDP config log reads to the received buffer Yehyeong Lee
2026-10-13 13:00 ` Kanchan Joshi
@ 2026-10-13 14:20 ` Christoph Hellwig
2026-10-13 15:52 ` Yehyeong Lee
2026-10-13 15:45 ` Keith Busch
2 siblings, 1 reply; 5+ messages in thread
From: Christoph Hellwig @ 2026-10-13 14:20 UTC (permalink / raw)
To: Yehyeong Lee
Cc: Keith Busch, Jens Axboe, Christoph Hellwig, Sagi Grimberg,
Nitesh Shetty, Hannes Reinecke, Kanchan Joshi, gliuxen,
linux-nvme, linux-kernel, stable
Maybe we should cut the long trace in the commit log, but the change
itself looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4] nvme: bound FDP config log reads to the received buffer
2026-10-13 10:28 ` [PATCH v4] nvme: bound FDP config log reads to the received buffer Yehyeong Lee
2026-10-13 13:00 ` Kanchan Joshi
2026-10-13 14:20 ` Christoph Hellwig
@ 2026-10-13 15:45 ` Keith Busch
2 siblings, 0 replies; 5+ messages in thread
From: Keith Busch @ 2026-10-13 15:45 UTC (permalink / raw)
To: Yehyeong Lee
Cc: Jens Axboe, Christoph Hellwig, Sagi Grimberg, Nitesh Shetty,
Hannes Reinecke, Kanchan Joshi, gliuxen, linux-nvme, linux-kernel,
stable
On Tue, Oct 13, 2026 at 07:28:12PM +0900, Yehyeong Lee wrote:
> nvme_query_fdp_granularity() parses the FDP Configurations log using
> sizes the controller returns, without checking that they fit the buffer
> it was given. The log size is only bounded from above before
> kvmalloc(), so a size below the log header makes
> le16_to_cpu(h->numfdpc) read past the allocation, and a size of zero
> yields ZERO_SIZE_PTR and faults. Each configuration descriptor is then
> dereferenced (dsze while walking, and nrg and runs on the selected one)
> without checking that the whole descriptor lies within the buffer, so a
> descriptor at or past the end is read out of bounds. The out-of-bounds
> runs value is then exposed to userspace through
> /sys/block/nvmeXnY/queue/write_stream_granularity.
>
> A device-supplied dsze that is not a multiple of eight also leaves the
> following descriptors unaligned, and the structure is not packed, so
> reading nrg and runs from one can fault on architectures without
> efficient unaligned access.
Thanks, applied to nvme-7.4.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4] nvme: bound FDP config log reads to the received buffer
2026-10-13 14:20 ` Christoph Hellwig
@ 2026-10-13 15:52 ` Yehyeong Lee
0 siblings, 0 replies; 5+ messages in thread
From: Yehyeong Lee @ 2026-10-13 15:52 UTC (permalink / raw)
To: Keith Busch, Jens Axboe, Christoph Hellwig, Sagi Grimberg
Cc: Nitesh Shetty, Hannes Reinecke, Kanchan Joshi, gliuxen,
linux-nvme, linux-kernel, stable, yhlee
Thanks, all. No objection to trimming the KASAN trace down to a few
frames in the commit log if a shorter one reads better -- please feel
free to cut it down.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-13 15:53 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <CGME20261013102825epcas5p38d2293e8e6c86a9142f6d24ba9fd371e@epcas5p3.samsung.com>
2026-10-13 10:28 ` [PATCH v4] nvme: bound FDP config log reads to the received buffer Yehyeong Lee
2026-10-13 13:00 ` Kanchan Joshi
2026-10-13 14:20 ` Christoph Hellwig
2026-10-13 15:52 ` Yehyeong Lee
2026-10-13 15:45 ` Keith Busch
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.