# ROS2 (Robot Operating System 2) — CWE-407 Disclosure Brief

**Project:** ROS2 (Robot Operating System 2)
**Disclosure date:** 2026-03-27
**Severity:** MEDIUM
**Speedup:** 5× (worst case)
**Status:** PATCHED

---

## Finding

Two independent defects in rclcpp use `std::find` on unsorted `std::vector` for repeated membership checks where `std::unordered_set` is the correct structure. The first affects real-time parameter event filtering (ros2-0001), performing O(N×P) work per event. The second affects `list_parameters()` prefix deduplication (ros2-0002), accumulating O(P²) comparisons as the result prefix list grows.

## The Defect(s)

| ID | Location | Pattern | Complexity |
|----|----------|---------|------------|
| ros2-0001 | `rclcpp/src/rclcpp/parameter_events_filter.cpp:34` | `std::find` on `names` vector (N) inside 3 loops over P event parameters per event | O(3×N×P) per event |
| ros2-0002 | `rclcpp/src/rclcpp/node_interfaces/node_parameters.cpp:1128` | `std::find` on growing `result.prefixes` vector inside prefix-dedup loop | O(P²) |

## Complexity Proof

**ros2-0001:** Let N = number of filter names, P = number of parameters in the incoming event.

`ParameterEventsFilter` constructor iterates over 3 change-type arrays (new, changed, deleted parameters). For each of the P entries in each array, it calls `std::find(names.begin(), names.end(), param_name)` to check if the parameter is in the filter set:

```
3 arrays × P entries each × O(N) find = O(3×N×P) per event
```

With an `unordered_set<string>` the find is O(1) average, reducing to O(3×P) per event. Measured speedup: 4.5×.

**ros2-0002:** Let P = number of parameter names returned. `list_parameters()` deduplicates the prefix list by calling `std::find(result.prefixes.begin(), result.prefixes.end(), prefix)` before each insert. As `result.prefixes` grows from 0 to P entries:

```
Insert 1: find in list of 0 → O(0)
Insert 2: find in list of 1 → O(1)
...
Insert P: find in list of P-1 → O(P-1)
Total: 0 + 1 + ... + (P-1) = P(P-1)/2 = O(P²)
```

With an `unordered_set` tracking inserted prefixes alongside the vector, each check is O(1): total O(P). Measured speedup: 5×.

## Impact

ros2-0001 affects any ROS2 node subscribing to parameter events with a filter list — a common pattern in parameter monitoring nodes and dynamic reconfiguration handlers. In high-frequency parameter update scenarios (sensor calibration, controller tuning), this fires on every `/parameter_events` message.

ros2-0002 affects any node calling `list_parameters()` with a large parameter namespace, including parameter introspection tools (`ros2 param list`) and lifecycle management nodes.

## The Fix

**ros2-0001:** Convert the `names` vector to `std::unordered_set<std::string>` at construction time (or accept it as a set). Replace `std::find` with `.count()`.

**ros2-0002:** Maintain a parallel `std::unordered_set<std::string> seen_prefixes` during the dedup loop. Check and insert into the set O(1); only push to the vector when not already seen.

## Patch

```diff
// ros2-0001: parameter_events_filter.cpp
- std::vector<std::string> names_;
+ std::unordered_set<std::string> names_;

- if (std::find(names_.begin(), names_.end(), param.name) != names_.end()) {
+ if (names_.count(param.name)) {

// ros2-0002: node_parameters.cpp
+ std::unordered_set<std::string> seen_prefixes;
  for (const auto & kv : parameters_) {
      // ... extract prefix ...
-     if (std::find(result.prefixes.begin(), result.prefixes.end(), prefix)
-         == result.prefixes.end()) {
+     if (seen_prefixes.insert(prefix).second) {
          result.prefixes.push_back(prefix);
      }
  }
```

## What We Ask

Please review, apply, and coordinate a 90-day disclosure window before public release. Reply to security@undefect.com.

---

*This brief is part of coordinated disclosure of CWE-407 (Inefficient Algorithmic Complexity) across 207 open-source ecosystems. Full report: https://undefect.com*
