some fixes
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user