Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
36 changes: 36 additions & 0 deletions test/unittest/transport/SharedMemTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,42 @@ TEST_F(SHMTransportTests, dead_listener_sender_port_recover)
thread_wait_deadlock.join();
}

// 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, which is the other reason it ships disabled.
Comment thread
raulojeda22 marked this conversation as resolved.
Outdated
TEST_F(SHMTransportTests, port_corrupt_segment_crashes_on_open)
Comment thread
raulojeda22 marked this conversation as resolved.
Outdated
{
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 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);

// Opening the port again should not walk those structures. Today it does,
Comment thread
raulojeda22 marked this conversation as resolved.
Outdated
// and this is where the process dies.
auto recovered = shared_mem_global->open_port(0, 1, 1000);
ASSERT_TRUE(recovered != nullptr);
ASSERT_NO_THROW(recovered->healthy_check());
}

TEST_F(SHMTransportTests, port_not_ok_listener_recover)
{
auto shared_mem_manager = SharedMemManager::create(domain_name);
Expand Down
30 changes: 30 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,34 @@ 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)
{
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, 0xEF, size - keep_header_bytes);
Comment thread
raulojeda22 marked this conversation as resolved.
Outdated
}
}

};

} // namespace rtps
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -634,30 +634,31 @@ 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;
}
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