some fixes

This commit is contained in:
Ayzen
2026-06-05 14:40:10 +03:00
parent 22942d9dc9
commit bbea744459
35 changed files with 1797 additions and 297 deletions
@@ -5,6 +5,8 @@
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <iostream>
#include <limits>
#include <new>
#include <stdexcept>
#include <string>
@@ -186,6 +188,14 @@ auto ShmRing::open_or_create(
const bool geometry_ok = header->capacity == capacity && header->slot_size_bytes == slot_size_bytes;
if (magic_ok && version_ok && geometry_ok) {
// Fix #50: the requested mapped_size matched the header geometry, but the
// backing file may have been created undersized by another process. Confirm
// st_size covers the geometry before trusting the mapping.
struct stat info {};
if (fstat(fd, &info) != 0) {
throw errno_message("Failed to stat shared memory ring", name);
}
validate_geometry(*header, static_cast<std::size_t>(info.st_size), name);
break;
}
@@ -247,6 +257,8 @@ auto ShmRing::open_existing(const std::string& name) -> ShmRing {
if (header->version != kRingVersion) {
throw std::runtime_error("Shared memory ring version mismatch for " + name);
}
// Fix #50: ensure the mapping actually spans every slot the header describes.
validate_geometry(*header, mapped_size, name);
ShmRing ring{};
ring.fd_ = scoped_fd.release();
@@ -293,17 +305,28 @@ auto ShmRing::push(std::span<const std::uint8_t> payload) -> bool {
}
const auto write_seq = header_->write_seq.load(std::memory_order_relaxed);
const auto read_seq = header_->read_seq.load(std::memory_order_acquire);
auto read_seq = header_->read_seq.load(std::memory_order_acquire);
if (checked_u64_diff(write_seq, read_seq) >= header_->capacity) {
header_->read_seq.store(read_seq + 1U, std::memory_order_release);
header_->dropped.fetch_add(1U, std::memory_order_relaxed);
// Ring full: overwrite the oldest unread slot. Advance read_seq via CAS so a
// concurrent consumer's advance is never clobbered (read_seq never moves back).
header_->read_seq.compare_exchange_strong(
read_seq, read_seq + 1U, std::memory_order_acq_rel, std::memory_order_acquire);
const auto dropped = header_->dropped.fetch_add(1U, std::memory_order_relaxed) + 1U;
if (dropped == 1U || dropped % 100U == 0U) {
std::cerr << "shm_ring: overflow overwrote an unread slot (dropped total=" << dropped << ")\n";
}
}
auto* slot = slot_header(write_seq);
// Seqlock publish: write the payload + size FIRST, then store the slot sequence
// LAST (after a release fence). A consumer that observes sequence == write_seq+1
// is therefore guaranteed a fully-written payload. (Publishing the sequence before
// the copy, as before, let a consumer copy a half-written slot.)
slot->payload_size = static_cast<std::uint32_t>(payload.size());
slot->sequence = write_seq + 1U;
std::memcpy(slot_payload(slot), payload.data(), payload.size());
std::atomic_thread_fence(std::memory_order_release);
slot->sequence = write_seq + 1U;
std::atomic_thread_fence(std::memory_order_release);
header_->write_seq.store(write_seq + 1U, std::memory_order_release);
@@ -323,21 +346,31 @@ auto ShmRing::pop(std::vector<std::uint8_t>& payload) -> bool {
auto* slot = slot_header(read_seq);
if (slot->sequence != read_seq + 1U) {
// Producer overwrote this slot before consumer read it. Resync to latest.
// Slot not published for this seq, or already overwritten (lapped). Resync.
header_->read_seq.store(write_seq, std::memory_order_release);
return false;
}
const auto payload_size = slot->payload_size;
if (payload_size > header_->slot_size_bytes) {
// Corrupt/torn slot: skip it (resync) rather than throw — a single bad slot
// must never abort the long-running consumer.
header_->read_seq.store(write_seq, std::memory_order_release);
throw std::runtime_error("Invalid payload size in shared memory slot");
return false;
}
std::atomic_thread_fence(std::memory_order_acquire);
payload.resize(payload_size);
std::memcpy(payload.data(), slot_payload(slot), payload_size);
// Seqlock verify: if the slot sequence changed during the copy, the producer
// lapped us mid-read and the payload is torn — discard and resync.
std::atomic_thread_fence(std::memory_order_acquire);
if (slot->sequence != read_seq + 1U) {
header_->read_seq.store(write_seq, std::memory_order_release);
return false;
}
header_->read_seq.store(read_seq + 1U, std::memory_order_release);
return true;
}
@@ -377,4 +410,34 @@ void ShmRing::validate_name(const std::string& name) {
}
}
void ShmRing::validate_geometry(const Header& header, std::size_t mapped_size, const std::string& name) {
// Fix #50: derive the expected size from the header's own geometry fields and
// require the real mapping to cover it. A bogus capacity/slot_size or a truncated
// mapping would otherwise yield out-of-bounds slot offsets and a SIGSEGV.
const std::uint32_t capacity = header.capacity;
const std::uint32_t slot_size_bytes = header.slot_size_bytes;
if (capacity == 0U) {
throw std::runtime_error("Shared memory ring capacity is zero for " + name);
}
if (slot_size_bytes == 0U) {
throw std::runtime_error("Shared memory ring slot size is zero for " + name);
}
constexpr auto kMax = std::numeric_limits<std::size_t>::max();
const auto slot_stride = sizeof(SlotHeader) + static_cast<std::size_t>(slot_size_bytes);
// Guard each multiply/add against std::size_t overflow before computing expected_size.
if (slot_stride > kMax / static_cast<std::size_t>(capacity)) {
throw std::runtime_error("Shared memory ring geometry overflows for " + name);
}
const auto slots_size = slot_stride * static_cast<std::size_t>(capacity);
if (slots_size > kMax - sizeof(Header)) {
throw std::runtime_error("Shared memory ring geometry overflows for " + name);
}
const auto expected_size = sizeof(Header) + slots_size;
if (mapped_size < expected_size) {
throw std::runtime_error("Shared memory ring size is too small for " + name);
}
}
} // namespace radar::ipc