Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions test/unittest/transport/SharedMemTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,51 @@ TEST_F(SHMTransportTests, dead_listener_sender_port_recover)
thread_wait_deadlock.join();
}

// Regression test for https://github.com/eProsima/Fast-DDS/issues/6501
// Reproduces a crash when opening a port whose segment holds damaged allocator
// structures.
//
// open_port_internal validates an existing segment with check_sanity(), which
// iterates the allocator's free-block tree through offset pointers stored inside
// the segment. When those links are inconsistent the traversal reads unmapped
// memory and the process dies (0xC0000005 on Windows, SIGSEGV elsewhere). The
// fault is not a C++ exception, so the catch(std::exception&) around the call
// cannot intercept it.
//
// Note the port here is open and owned, so this is not about stale or leftover
// segments: ownership does not protect the traversal.
//
// On platforms without gtest's SEH handling this aborts the whole test binary
// rather than failing one case.
TEST_F(SHMTransportTests, port_corrupt_segment_recovers_on_open)
{
auto shared_mem_manager = SharedMemManager::create(domain_name);
SharedMemGlobal* shared_mem_global = shared_mem_manager->global_segment();
MockPortSharedMemGlobal port_mocker;

shared_mem_global->remove_port(0);

auto test_case = [&](uint8_t corrupt_byte)
{
auto port = shared_mem_global->open_port(0, 1, 1000);
ASSERT_NO_THROW(port->healthy_check());

// Damage the allocator structures the way an abruptly terminated peer can.
port_mocker.corrupt_segment_allocator(*port, corrupt_byte);

// Opening the port again should not walk those structures.
auto recovered = shared_mem_global->open_port(0, 1, 1000);
ASSERT_TRUE(recovered != nullptr);
ASSERT_NO_THROW(recovered->healthy_check());
};

for (uint8_t corrupt_byte = 0xFF; corrupt_byte > 0x00; corrupt_byte--)
{
test_case(corrupt_byte);
}
test_case(0x00);
}

TEST_F(SHMTransportTests, port_not_ok_listener_recover)
{
auto shared_mem_manager = SharedMemManager::create(domain_name);
Expand Down
31 changes: 31 additions & 0 deletions test/unittest/transport/mock/SharedMemGlobalMock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#ifndef _FASTDDS_MOCKSHAREDMEM_GLOBAL_H_
#define _FASTDDS_MOCKSHAREDMEM_GLOBAL_H_

#include <cstring>

#include <rtps/transport/shared_mem/SharedMemGlobal.hpp>

namespace eprosima {
Expand Down Expand Up @@ -109,6 +111,35 @@ class MockPortSharedMemGlobal
port.node_->num_listeners++;
}

/**
* Corrupt the allocator structures of a port segment in place.
*
* Writing the file on disk is unreliable for this: on Windows the segment
* may still be mapped or pending delete, so the bytes written are not
* necessarily the ones a later open sees. Scribbling directly on the
* mapping is unambiguous.
*
* The boost segment_manager header at the start is left alone so that
* open_only still succeeds -- damaging it only produces an exception, which
* open_port_internal already handles. What is destroyed here are the
* free-block tree links that check_sanity() walks.
*/
static void corrupt_segment_allocator(
SharedMemGlobal::Port& port,
uint8_t corrupt_value)
{
constexpr size_t keep_header_bytes = 0x100;

auto& segment = port.port_segment_->get();
auto base = static_cast<char*>(segment.get_address());
auto size = static_cast<size_t>(segment.get_size());

if (size > keep_header_bytes)
{
std::memset(base + keep_header_bytes, corrupt_value, size - keep_header_bytes);
}
}

};

} // namespace rtps
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -634,30 +634,35 @@ bool rbtree_best_fit<MutexFamily, VoidPointer, MemAlignment>::
//-----------------------
boost::interprocess::scoped_lock<mutex_type> guard(m_header);
//-----------------------
imultiset_iterator ib(m_header.m_imultiset.begin()), ie(m_header.m_imultiset.end());

size_type free_memory = 0;

//Iterate through all blocks obtaining their size
for(; ib != ie; ++ib){
free_memory += (size_type)ib->m_size*Alignment;
algo_impl_t::assert_alignment(&*ib);
if(!algo_impl_t::check_alignment(&*ib))
return false;
}

//Check allocated bytes are less than size
if(m_header.m_allocated > m_header.m_size){
return false;
}

//Calculate the maximum free memory available in the segment
size_type block1_off =
priv_first_block_offset_from_this(this, m_header.m_extra_hdr_bytes);
size_type max_free_memory = m_header.m_size - block1_off;

//Check free bytes are less than size
if(free_memory > (m_header.m_size - block1_off)){
return false;
//Iterate through all blocks obtaining their size
imultiset_iterator ib(m_header.m_imultiset.begin()), ie(m_header.m_imultiset.end());
size_type free_memory = 0;
for(; ib != ie; ++ib){
if(!algo_impl_t::check_alignment(&*ib)){
return false;
}
//A size of 0 is not allowed in the multiset
if(!ib->m_size){
return false;
}
free_memory += (size_type)ib->m_size*Alignment;
//Check free bytes are less than size
if(free_memory > max_free_memory){
return false;
}
}

return true;
}

Expand Down
Loading