From 0de9eeaebabb269925426828e49867af104b1c97 Mon Sep 17 00:00:00 2001 From: KD2YCU Date: Sat, 23 May 2026 17:40:32 -0400 Subject: [PATCH 1/2] Fix bug from de7f1c78f70f8df4b2956f7363da3774348bc58e that changed from using unsigned long to id_t. new_subid_range() returns the same value as the ceiling allowing potentially overlapping subordinate ID ranges --- lib/subordinateio.c | 56 ++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/lib/subordinateio.c b/lib/subordinateio.c index 357ec54621..f47ace1d0f 100644 --- a/lib/subordinateio.c +++ b/lib/subordinateio.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "alloc/malloc.h" @@ -332,10 +333,11 @@ static int subordinate_range_cmp (const void *p1, const void *p2) static id_t find_free_range(struct commonio_db *db, id_t min, id_t max, unsigned long count) { - id_t low, high; + uintmax_t low, max_id; const struct subordinate_range *range; - if (count == 0 || max < min || count - 1 > max - min) { + if (count == 0 || max < min + || (uintmax_t) count - 1 > (uintmax_t) max - min) { errno = ERANGE; return -1; } @@ -344,35 +346,48 @@ find_free_range(struct commonio_db *db, id_t min, id_t max, unsigned long count) commonio_sort (db, subordinate_range_cmp); commonio_rewind(db); - low = min; + low = (uintmax_t) min; + max_id = (uintmax_t) max; while (NULL != (range = commonio_next(db))) { - id_t first, last; + uintmax_t first, last; - first = range->start; - last = first + range->count - 1; - - /* Find the top end of the hole before this range */ - high = first; + if (range->count == 0) + continue; - /* Don't allocate IDs after max (included) */ - if (high > max + 1) { - high = max + 1; + first = range->start; + if (__builtin_add_overflow(first, range->count - 1, &last)) + last = UINTMAX_MAX; + + /* + * Check the hole before this range as an inclusive interval. + * This avoids constructing max + 1, which wraps when max is + * the largest representable id_t value. + */ + if (first > low) { + uintmax_t high = first - 1; + + /* Don't allocate IDs after max (included). */ + if (high > max_id) + high = max_id; + + /* Is the hole before this range large enough? */ + if ((high >= low) && ((high - low + 1) >= count)) + return (id_t) low; } - /* Is the hole before this range large enough? */ - if ((high > low) && ((high - low) >= count)) - return low; - /* Compute the low end of the next hole */ - if (low < (last + 1)) + if (last >= low) { + if (last == UINTMAX_MAX) + goto fail; low = last + 1; - if (low > max) + } + if (low > max_id) goto fail; } /* Is the remaining unclaimed area large enough? */ - if (((max - low) + 1) >= count) - return low; + if (((max_id - low) + 1) >= count) + return (id_t) low; fail: errno = EUSERS; return -1; @@ -1128,4 +1143,3 @@ void free_subid_pointer(void *ptr) #else /* !ENABLE_SUBIDS */ extern int ISO_C_forbids_an_empty_translation_unit; #endif /* !ENABLE_SUBIDS */ - From d25d136c63211fc0aff3283a6df0b59be9fb16b2 Mon Sep 17 00:00:00 2001 From: KD2YCU Date: Sun, 24 May 2026 18:37:51 -0400 Subject: [PATCH 2/2] lib: use signed arithmetic in find_free_range() Switch low/max_id (and the loop-local first/last/high) from uintmax_t to intmax_t so the gap arithmetic is signed: any overflow becomes undefined behaviour that sanitizers can catch, rather than silently wrapping. All operands are id_t values (<= 2^32-1) and count is already bounded to 2^32, so every value stays within the range where intmax_t and uintmax_t agree -- this preserves the 64-bit width that fixed the range-overlap bug (0de9eeae) and is a pure no-op over all valid input. Also drop the superfluous (id_t) casts on the return statements (implicit narrowing is fine and the value always fits), add (intmax_t) casts on the count comparisons to keep them signed, move the broken if's opening brace onto its own line per the brace style, and rewrite the stale "avoids max + 1 wrap" comment to instead document why a fitted block can never overlap an existing range. --- lib/subordinateio.c | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/subordinateio.c b/lib/subordinateio.c index f47ace1d0f..3377c322a3 100644 --- a/lib/subordinateio.c +++ b/lib/subordinateio.c @@ -333,11 +333,12 @@ static int subordinate_range_cmp (const void *p1, const void *p2) static id_t find_free_range(struct commonio_db *db, id_t min, id_t max, unsigned long count) { - uintmax_t low, max_id; + intmax_t low, max_id; const struct subordinate_range *range; if (count == 0 || max < min - || (uintmax_t) count - 1 > (uintmax_t) max - min) { + || (uintmax_t) count - 1 > (uintmax_t) max - min) + { errno = ERANGE; return -1; } @@ -346,38 +347,40 @@ find_free_range(struct commonio_db *db, id_t min, id_t max, unsigned long count) commonio_sort (db, subordinate_range_cmp); commonio_rewind(db); - low = (uintmax_t) min; - max_id = (uintmax_t) max; + low = min; + max_id = max; while (NULL != (range = commonio_next(db))) { - uintmax_t first, last; + intmax_t first, last; if (range->count == 0) continue; first = range->start; if (__builtin_add_overflow(first, range->count - 1, &last)) - last = UINTMAX_MAX; + last = INTMAX_MAX; /* - * Check the hole before this range as an inclusive interval. - * This avoids constructing max + 1, which wraps when max is - * the largest representable id_t value. + * Ranges are sorted by start, and low has been advanced past + * every range seen so far, so the gap [low, first - 1] (clamped + * to max) contains no existing range. A block that fits in + * this gap lies below first, hence below every later range too, + * so it cannot overlap any allocation. */ if (first > low) { - uintmax_t high = first - 1; + intmax_t high = first - 1; /* Don't allocate IDs after max (included). */ if (high > max_id) high = max_id; /* Is the hole before this range large enough? */ - if ((high >= low) && ((high - low + 1) >= count)) - return (id_t) low; + if ((high >= low) && ((high - low + 1) >= (intmax_t) count)) + return low; } /* Compute the low end of the next hole */ if (last >= low) { - if (last == UINTMAX_MAX) + if (last == INTMAX_MAX) goto fail; low = last + 1; } @@ -386,8 +389,8 @@ find_free_range(struct commonio_db *db, id_t min, id_t max, unsigned long count) } /* Is the remaining unclaimed area large enough? */ - if (((max_id - low) + 1) >= count) - return (id_t) low; + if (((max_id - low) + 1) >= (intmax_t) count) + return low; fail: errno = EUSERS; return -1;