public inbox for stable@vger.kernel.org
 help / color / mirror / Atom feed
From: Werner Kasselman <werner@verivus.ai>
To: Trond Myklebust <trondmy@kernel.org>, Anna Schumaker <anna@kernel.org>
Cc: Christoph Hellwig <hch@lst.de>,
	"linux-nfs@vger.kernel.org" <linux-nfs@vger.kernel.org>,
	"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>,
	"stable@vger.kernel.org" <stable@vger.kernel.org>,
	Werner Kasselman <werner@verivus.ai>
Subject: [PATCH 1/2] pnfs/blocklayout: validate volume indices and limit recursion depth
Date: Tue, 21 Apr 2026 10:03:42 +0000	[thread overview]
Message-ID: <20260421100338.1227152-2-werner@verivus.com> (raw)
In-Reply-To: <20260421100338.1227152-1-werner@verivus.com>

bl_parse_deviceid() trusts server-supplied volume indices from XDR
without bounds-checking and recurses with no depth limit.  A malicious
server can trigger an OOB heap read or overflow the kernel stack via
crafted GETDEVICEINFO responses.

Validate that every child volume index falls within the allocated
volumes array, reject nr_volumes == 0 before computing the entry-point
index, and cap recursion at PNFS_BLOCK_MAX_DEPTH (16).

Found by static analysis (sqry).

Fixes: 5c83746a0cf2 ("pnfs/blocklayout: in-kernel GETDEVICEINFO XDR parsing")
Cc: stable@vger.kernel.org # 3.17+
Signed-off-by: Werner Kasselman <werner@verivus.com>
---
 fs/nfs/blocklayout/blocklayout.h |  1 +
 fs/nfs/blocklayout/dev.c         | 54 +++++++++++++++++++++++---------
 2 files changed, 41 insertions(+), 14 deletions(-)

diff --git a/fs/nfs/blocklayout/blocklayout.h b/fs/nfs/blocklayout/blocklayout.h
index 6da40ca19570..ec8917cc335d 100644
--- a/fs/nfs/blocklayout/blocklayout.h
+++ b/fs/nfs/blocklayout/blocklayout.h
@@ -48,6 +48,7 @@ struct pnfs_block_dev;
 
 #define PNFS_BLOCK_MAX_UUIDS	4
 #define PNFS_BLOCK_MAX_DEVICES	64
+#define PNFS_BLOCK_MAX_DEPTH	16
 
 /*
  * Random upper cap for the uuid length to avoid unbounded allocation.
diff --git a/fs/nfs/blocklayout/dev.c b/fs/nfs/blocklayout/dev.c
index cc6327d97a91..d9b1af863535 100644
--- a/fs/nfs/blocklayout/dev.c
+++ b/fs/nfs/blocklayout/dev.c
@@ -287,7 +287,8 @@ static bool bl_map_stripe(struct pnfs_block_dev *dev, u64 offset,
 
 static int
 bl_parse_deviceid(struct nfs_server *server, struct pnfs_block_dev *d,
-		struct pnfs_block_volume *volumes, int idx, gfp_t gfp_mask);
+		struct pnfs_block_volume *volumes, int nr_volumes, int idx,
+		int depth, gfp_t gfp_mask);
 
 
 static int
@@ -439,12 +440,14 @@ bl_parse_scsi(struct nfs_server *server, struct pnfs_block_dev *d,
 
 static int
 bl_parse_slice(struct nfs_server *server, struct pnfs_block_dev *d,
-		struct pnfs_block_volume *volumes, int idx, gfp_t gfp_mask)
+		struct pnfs_block_volume *volumes, int nr_volumes, int idx,
+		int depth, gfp_t gfp_mask)
 {
 	struct pnfs_block_volume *v = &volumes[idx];
 	int ret;
 
-	ret = bl_parse_deviceid(server, d, volumes, v->slice.volume, gfp_mask);
+	ret = bl_parse_deviceid(server, d, volumes, nr_volumes,
+				v->slice.volume, depth + 1, gfp_mask);
 	if (ret)
 		return ret;
 
@@ -455,7 +458,8 @@ bl_parse_slice(struct nfs_server *server, struct pnfs_block_dev *d,
 
 static int
 bl_parse_concat(struct nfs_server *server, struct pnfs_block_dev *d,
-		struct pnfs_block_volume *volumes, int idx, gfp_t gfp_mask)
+		struct pnfs_block_volume *volumes, int nr_volumes, int idx,
+		int depth, gfp_t gfp_mask)
 {
 	struct pnfs_block_volume *v = &volumes[idx];
 	u64 len = 0;
@@ -467,8 +471,9 @@ bl_parse_concat(struct nfs_server *server, struct pnfs_block_dev *d,
 		return -ENOMEM;
 
 	for (i = 0; i < v->concat.volumes_count; i++) {
-		ret = bl_parse_deviceid(server, &d->children[i],
-				volumes, v->concat.volumes[i], gfp_mask);
+		ret = bl_parse_deviceid(server, &d->children[i], volumes,
+				nr_volumes, v->concat.volumes[i],
+				depth + 1, gfp_mask);
 		if (ret)
 			return ret;
 
@@ -484,7 +489,8 @@ bl_parse_concat(struct nfs_server *server, struct pnfs_block_dev *d,
 
 static int
 bl_parse_stripe(struct nfs_server *server, struct pnfs_block_dev *d,
-		struct pnfs_block_volume *volumes, int idx, gfp_t gfp_mask)
+		struct pnfs_block_volume *volumes, int nr_volumes, int idx,
+		int depth, gfp_t gfp_mask)
 {
 	struct pnfs_block_volume *v = &volumes[idx];
 	u64 len = 0;
@@ -496,8 +502,9 @@ bl_parse_stripe(struct nfs_server *server, struct pnfs_block_dev *d,
 		return -ENOMEM;
 
 	for (i = 0; i < v->stripe.volumes_count; i++) {
-		ret = bl_parse_deviceid(server, &d->children[i],
-				volumes, v->stripe.volumes[i], gfp_mask);
+		ret = bl_parse_deviceid(server, &d->children[i], volumes,
+				nr_volumes, v->stripe.volumes[i],
+				depth + 1, gfp_mask);
 		if (ret)
 			return ret;
 
@@ -513,19 +520,34 @@ bl_parse_stripe(struct nfs_server *server, struct pnfs_block_dev *d,
 
 static int
 bl_parse_deviceid(struct nfs_server *server, struct pnfs_block_dev *d,
-		struct pnfs_block_volume *volumes, int idx, gfp_t gfp_mask)
+		struct pnfs_block_volume *volumes, int nr_volumes, int idx,
+		int depth, gfp_t gfp_mask)
 {
+	if (idx < 0 || idx >= nr_volumes) {
+		dprintk("volume index %d out of range (0..%d)\n",
+			idx, nr_volumes - 1);
+		return -EIO;
+	}
+
+	if (depth >= PNFS_BLOCK_MAX_DEPTH) {
+		dprintk("volume nesting too deep (%d)\n", depth);
+		return -EIO;
+	}
+
 	d->type = volumes[idx].type;
 
 	switch (d->type) {
 	case PNFS_BLOCK_VOLUME_SIMPLE:
 		return bl_parse_simple(server, d, volumes, idx, gfp_mask);
 	case PNFS_BLOCK_VOLUME_SLICE:
-		return bl_parse_slice(server, d, volumes, idx, gfp_mask);
+		return bl_parse_slice(server, d, volumes, nr_volumes,
+				idx, depth, gfp_mask);
 	case PNFS_BLOCK_VOLUME_CONCAT:
-		return bl_parse_concat(server, d, volumes, idx, gfp_mask);
+		return bl_parse_concat(server, d, volumes, nr_volumes,
+				idx, depth, gfp_mask);
 	case PNFS_BLOCK_VOLUME_STRIPE:
-		return bl_parse_stripe(server, d, volumes, idx, gfp_mask);
+		return bl_parse_stripe(server, d, volumes, nr_volumes,
+				idx, depth, gfp_mask);
 	case PNFS_BLOCK_VOLUME_SCSI:
 		return bl_parse_scsi(server, d, volumes, idx, gfp_mask);
 	default:
@@ -559,6 +581,9 @@ bl_alloc_deviceid_node(struct nfs_server *server, struct pnfs_device *pdev,
 		goto out_free_scratch;
 	nr_volumes = be32_to_cpup(p++);
 
+	if (nr_volumes <= 0)
+		goto out_free_scratch;
+
 	volumes = kzalloc_objs(struct pnfs_block_volume, nr_volumes, gfp_mask);
 	if (!volumes)
 		goto out_free_scratch;
@@ -573,7 +598,8 @@ bl_alloc_deviceid_node(struct nfs_server *server, struct pnfs_device *pdev,
 	if (!top)
 		goto out_free_volumes;
 
-	ret = bl_parse_deviceid(server, top, volumes, nr_volumes - 1, gfp_mask);
+	ret = bl_parse_deviceid(server, top, volumes, nr_volumes,
+				nr_volumes - 1, 0, gfp_mask);
 
 	node = &top->node;
 	nfs4_init_deviceid_node(node, server, &pdev->dev_id);
-- 
2.43.0


  reply	other threads:[~2026-04-21 10:03 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-21 10:03 [PATCH 0/2] pnfs/blocklayout: harden GETDEVICEINFO volume parser Werner Kasselman
2026-04-21 10:03 ` Werner Kasselman [this message]
2026-04-21 10:03 ` [PATCH 2/2] pnfs/blocklayout: cap total parse operations in volume topology Werner Kasselman

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260421100338.1227152-2-werner@verivus.com \
    --to=werner@verivus.ai \
    --cc=anna@kernel.org \
    --cc=hch@lst.de \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=stable@vger.kernel.org \
    --cc=trondmy@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox