Skip to content

Commit 182ad38

Browse files
authored
Guard against integer overflow in duration conversion (#1584)
Guard against overflow when converting from rclcpp::Duration to builtin_interfaces::msg::Duration, which is a unsigned to signed conversion. Use non-std int types for consistency Handle large negative values Signed-off-by: Jacob Perron <[email protected]>
1 parent 59ad83a commit 182ad38

File tree

2 files changed

+54
-8
lines changed

2 files changed

+54
-8
lines changed

rclcpp/src/rclcpp/duration.cpp

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,27 @@ Duration::operator builtin_interfaces::msg::Duration() const
6767
{
6868
builtin_interfaces::msg::Duration msg_duration;
6969
constexpr rcl_duration_value_t kDivisor = RCL_S_TO_NS(1);
70+
constexpr int32_t max_s = std::numeric_limits<int32_t>::max();
71+
constexpr int32_t min_s = std::numeric_limits<int32_t>::min();
72+
constexpr uint32_t max_ns = std::numeric_limits<uint32_t>::max();
7073
const auto result = std::div(rcl_duration_.nanoseconds, kDivisor);
7174
if (result.rem >= 0) {
72-
msg_duration.sec = static_cast<std::int32_t>(result.quot);
73-
msg_duration.nanosec = static_cast<std::uint32_t>(result.rem);
75+
// saturate if we will overflow
76+
if (result.quot > max_s) {
77+
msg_duration.sec = max_s;
78+
msg_duration.nanosec = max_ns;
79+
} else {
80+
msg_duration.sec = static_cast<int32_t>(result.quot);
81+
msg_duration.nanosec = static_cast<uint32_t>(result.rem);
82+
}
7483
} else {
75-
msg_duration.sec = static_cast<std::int32_t>(result.quot - 1);
76-
msg_duration.nanosec = static_cast<std::uint32_t>(kDivisor + result.rem);
84+
if (result.quot <= min_s) {
85+
msg_duration.sec = min_s;
86+
msg_duration.nanosec = 0u;
87+
} else {
88+
msg_duration.sec = static_cast<int32_t>(result.quot - 1);
89+
msg_duration.nanosec = static_cast<uint32_t>(kDivisor + result.rem);
90+
}
7791
}
7892
return msg_duration;
7993
}
@@ -238,11 +252,14 @@ Duration::to_rmw_time() const
238252
throw std::runtime_error("rmw_time_t cannot be negative");
239253
}
240254

241-
// reuse conversion logic from msg creation
242-
builtin_interfaces::msg::Duration msg = *this;
255+
// Purposefully avoid creating from builtin_interfaces::msg::Duration
256+
// to avoid possible overflow converting from int64_t to int32_t, then back to uint64_t
243257
rmw_time_t result;
244-
result.sec = static_cast<uint64_t>(msg.sec);
245-
result.nsec = static_cast<uint64_t>(msg.nanosec);
258+
constexpr rcl_duration_value_t kDivisor = RCL_S_TO_NS(1);
259+
const auto div_result = std::div(rcl_duration_.nanoseconds, kDivisor);
260+
result.sec = static_cast<uint64_t>(div_result.quot);
261+
result.nsec = static_cast<uint64_t>(div_result.rem);
262+
246263
return result;
247264
}
248265

rclcpp/test/rclcpp/test_duration.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ TEST_F(TestDuration, maximum_duration) {
138138
static const int64_t HALF_SEC_IN_NS = 500 * 1000 * 1000;
139139
static const int64_t ONE_SEC_IN_NS = 1000 * 1000 * 1000;
140140
static const int64_t ONE_AND_HALF_SEC_IN_NS = 3 * HALF_SEC_IN_NS;
141+
static const int64_t MAX_NANOSECONDS = std::numeric_limits<int64_t>::max();
141142

142143
TEST_F(TestDuration, from_seconds) {
143144
EXPECT_EQ(rclcpp::Duration(0ns), rclcpp::Duration::from_seconds(0.0));
@@ -267,6 +268,34 @@ TEST_F(TestDuration, conversions) {
267268
auto chrono_duration = duration.to_chrono<std::chrono::nanoseconds>();
268269
EXPECT_EQ(chrono_duration.count(), -ONE_AND_HALF_SEC_IN_NS);
269270
}
271+
272+
{
273+
auto duration = rclcpp::Duration::from_nanoseconds(MAX_NANOSECONDS);
274+
275+
const auto duration_msg = static_cast<builtin_interfaces::msg::Duration>(duration);
276+
EXPECT_EQ(duration_msg.sec, std::numeric_limits<int32_t>::max());
277+
EXPECT_EQ(duration_msg.nanosec, std::numeric_limits<uint32_t>::max());
278+
279+
auto rmw_time = duration.to_rmw_time();
280+
EXPECT_EQ(rmw_time.sec, 9223372036u);
281+
EXPECT_EQ(rmw_time.nsec, 854775807u);
282+
283+
auto chrono_duration = duration.to_chrono<std::chrono::nanoseconds>();
284+
EXPECT_EQ(chrono_duration.count(), MAX_NANOSECONDS);
285+
}
286+
287+
{
288+
auto duration = rclcpp::Duration::from_nanoseconds(-MAX_NANOSECONDS);
289+
290+
const auto duration_msg = static_cast<builtin_interfaces::msg::Duration>(duration);
291+
EXPECT_EQ(duration_msg.sec, std::numeric_limits<int32_t>::min());
292+
EXPECT_EQ(duration_msg.nanosec, 0u);
293+
294+
EXPECT_THROW(duration.to_rmw_time(), std::runtime_error);
295+
296+
auto chrono_duration = duration.to_chrono<std::chrono::nanoseconds>();
297+
EXPECT_EQ(chrono_duration.count(), -MAX_NANOSECONDS);
298+
}
270299
}
271300

272301
TEST_F(TestDuration, test_some_constructors) {

0 commit comments

Comments
 (0)