mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Daehyeon Ko <4ncienth@gmail.com>
To: Ilya Dryomov <idryomov@gmail.com>
Cc: Alex Markuze <amarkuze@redhat.com>,
	Viacheslav Dubeyko <slava@dubeyko.com>,
	ceph-devel@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH] libceph: validate CRUSH bucket references before use
Date: Tue, 15 Sep 2026 08:58:17 +0900	[thread overview]
Message-ID: <20260914235817.2201175-1-4ncienth@gmail.com> (raw)

crush_decode() copies signed bucket item values directly from an OSDMap.
Nonnegative values identify devices and negative values identify buckets.
The firstn and indep mappers reject devices beyond max_devices, but index
map->buckets[-1-item] before validating a negative item's bucket index. A
malformed map can therefore cause an out-of-bounds pointer read and
dereference.

Commit 3cde4a830230 ("libceph: reject buckets with mismatched CRUSH ids")
validates an in-range bucket's own id. It does not validate the item which
selects the bucket-pointer slot.

Add a common negative-item lookup which calculates the index without signed
overflow and checks its range and pointer before use. Reuse the validated
pointer for type checks, descent and recursive choose.

An in-kernel KASAN module linked with the exact mapper reports 8-byte slab
out-of-bounds reads in firstn and indep on 3/3 fresh boots each. The fixed
mapper rejects both malformed modes in 3/3 boots without a KASAN report.
Valid firstn and indep controls remain unchanged.

Fixes: 5ecc0a0f8128 ("ceph: CRUSH mapping algorithm")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Daehyeon Ko <4ncienth@gmail.com>
---
 net/ceph/crush/mapper.c | 51 ++++++++++++++++++++++++++++-------------
 1 file changed, 35 insertions(+), 16 deletions(-)

diff --git a/net/ceph/crush/mapper.c b/net/ceph/crush/mapper.c
index 17b041779fb9..4815a1b243cf 100644
--- a/net/ceph/crush/mapper.c
+++ b/net/ceph/crush/mapper.c
@@ -406,6 +406,21 @@ static int crush_bucket_choose(const struct crush_bucket *in,
 	}
 }
 
+static const struct crush_bucket *
+crush_get_bucket(const struct crush_map *map, int item)
+{
+	__u32 pos;
+
+	if (item >= 0 || map->max_buckets <= 0)
+		return NULL;
+
+	pos = -1U - (__u32)item;
+	if (pos >= (__u32)map->max_buckets)
+		return NULL;
+
+	return map->buckets[pos];
+}
+
 /*
  * true if device is marked "out" (failed, fully offloaded)
  * of the cluster
@@ -476,6 +491,7 @@ static int crush_choose_firstn(const struct crush_map *map,
 	int i;
 	int item = 0;
 	int itemtype;
+	const struct crush_bucket *item_bucket;
 	int collide, reject;
 	int count = out_size;
 
@@ -520,28 +536,29 @@ static int crush_choose_firstn(const struct crush_map *map,
 						(choose_args ?
 						 &choose_args[-1-in->id] : NULL),
 						outpos);
-				if (item >= map->max_devices) {
+				item_bucket = crush_get_bucket(map, item);
+				if (item >= map->max_devices ||
+				    (item < 0 && !item_bucket)) {
 					dprintk("   bad item %d\n", item);
 					skip_rep = 1;
 					break;
 				}
 
 				/* desired type? */
-				if (item < 0)
-					itemtype = map->buckets[-1-item]->type;
+				if (item_bucket)
+					itemtype = item_bucket->type;
 				else
 					itemtype = 0;
 				dprintk("  item %d type %d\n", item, itemtype);
 
 				/* keep going? */
 				if (itemtype != type) {
-					if (item >= 0 ||
-					    (-1-item) >= map->max_buckets) {
+					if (!item_bucket) {
 						dprintk("   bad item type %d\n", type);
 						skip_rep = 1;
 						break;
 					}
-					in = map->buckets[-1-item];
+					in = item_bucket;
 					retry_bucket = 1;
 					continue;
 				}
@@ -556,7 +573,7 @@ static int crush_choose_firstn(const struct crush_map *map,
 
 				reject = 0;
 				if (!collide && recurse_to_leaf) {
-					if (item < 0) {
+					if (item_bucket) {
 						int sub_r;
 						if (vary_r)
 							sub_r = r >> (vary_r-1);
@@ -565,7 +582,7 @@ static int crush_choose_firstn(const struct crush_map *map,
 						if (crush_choose_firstn(
 							    map,
 							    work,
-							    map->buckets[-1-item],
+							    item_bucket,
 							    weight, weight_max,
 							    x, stable ? 1 : outpos+1, 0,
 							    out2, outpos, count,
@@ -664,6 +681,7 @@ static void crush_choose_indep(const struct crush_map *map,
 	int i;
 	int item = 0;
 	int itemtype;
+	const struct crush_bucket *item_bucket;
 	int collide;
 
 	dprintk("CHOOSE%s INDEP bucket %d x %d outpos %d numrep %d\n", recurse_to_leaf ? "_LEAF" : "",
@@ -730,7 +748,9 @@ static void crush_choose_indep(const struct crush_map *map,
 					(choose_args ?
 					 &choose_args[-1-in->id] : NULL),
 					outpos);
-				if (item >= map->max_devices) {
+				item_bucket = crush_get_bucket(map, item);
+				if (item >= map->max_devices ||
+				    (item < 0 && !item_bucket)) {
 					dprintk("   bad item %d\n", item);
 					out[rep] = CRUSH_ITEM_NONE;
 					if (out2)
@@ -740,16 +760,15 @@ static void crush_choose_indep(const struct crush_map *map,
 				}
 
 				/* desired type? */
-				if (item < 0)
-					itemtype = map->buckets[-1-item]->type;
+				if (item_bucket)
+					itemtype = item_bucket->type;
 				else
 					itemtype = 0;
 				dprintk("  item %d type %d\n", item, itemtype);
 
 				/* keep going? */
 				if (itemtype != type) {
-					if (item >= 0 ||
-					    (-1-item) >= map->max_buckets) {
+					if (!item_bucket) {
 						dprintk("   bad item type %d\n", type);
 						out[rep] = CRUSH_ITEM_NONE;
 						if (out2)
@@ -758,7 +777,7 @@ static void crush_choose_indep(const struct crush_map *map,
 						left--;
 						break;
 					}
-					in = map->buckets[-1-item];
+					in = item_bucket;
 					continue;
 				}
 
@@ -774,11 +793,11 @@ static void crush_choose_indep(const struct crush_map *map,
 					break;
 
 				if (recurse_to_leaf) {
-					if (item < 0) {
+					if (item_bucket) {
 						crush_choose_indep(
 							map,
 							work,
-							map->buckets[-1-item],
+							item_bucket,
 							weight, weight_max,
 							x, 1, numrep, 0,
 							out2, rep,

base-commit: 142040037eba7ddd2de6f13067f33585451b8efe
-- 
2.55.0


                 reply	other threads:[~2026-09-14 23:58 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

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=20260914235817.2201175-1-4ncienth@gmail.com \
    --to=4ncienth@gmail.com \
    --cc=amarkuze@redhat.com \
    --cc=ceph-devel@vger.kernel.org \
    --cc=idryomov@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=slava@dubeyko.com \
    /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

all inboxes | Powered by JetHome®