Skip to content

Commit c952b31

Browse files
Handle dead connections in HTTP response methods
When a client disconnects mid-response, binding.pyx raises ReferenceError from connection.send() because the underlying C connection (thisptr) is NULL. This became more visible after _log_request() was added, since the SQLite write introduces enough delay for clients to disconnect before the response is sent. Catch ReferenceError in send_response, send_header, and end_headers so the response path silently stops instead of crashing.
1 parent 15b042b commit c952b31

File tree

2 files changed

+90
-3
lines changed

2 files changed

+90
-3
lines changed

modules/python/dionaea/http.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,7 +1088,10 @@ def send_response(self, code, message=None):
10881088
message = self.responses[code][0]
10891089
else:
10901090
message = ''
1091-
self.send(f"HTTP/1.1 {code} {message}\r\n")
1091+
try:
1092+
self.send(f"HTTP/1.1 {code} {message}\r\n")
1093+
except ReferenceError:
1094+
return
10921095

10931096
def send_error(self, code, message=None):
10941097
if message is None:
@@ -1140,10 +1143,16 @@ def send_error(self, code, message=None):
11401143
return f
11411144

11421145
def send_header(self, key, value):
1143-
self.send(f"{key}: {value}\r\n")
1146+
try:
1147+
self.send(f"{key}: {value}\r\n")
1148+
except ReferenceError:
1149+
return
11441150

11451151
def end_headers(self) -> None:
1146-
self.send("\r\n")
1152+
try:
1153+
self.send("\r\n")
1154+
except ReferenceError:
1155+
return
11471156

11481157
def handle_disconnect(self) -> bool:
11491158
return False
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# ABOUTME: Tests that HTTP response methods handle dead connections gracefully.
2+
# ABOUTME: Verifies send_response, send_header, end_headers don't crash when the client disconnects mid-response.
3+
4+
# This file is part of the dionaea honeypot
5+
#
6+
# SPDX-FileCopyrightText: 2026 Michel
7+
#
8+
# SPDX-License-Identifier: GPL-2.0-or-later
9+
10+
"""
11+
When a client disconnects mid-response, binding.pyx raises ReferenceError
12+
from connection.send() because the underlying C connection object is gone.
13+
The HTTP response methods must handle this gracefully instead of crashing.
14+
"""
15+
16+
import os
17+
import sys
18+
import types
19+
import unittest.mock
20+
21+
# Set up mock modules for dionaea's C extension dependencies before importing http.py
22+
_dionaea_mod = types.ModuleType("dionaea")
23+
_dionaea_mod.__path__ = [os.path.join(os.path.dirname(__file__), '..', '..', '..', 'modules', 'python', 'dionaea')]
24+
_dionaea_mod.ServiceLoader = type("ServiceLoader", (), {})
25+
26+
_core_mod = types.ModuleType("dionaea.core")
27+
28+
29+
class _FakeConnection:
30+
"""Stand-in for binding.pyx connection whose send() raises ReferenceError."""
31+
pass
32+
33+
34+
_core_mod.connection = _FakeConnection
35+
_core_mod.g_dionaea = unittest.mock.MagicMock()
36+
_core_mod.incident = unittest.mock.MagicMock()
37+
38+
_util_mod = types.ModuleType("dionaea.util")
39+
_util_mod.detect_shellshock = lambda *a, **kw: None
40+
41+
_exc_mod = types.ModuleType("dionaea.exception")
42+
_exc_mod.ServiceConfigError = type("ServiceConfigError", (Exception,), {})
43+
44+
sys.modules["dionaea"] = _dionaea_mod
45+
sys.modules["dionaea.core"] = _core_mod
46+
sys.modules["dionaea.util"] = _util_mod
47+
sys.modules["dionaea.exception"] = _exc_mod
48+
49+
# Now we can import httpd
50+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'modules', 'python'))
51+
from dionaea.http import httpd # noqa: E402
52+
53+
54+
def _make_dead_httpd():
55+
"""Create an httpd whose send() raises ReferenceError (dead C connection)."""
56+
h = httpd.__new__(httpd)
57+
h.responses = httpd.responses
58+
59+
def dead_send(data, **kwargs):
60+
raise ReferenceError("the object requested does not exist")
61+
62+
h.send = dead_send
63+
return h
64+
65+
66+
def test_send_response_on_dead_connection():
67+
h = _make_dead_httpd()
68+
h.send_response(404)
69+
70+
71+
def test_send_header_on_dead_connection():
72+
h = _make_dead_httpd()
73+
h.send_header("Content-Type", "text/html")
74+
75+
76+
def test_end_headers_on_dead_connection():
77+
h = _make_dead_httpd()
78+
h.end_headers()

0 commit comments

Comments
 (0)