]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
crimson/seastore: fix LogNode overflow from unsigned underflow in expect_overflow
authorMatan Breizman <mbreizma@redhat.com>
Sun, 19 Jul 2026 12:07:10 +0000 (12:07 +0000)
committerMatan Breizman <mbreizma@redhat.com>
Sun, 19 Jul 2026 13:29:04 +0000 (13:29 +0000)
The can_ow branch of LogManager's expect_overflow computed:
```
remain = capacity() - get_last_pos() - reserved_len
```
all uint32 operands, once the log leaf node is near full
(get_last_pos() + reserved_len >= capacity()) this subtraction underflows.

The node never rolls to a new leaf, instead it overflows the
16 KB block at commit-time delta replay.

Captured at the replay overflow point (per-collection batching, 4K randwrite):

```
  LogNode OVERFLOW at replay: pos=16641 size=58 reserved_len=3098
                              reserved_size=6 capacity=16328 LIMIT=16384

  log_node.h : In function 'void LogKVNodeLayout::set_last_pos(uint32_t)',
               ceph_assert(pos <= LOG_NODE_BLOCK_SIZE)
```

Here reserved_len (3098) is accurate: ~13543 committed + 3098 pending =
16641 = pos, i.e. the node was already ~13.5 KB full and the predictor
should have rolled over. Instead capacity - get_last_pos - reserved_len
went negative (16328 - ~13273 - 3098 < 0), wrapped in uint32, and the
check passed; pos reached 16641 > 16384 and set_last_pos asserted.

This was exposed by the per-coll batching where many overwrites append in
a single txn.

Signed-off-by: Matan Breizman <mbreizma@redhat.com>
src/crimson/os/seastore/omap_manager/log/log_node.cc

index e87a3997b3fe5808f3ab9f65513b74bc37b3fd2b..fdb68aeaccbb58f967bb9ef9adfe0c096da6b216 100644 (file)
@@ -324,14 +324,14 @@ bool LogNode::expect_overflow(const std::string &key,
   size_t ksize = key.size();
   if (can_ow) { 
     int gap = ow_gap_from_last_entry(key.size(), vsize);
-    uint64_t remain = capacity() - get_last_pos() - reserved_len;
     if (gap >= 0) {
       gap += static_cast<uint64_t>(gap);
     } else {
       uint64_t d = static_cast<uint64_t>(-gap);
       gap -= d;
     }
-    return remain < get_entry_size(ksize, vsize);
+    return get_last_pos() + reserved_len + get_entry_size(ksize, vsize)
+           > capacity();
   } else if (get_size() + reserved_size + 1 > d_bitmap_t::MAX_ENTRY) {
     return true;
   } else if (is_ow_key(key) && !can_ow) {