Skip to content

File Descriptor Leak Reaching Soft Limit with DDoS Requests #1188

Description

@aonq

We were testing Crow. We noticed when sending DDoS requests, it hits the fd soft limit, causing new fd open()s fail with errno 24 "Too many open files".

We discovered that during each handling in do_accept() in http_server.h, the shared pointer of Connection "p" is copied to the lambda in asio::io_service's post(), causing it to be enqueued and handled later. If we hammer with for example 2000 requests, each request takes several hundred milliseconds, Crow keeps creating and holding new Connections until hitting the 1024 fd limit.

        void do_accept()
        {
            if (!shutting_down_)
            {
                uint16_t service_idx = pick_io_service_idx();
                asio::io_service& is = *io_service_pool_[service_idx];
                task_queue_length_pool_[service_idx]++;
                CROW_LOG_DEBUG << &is << " {" << service_idx << "} queue length: " << task_queue_length_pool_[service_idx];

                auto p = std::make_shared<Connection<Adaptor, Handler, Middlewares...>>(
                  is, handler_, server_name_, middlewares_,
                  get_cached_date_str_pool_[service_idx], *task_timer_pool_[service_idx], adaptor_ctx_, task_queue_length_pool_[service_idx]);

                acceptor_.async_accept(
                  p->socket(),
                  [this, p, &is, service_idx](error_code ec) {
                      if (!ec)
                      {
                          is.post(
                            [p] {
                                p->start();
                            });
                      }
                      else
                      {
                          task_queue_length_pool_[service_idx]--;
                          CROW_LOG_DEBUG << &is << " {" << service_idx << "} queue length: " << task_queue_length_pool_[service_idx];
                      }
                      do_accept();
                  });
            }
        }

We did a temporary workaround:
We added a blocking check before is.post(...) -- If the task queue is larger than max_task_queue_length_, the new Connection won't be forwarded to p->start().

                acceptor_.async_accept(
                  p->socket(),
                  [this, p, &is, service_idx](error_code ec) {
                      // New change
                      if(max_task_queue_length_.has_value() && task_queue_length_pool_[service_idx] > max_task_queue_length_.value())
                      {
                        CROW_LOG_DEBUG << "Too many queued tasks for io_service " << &is << " {" << service_idx << "}, rejecting connection. Queue length: " << task_queue_length_pool_[service_idx];
                      }
                      else if (!ec)
                      {
                          is.post(
                            [p] {
                                p->start();
                            });
                      }

This works for now, but we would like to know is there a better solution on the fd level in Crow? Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    discussionThe viability / implementation of the issue is up for debatefeatureCode based project improvement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions