mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] libceph: validate CRUSH bucket references before use
@ 2026-09-14 23:58 Daehyeon Ko
  0 siblings, 0 replies; only message in thread
From: Daehyeon Ko @ 2026-09-14 23:58 UTC (permalink / raw)
  To: Ilya Dryomov; +Cc: Alex Markuze, Viacheslav Dubeyko, ceph-devel, linux-kernel

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


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-09-14 23:58 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-14 23:58 [PATCH] libceph: validate CRUSH bucket references before use Daehyeon Ko

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®