Skip to content

Harden PubSubService::topicMap_ against untrusted topic-name hash flooding #2602

Description

@K-ANOY

Describe the bug

PubSubService<MessageType> stores topic names in a default-hashed std::unordered_map<std::string, ...>. Applications that accept client-chosen topics can expose collision-driven overhead: on builds with predictable string hashing and bucket selection, many distinct colliding names can make lookups approach O(n) and cumulative subscription work approach O(n²).

Drogon's bundled WebSocket chat example passes the client's room_name query value directly to subscribe. Creating a new topic involves lookup and insertion under an exclusive lock, so prolonged operations can delay other users of the same PubSubService instance.

The request parameter map uses SafeStringMap, but the parameter's value becomes a key in the separate default-hashed topic map. The parameter map's protection does not extend to topic-name lookups.

To Reproduce

  1. Build and run the bundled websocket_server example. It listens on 127.0.0.1:8848 and exposes /chat.
  2. Connect to ws://127.0.0.1:8848/chat?room_name=room_a with a WebSocket client. The handler uses room_a as a topic name. This ordinary name illustrates the input path; it is not a collision sample.
  3. For a collision workload, prepare distinct topic names that concentrate in one bucket under the target build's string hash and actual table growth. Open one connection per name, URL-encoding the query value as needed, and keep the connections open. Collision selection must use the decoded names.
  4. Compare subscription-processing time with an equal number of ordinary names of the same lengths, using the same connection schedule and a fresh server instance for each run. Repeat at increasing topic counts to measure collision-specific overhead separately from connection setup and ordinary topic allocation.

Each connection in this example holds one subscription. Closing it unsubscribes, and the last unsubscribe removes the topic. Repeated connections to the same room do not grow topicMap_; distinct live rooms are required.

Expected behavior

Applications using client-selected topics should be protected against hash flooding through a keyed hash designed for adversarial input and appropriate live-topic/subscription limits. Chosen topic names should not produce disproportionate processing costs that delay unrelated topic operations on the same service instance.

Additional context

Relevant source locations:

  • examples/websocket_server/WebSocketServer.cc:59–60: reads room_name and subscribes; 46–50: unsubscribes on disconnect.
  • lib/inc/drogon/PubSubService.h:275–276: declares the default-hashed topic map; 285–300: looks up and creates topics; 198–218: removes an empty topic after unsubscribe.
  • lib/src/HttpRequestImpl.h:198–210: request parameters use SafeStringMap; getParameter returns the parameter value.

Relevant statements, with unrelated code omitted:

std::unordered_map<std::string, std::shared_ptr<Topic<MessageType>>>
    topicMap_;

// subscribeToTopic: insert an unknown topic under the exclusive lock.
topicMap_[topicName] = std::move(topicPtr);

// examples/websocket_server/WebSocketServer.cc:59-60
s.chatRoomName_ = req->getParameter("room_name");
s.id_ = chatRooms_.subscribe(s.chatRoomName_, /* callback */);

PubSubService imposes no topic-count limit itself. The example inherits a configured global connection limit of 100,000, with no per-IP limit by default (lib/src/HttpConnectionLimit.h:50,53). Its loopback listener requires deployment changes for external access.

The topic table is shared within one service instance. Subscription checks an unknown topic under an exclusive lock before inserting (PubSubService.h:292–300). Publication looks up the topic under a shared lock and releases that lock before invoking callbacks (148–159).

Suggested fix: use a hash-flooding-resistant keyed string hash for topicMap_, and add or document live-topic limits per service and subscription limits at the application layer, where connection/user identity is available.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions