[PATCH v2] drbd: reject an out-of-range offset when decoding a compressed bitmap

From: Yehyeong Lee

Date: Tue Oct 06 2026 - 10:16:39 EST


recv_bm_rle_bits() decodes a run-length-encoded bitmap from the peer and
accumulates the bit position s across the runs. The bound was only
applied where bits are actually set: a clear run advanced s with no check
at all, so a peer could leave c->bit_offset past the end of the bitmap
once the bitstream ran out mid-sequence. receive_bitmap() then carries
that offset into the next packet, where the plain-bitmap path computes
c->bm_words - c->word_offset, underflows it, and hands the result to
drbd_bm_merge_lel(), which only WARN_ON()s an out-of-range word offset
before reaching bm_word_to_page_idx() and its BUG_ON().

Check each run against the remaining bitmap before advancing s. A single
VLI code decodes to a run length of up to about 2^56, so checking after
the loop would leave s free to move far out of range (and in principle to
wrap) in the middle of the sequence; bounding every run instead keeps
s <= c->bm_bits as an invariant, which also means c->bm_bits - s cannot
underflow. The set-run bound becomes redundant once that holds --
e >= c->bm_bits is exactly rl > c->bm_bits - s -- so drop it rather than
leave a check that can no longer fire.

Fixes: b411b3637fa7 ("The DRBD driver")
Cc: stable@xxxxxxxxxxxxxxx
Suggested-by: Philipp Reisner <philipp.reisner@xxxxxxxxxx>
Signed-off-by: Yehyeong Lee <yhlee@xxxxxxxxxxxxxxxxxx>
---
v2: per Philipp Reisner's review -- check each run before advancing s so
s stays <= c->bm_bits throughout (a single VLI run can be ~2^56),
instead of only after the loop. The set-run bound is then redundant
and is dropped.
v1: https://lore.kernel.org/all/20261006034333.351130-1-yhlee@xxxxxxxxxxxxxxxxxx/

drivers/block/drbd/drbd_receiver.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/drivers/block/drbd/drbd_receiver.c b/drivers/block/drbd/drbd_receiver.c
index 2135c14354a85..1febb5e089cc1 100644
--- a/drivers/block/drbd/drbd_receiver.c
+++ b/drivers/block/drbd/drbd_receiver.c
@@ -4529,12 +4529,19 @@ recv_bm_rle_bits(struct drbd_peer_device *peer_device,
if (bits <= 0)
return -EIO;

+ /*
+ * A single run can be up to about 2^56, so check it before
+ * advancing s: that keeps s <= c->bm_bits throughout and
+ * leaves no way for s to wrap in the middle of the loop.
+ */
+ if (rl > c->bm_bits - s) {
+ drbd_err(peer_device, "bitmap overflow (s:%lu rl:%llu) while decoding bm RLE packet\n",
+ s, (unsigned long long)rl);
+ return -EIO;
+ }
+
if (toggle) {
e = s + rl -1;
- if (e >= c->bm_bits) {
- drbd_err(peer_device, "bitmap overflow (e:%lu) while decoding bm RLE packet\n", e);
- return -EIO;
- }
_drbd_bm_set_bits(peer_device->device, s, e);
}

--
2.43.0