Skip to content

Commit 048bc4d

Browse files
PavelGuzenfeldaosmw
andcommitted
Bounds-check EventHandlerBase::is_ready() against the wait set (#2376)
is_ready() indexed wait_set.events with wait_set_event_index_, which is only assigned once the handler is added to that wait set via add_to_wait_set(). If is_ready() runs first (e.g. a publisher/subscription event thread racing node setup), the index is uninitialized or refers to a different wait set, so the access reads out of bounds and can segfault (observed with ASan during lifecycle configure). Initialize wait_set_event_index_ and make is_ready() return false when the index is not within the wait set's events instead of dereferencing it. Initializing alone is not sufficient (index 0 may still not be this handler's slot), so the bounds check is the actual guard. Adds a regression test that calls is_ready() on a handler that was never added to a wait set. Refs #2376 Co-authored-by: Mike Wake <michael.wake@aosgrp.com.au> Generated-by: Claude Opus 4.8 (Anthropic) Signed-off-by: Pavel Guzenfeld <pavelguzenfeld@gmail.com> Signed-off-by: Mike Wake <michael.wake@aosgrp.com.au>
1 parent 7321b44 commit 048bc4d

3 files changed

Lines changed: 33 additions & 1 deletion

File tree

rclcpp/include/rclcpp/event_handler.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include <atomic>
1919
#include <functional>
20+
#include <limits>
2021
#include <memory>
2122
#include <mutex>
2223
#include <stdexcept>
@@ -247,7 +248,9 @@ class EventHandlerBase : public Waitable
247248
std::function<void(size_t)> on_new_event_callback_{nullptr};
248249

249250
rcl_event_t event_handle_;
250-
size_t wait_set_event_index_;
251+
// Sentinel that is always out of range, so is_ready() reports "not in any wait set"
252+
// until add_to_wait_set() assigns the real index. See ros2/rclcpp#2376.
253+
size_t wait_set_event_index_ = std::numeric_limits<size_t>::max();
251254
};
252255

253256
template<typename EventCallbackT, typename ParentHandleT>

rclcpp/src/rclcpp/event_handler.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ EventHandlerBase::add_to_wait_set(rcl_wait_set_t & wait_set)
6969
bool
7070
EventHandlerBase::is_ready(const rcl_wait_set_t & wait_set)
7171
{
72+
// wait_set_event_index_ is only valid once this handler has been added to this wait
73+
// set via add_to_wait_set(). If is_ready() is reached before that (e.g. an event
74+
// thread racing node setup, see ros2/rclcpp#2376), the index may not refer to a slot
75+
// in this wait set, so guard against an out-of-bounds read.
76+
if (wait_set_event_index_ >= wait_set.size_of_events) {
77+
return false;
78+
}
7279
return wait_set.events[wait_set_event_index_] == &event_handle_;
7380
}
7481

rclcpp/test/rclcpp/test_qos_event.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,28 @@ TEST_F(TestQosEvent, add_to_wait_set) {
338338
}
339339
}
340340

341+
// Regression for ros2/rclcpp#2376: is_ready() must not index wait_set.events with an
342+
// uninitialized/stale wait_set_event_index_ when the handler is not in the wait set
343+
// (e.g. an event thread racing node setup). It must return false, not read out of bounds.
344+
TEST_F(TestQosEvent, is_ready_when_not_in_wait_set) {
345+
auto publisher = node->create_publisher<test_msgs::msg::Empty>(topic_name, 10);
346+
auto rcl_handle = publisher->get_publisher_handle();
347+
348+
auto callback = [](int) {};
349+
350+
const rcl_publisher_event_type_t event_type =
351+
!rclcpp::PublisherBase::event_type_is_supported(RCL_PUBLISHER_OFFERED_DEADLINE_MISSED) ?
352+
RCL_PUBLISHER_MATCHED : RCL_PUBLISHER_OFFERED_DEADLINE_MISSED;
353+
354+
rclcpp::EventHandler<decltype(callback), decltype(rcl_handle)> handler(
355+
callback, rcl_publisher_event_init, rcl_handle, event_type);
356+
357+
// The handler has not been added to any wait set, so its event index is not valid for
358+
// this (empty) wait set; is_ready() must report not-ready instead of dereferencing it.
359+
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
360+
EXPECT_FALSE(handler.is_ready(wait_set));
361+
}
362+
341363
TEST_F(TestQosEvent, test_on_new_event_callback)
342364
{
343365
if (!rclcpp::SubscriptionBase::event_type_is_supported(

0 commit comments

Comments
 (0)