qla2x00t-32gbit: Reject exhausted bitmap tags before access

When every valid fallback tag is occupied, find_first_zero_bit returns
tag_num. The old loop calls test_and_set_bit before checking that
sentinel. If the bit was clear, it even exits the loop without checking
the range and uses tag_num as an index into tgt_cmd_map.

For word-aligned pool sizes this touches a word beyond the bitmap. For
other sizes it can claim padding in the last word. Both cases can return
an out-of-range command-map entry and let memset overwrite memory beyond
the map. Manual review found this bounds bug while auditing the fallback
allocation flagged by Coccinelle null/kmerr.cocci. No separate analyzer
diagnostic reported the invalid bitmap or command-map access.

Check the sentinel before test_and_set_bit and return -ENOENT when no tag
is available. Preserve the retry when another allocator wins the bit.
Expose the helper to the QLA KUnit suite in qla2x00tgt.ko.

Add KUnit cases for exhausted pools at word and non-word boundaries, an
empty pool, unchanged guard words and reuse of the final valid tag. A
spare guard word makes the old invalid access observable without needing
an actual out-of-bounds access in the test fixture.
This commit is contained in:
Gleb Chesnokov
2026-10-21 09:23:02 +03:00
parent 9671b117cb
commit 2d7183ce83
4 changed files with 55 additions and 10 deletions
+3
View File
@@ -76,6 +76,9 @@ dependency. Use a disposable VM without a passed-through QLogic HBA:
module initialization still runs. Fixtures stub target registration and
check resource unwinding and destruction; hardware session teardown
requires separate validation.
The fallback bitmap allocation and exhaustion cases also run on current kernels,
even though the normal driver uses sbitmap there. They check word boundaries,
allocation failure, a full or empty pool, and reuse of the last valid tag.
The runner reads suite metadata and dependencies from the modules and loads selected
owners in dependency order. Supply all required SCST modules from the same build;
@@ -183,10 +183,45 @@ static void qla_bitmap_allocation_failure_test(struct kunit *test)
KUNIT_EXPECT_PTR_EQ(test, f->bitmap, NULL);
}
static void qla_bitmap_exhaustion_test(struct kunit *test)
{
unsigned int tags = *(const unsigned int *)test->param_value;
unsigned int words = BITS_TO_LONGS(tags);
unsigned long *bitmap;
unsigned int i;
/* A zero guard word makes an out-of-range claim observable without an OOB. */
bitmap = kunit_kcalloc(test, words + 1, sizeof(*bitmap), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, bitmap);
for (i = 0; i < tags; i++)
KUNIT_EXPECT_EQ(test, sqa_get_tag_bitmap(bitmap, tags), (int)i);
KUNIT_EXPECT_EQ(test, sqa_get_tag_bitmap(bitmap, tags), -ENOENT);
KUNIT_EXPECT_EQ(test, bitmap[words], 0UL);
if (tags % BITS_PER_LONG)
KUNIT_EXPECT_EQ(test, bitmap[words - 1], BITMAP_LAST_WORD_MASK(tags));
/* The last valid tag remains reusable after the pool was exhausted. */
clear_bit(tags - 1, bitmap);
KUNIT_EXPECT_EQ(test, sqa_get_tag_bitmap(bitmap, tags), (int)tags - 1);
KUNIT_EXPECT_EQ(test, sqa_get_tag_bitmap(bitmap, tags), -ENOENT);
KUNIT_EXPECT_EQ(test, bitmap[words], 0UL);
}
static void qla_bitmap_empty_pool_test(struct kunit *test)
{
unsigned long guard = 0;
KUNIT_EXPECT_EQ(test, sqa_get_tag_bitmap(&guard, 0), -ENOENT);
KUNIT_EXPECT_EQ(test, guard, 0UL);
}
static struct kunit_case qla_pool_test_cases[] = {
KUNIT_CASE_PARAM(qla_pool_lifetime_test, qla_pool_gen_params),
KUNIT_CASE_PARAM(qla_bitmap_allocation_test, qla_bitmap_gen_params),
KUNIT_CASE(qla_bitmap_allocation_failure_test),
KUNIT_CASE_PARAM(qla_bitmap_exhaustion_test, qla_bitmap_gen_params),
KUNIT_CASE(qla_bitmap_empty_pool_test),
{}
};
+15 -10
View File
@@ -239,6 +239,20 @@ int sqa_init_tag_bitmap(unsigned long **bitmap, unsigned int tag_num)
return -ENOMEM;
return 0;
}
SCST_KUNIT_VISIBLE
int sqa_get_tag_bitmap(unsigned long *bitmap, unsigned int tag_num)
{
unsigned int tag;
for (;;) {
tag = find_first_zero_bit(bitmap, tag_num);
if (tag >= tag_num)
return -ENOENT;
if (!test_and_set_bit(tag, bitmap))
return tag;
}
}
#endif
SCST_KUNIT_VISIBLE
@@ -482,16 +496,7 @@ static struct qla_tgt_cmd *sqa_qla2xxx_get_cmd(struct fc_port *sess)
tag = sbitmap_queue_get(&sqa_tgt->tgt_tag_pool, &cpu);
#else
for (;;) {
tag = find_first_zero_bit(sqa_tgt->tgt_tag_pool,
sqa_tgt->tag_num);
if (test_and_set_bit(tag, sqa_tgt->tgt_tag_pool) == 0)
break;
if (tag >= sqa_tgt->tag_num) {
tag = -ENOENT;
break;
}
}
tag = sqa_get_tag_bitmap(sqa_tgt->tgt_tag_pool, sqa_tgt->tag_num);
#endif
if (tag < 0)
return NULL;
@@ -21,6 +21,8 @@ struct sqa_scst_tgt *sqa_alloc_tgt(struct scst_tgt_template *tmplate,
const char *name, unsigned int tag_num,
size_t tag_size);
void sqa_free_tgt(struct sqa_scst_tgt *sqa_tgt);
int sqa_get_tag_bitmap(unsigned long *bitmap, unsigned int tag_num);
#endif
#endif /* SCST_QLA2XXX_TEST_H */