* [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®