Skip to content

Commit 188aa5c

Browse files
committed
Fix race between critnib_release() and free_leaf() in critnib
Fix race between critnib_release() and free_leaf() in critnib: critnib_release() decremented ref_count to 0 and (before it called c->cb_free_leaf(k->to_be_freed)) free_leaf() added this leaf to the c->deleted_leaf list and alloc_leaf() reused it and zeroed k->to_be_freed before it could be freed in critnib_release(). This patch fixes this issue. Signed-off-by: Lukasz Dorau <[email protected]>
1 parent 89ef4cc commit 188aa5c

File tree

1 file changed

+56
-29
lines changed

1 file changed

+56
-29
lines changed

src/critnib/critnib.c

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@
8989
#define NIB ((1ULL << SLICE) - 1)
9090
#define SLNODES (1 << SLICE)
9191

92+
// it has to be >= 2
93+
#define LEAF_VALID (2ULL)
94+
9295
typedef uintptr_t word;
9396
typedef uint8_t sh_t;
9497

@@ -331,18 +334,26 @@ static void free_leaf(struct critnib *__restrict c,
331334
return;
332335
}
333336

337+
// k should be added to the c->deleted_leaf list here
338+
// or in critnib_release() when the reference count drops to 0.
339+
utils_atomic_store_release_u8(&k->pending_deleted_leaf, 1);
340+
334341
if (c->cb_free_leaf) {
335342
uint64_t ref_count;
336343
utils_atomic_load_acquire_u64(&k->ref_count, &ref_count);
337344
if (ref_count > 0) {
338-
// k will be added to c->deleted_leaf in critnib_release()
345+
// k will be added to the c->deleted_leaf list in critnib_release()
339346
// when the reference count drops to 0.
340-
utils_atomic_store_release_u8(&k->pending_deleted_leaf, 1);
341347
return;
342348
}
343349
}
344350

345-
add_to_deleted_leaf_list(c, k);
351+
uint8_t expected = 1;
352+
uint8_t desired = 0;
353+
if (utils_compare_exchange_u8(&k->pending_deleted_leaf, &expected,
354+
&desired)) {
355+
add_to_deleted_leaf_list(c, k);
356+
}
346357
}
347358

348359
/*
@@ -392,8 +403,8 @@ int critnib_insert(struct critnib *c, word key, void *value, int update) {
392403
utils_atomic_store_release_u8(&k->pending_deleted_leaf, 0);
393404

394405
if (c->cb_free_leaf) {
395-
// mark the leaf as valid (ref_count == 1)
396-
utils_atomic_store_release_u64(&k->ref_count, 1ULL);
406+
// mark the leaf as valid (ref_count == 2)
407+
utils_atomic_store_release_u64(&k->ref_count, LEAF_VALID);
397408
} else {
398409
// the reference counter is not used in this case
399410
utils_atomic_store_release_u64(&k->ref_count, 0ULL);
@@ -602,36 +613,52 @@ int critnib_release(struct critnib *c, void *ref) {
602613
struct critnib_leaf *k = (struct critnib_leaf *)ref;
603614

604615
uint64_t ref_count;
605-
utils_atomic_load_acquire_u64(&k->ref_count, &ref_count);
606-
607-
if (ref_count == 0) {
608-
return -1;
609-
}
610-
616+
uint64_t ref_desired;
611617
/* decrement the reference count */
612-
if (utils_atomic_decrement_u64(&k->ref_count) == 0) {
613-
void *to_be_freed = NULL;
614-
utils_atomic_load_acquire_ptr(&k->to_be_freed, &to_be_freed);
615-
if (to_be_freed) {
616-
utils_atomic_store_release_ptr(&k->to_be_freed, NULL);
617-
c->cb_free_leaf(c->leaf_allocator, to_be_freed);
618-
}
619-
uint8_t pending_deleted_leaf;
620-
utils_atomic_load_acquire_u8(&k->pending_deleted_leaf,
621-
&pending_deleted_leaf);
622-
if (pending_deleted_leaf) {
623-
utils_atomic_store_release_u8(&k->pending_deleted_leaf, 0);
624-
add_to_deleted_leaf_list(c, k);
618+
do {
619+
utils_atomic_load_acquire_u64(&k->ref_count, &ref_count);
620+
if (ref_count < LEAF_VALID) {
621+
#ifndef NDEBUG
622+
LOG_FATAL("critnib_release() was called too many times (ref_count "
623+
"= %llu)\n",
624+
(unsigned long long)ref_count);
625+
assert(ref_count >= LEAF_VALID);
626+
#endif
627+
return -1;
625628
}
629+
ref_desired = ref_count - 1;
630+
} while (
631+
!utils_compare_exchange_u64(&k->ref_count, &ref_count, &ref_desired));
632+
633+
if (ref_desired >= LEAF_VALID) {
634+
// ref_counter was decremented and it is still valid
635+
return 0;
626636
}
627637

638+
/* ref_counter == (LEAF_VALID - 1)) - the leaf will be freed */
639+
void *to_be_freed = NULL;
640+
utils_atomic_load_acquire_ptr(&k->to_be_freed, &to_be_freed);
641+
utils_atomic_store_release_ptr(&k->to_be_freed, NULL);
628642
#ifndef NDEBUG
629-
// check if the reference count is overflowed
630-
utils_atomic_load_acquire_u64(&k->ref_count, &ref_count);
631-
assert((ref_count & (1ULL << 63)) == 0);
632-
assert(ref_count != (uint64_t)(0 - 1ULL));
643+
if (to_be_freed == NULL) {
644+
LOG_FATAL("leaf will not be freed (to_be_freed == NULL, value = %p)\n",
645+
k->value);
646+
assert(to_be_freed != NULL);
647+
}
633648
#endif
634649

650+
// mark the leaf as not used (ref_count == 0)
651+
utils_atomic_store_release_u64(&k->ref_count, 0ULL);
652+
653+
c->cb_free_leaf(c->leaf_allocator, to_be_freed);
654+
655+
uint8_t expected = 1;
656+
uint8_t desired = 0;
657+
if (utils_compare_exchange_u8(&k->pending_deleted_leaf, &expected,
658+
&desired)) {
659+
add_to_deleted_leaf_list(c, k);
660+
}
661+
635662
return 0;
636663
}
637664

@@ -661,7 +688,7 @@ static inline int increment_ref_count(struct critnib_leaf *k) {
661688

662689
do {
663690
utils_atomic_load_acquire_u64(&k->ref_count, &expected);
664-
if (expected == 0) {
691+
if (expected < LEAF_VALID) {
665692
return -1;
666693
}
667694
desired = expected + 1;

0 commit comments

Comments
 (0)