Giter Club home page Giter Club logo

Comments (3)

sylt avatar sylt commented on June 30, 2024 2

I've debugged the issue, and I think the smallest diff for fixing it (while maintaining the functionality of #2917) is:

diff --git a/gunicorn/workers/gthread.py b/gunicorn/workers/gthread.py
index c9c42345..9c1a92ad 100644
--- a/gunicorn/workers/gthread.py
+++ b/gunicorn/workers/gthread.py
@@ -119,6 +119,9 @@ class ThreadWorker(base.Worker):
 
     def accept(self, server, listener):
         try:
+            if self.nr_conns >= self.worker_connections:
+                return
+
             sock, client = listener.accept()
             # initialize the connection object
             conn = TConn(self.cfg, sock, client, server)
@@ -218,6 +221,10 @@ class ThreadWorker(base.Worker):
                                       return_when=futures.FIRST_COMPLETED)
             else:
                 # wait for a request to finish
+                events = self.poller.select(0.0)
+                for key, _ in events:
+                    callback = key.data
+                    callback(key.fileobj)
                 result = futures.wait(self.futures, timeout=1.0,
                                       return_when=futures.FIRST_COMPLETED)
 

For those interested, I think the root cause of the bug is the following:

  1. All is good. There are no connections. We're running in run(), specifically in self.poller.select(), waiting for a connection to arrive to us.
  2. All of a sudden, someone wants to connect. self.poller.select() returns our self.accept() function, and we enter and accept the connection. The resulting socket returned from accept() is added to the event queue.
  3. Step 2. Is repeated until we can't accept any more connections (i.e., self.nr_conns hit the limit self.worker_connections).
  4. Now, it's time for another loop in run(). Since we've hit our connection limit, the code will avoid executing self.poller.select(), since we shouldn't accept any new connections. Unfortunately, since our accept()ed sockets are also on the event queue, we won't process their requests either! Oops...
  5. So we loop ad infinitum. Since the newly added sockets haven't gotten their initial handlers run, there won't be any futures to process either. And doing futures.wait(self.futures) with self.futures = [] returns immediately, and then it's back to the beginning again of the loop, turning the loop into a busy loop.

About the patch: by running self.poller.select() even in the case when we want to avoid accepting connections, we'll always serve our already accepted connections, thereby fixing the bug. However, we need an extra if statement in self.accept() to protect us from accepting too many connections.

I had some other clean-ups in mind which would fix this bug, but I'm just posting this minimal version early on in case @benoitc would like to incorporate it.

from gunicorn.

javiertejero avatar javiertejero commented on June 30, 2024

@JorisOnGithub could you please confirm if this patch is not breaking all the improvements needed to solve #2917

PS: I couldn't reproduce the issues you described, but I don't have WebView2, and I couldn't reproduce with Microsoft Edge for macos

from gunicorn.

chris-griffin avatar chris-griffin commented on June 30, 2024

Hi @benoitc and team - First off, thank you all for what you do! I tried to contact [email protected] about this issue, but the email bounced.

image

Since this issue is already out in the public it seemed appropriate to post here instead. This issue is a denial-of-service (DOS) vulnerability although not labeled as such in the original description.

While the default of 1000 for worker_connections should hopefully mean that implementors that have reasonable rate limits in upstream reverse proxies/WAFs should be protected, anyone with a rate limit >1000 per IP address would make this trivial to exploit. Additionally, an effective DDoS with a relatively low number of IP addresses should be more plausible.

I see that gunicorn has not historically used GitHub's security advisory feature. I just wanted to highlight that it makes it very easy to request a CVE in case you are not familiar with this feature.

from gunicorn.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.