diff --git a/test/unittest/transport/SharedMemTests.cpp b/test/unittest/transport/SharedMemTests.cpp index 793ee17c340..4a9f80864f4 100644 --- a/test/unittest/transport/SharedMemTests.cpp +++ b/test/unittest/transport/SharedMemTests.cpp @@ -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); diff --git a/test/unittest/transport/mock/SharedMemGlobalMock.hpp b/test/unittest/transport/mock/SharedMemGlobalMock.hpp index ec6e2d07555..05f53db7945 100644 --- a/test/unittest/transport/mock/SharedMemGlobalMock.hpp +++ b/test/unittest/transport/mock/SharedMemGlobalMock.hpp @@ -15,6 +15,8 @@ #ifndef _FASTDDS_MOCKSHAREDMEM_GLOBAL_H_ #define _FASTDDS_MOCKSHAREDMEM_GLOBAL_H_ +#include + #include namespace eprosima { @@ -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(segment.get_address()); + auto size = static_cast(segment.get_size()); + + if (size > keep_header_bytes) + { + std::memset(base + keep_header_bytes, corrupt_value, size - keep_header_bytes); + } + } + }; } // namespace rtps diff --git a/thirdparty/boost/include/boost/interprocess/mem_algo/rbtree_best_fit.hpp b/thirdparty/boost/include/boost/interprocess/mem_algo/rbtree_best_fit.hpp index 7da31f73bfc..76c6a532f6c 100644 --- a/thirdparty/boost/include/boost/interprocess/mem_algo/rbtree_best_fit.hpp +++ b/thirdparty/boost/include/boost/interprocess/mem_algo/rbtree_best_fit.hpp @@ -634,30 +634,35 @@ bool rbtree_best_fit:: //----------------------- boost::interprocess::scoped_lock 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; }