CVE-2026-103022: Denial of Service via Unhandled Unicode Exception and File Descriptor Leak in Urwid Web Display

Vulnerability ID: CVE-2026-103022
CVSS Score: 5.3
Published: 2026-11-09

A Denial of Service (DoS) vulnerability exists in the web display module (urwid/display/web.py) of the Urwid console user interface library for Python in versions prior to 4.0.7. The handle_short_request() function unconditionally encodes HTTP POST input as ASCII without exception handling and opens a FIFO pipe file descriptor without a guaranteed cleanup block. Submitting non-ASCII characters triggers an unhandled UnicodeEncodeError that terminates CGI request processing and leaks the open file descriptor. Additionally, the screen update polling routine decodes Unix domain socket streams iteratively as UTF-8 without exception handling or deterministic socket closure, enabling uncaught UnicodeDecodeError exceptions and socket leaks.

TL;DR

Urwid versions prior to 4.0.7 contain unhandled Unicode encoding/decoding exceptions and resource leaks in urwid/display/web.py. A remote attacker with a valid session ID can send non-ASCII POST payloads to crash CGI request handlers and exhaust system file descriptors, resulting in a Denial of Service.

Technical Details

  • CVE ID: CVE-2026-103022 (GHSA-cp25-j9x5-p5xm)
  • Primary CWE: CWE-775 (Missing Release of File Descriptor) / CWE-248 (Uncaught Exception)
  • Attack Vector: Network (HTTP POST / CGI Request)
  • CVSS v3.1 Score: 5.3 Medium (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L)
  • EPSS Score: 0.472% (41.15th Percentile)
  • Impact: Denial of Service via CGI Crash and File Descriptor Exhaustion
  • Exploit Status: None (No public PoC or active exploitation)
  • CISA KEV Status: Not Listed

Affected Systems

  • Urwid Console User Interface Library for Python (urwid)
  • urwid: < 4.0.7 (Fixed in: 4.0.7)

Code Analysis

Commit: b79d9a9

Fix unhandled Unicode errors and resource leaks in web display handle_short_request() (PR #1201)

File: urwid/display/web.py @@ -38,6 +38,7 @@ import tempfile import typing from contextlib import suppress +from email.message import Message from urwid.str_util import calc_text_pos, calc_width, move_next_char from urwid.util import StoppingContext, get_encoding @@ -501,6 +502,12 @@ def is_web_request() -> bool: return "REQUEST_METHOD" in os.environ +def _request_charset() -> str: + content_type = Message() + content_type["content-type"] = os.environ.get("CONTENT_TYPE", "") + return content_type.get_content_charset() or "utf-8" + + def handle_short_request() -> bool: """ Handle short requests such as passing keystrokes to the application @@ -541,16 +548,23 @@ def handle_short_request() -> bool: if os.environ.get("HTTP_X_URWID_METHOD", None) == "polling": # this is a screen update request - s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: - s.connect(os.path.join(_prefs.pipe_dir, f"urwid_{urwid_id}.update")) - data = f"Content-type: text/plain\r\n\r\n{s.recv(BUF_SZ).decode('utf-8')}" - while data: - sys.stdout.write(data) - data = s.recv(BUF_SZ).decode("utf-8") + with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s: + s.connect(os.path.join(_prefs.pipe_dir, f"urwid_{urwid_id}.update")) + chunks = [] + while data := s.recv(BUF_SZ): + chunks.append(data) except OSError: sys.stdout.write("Status: 404 Not Found\r\n\r\n") return True + + try: + decoded = b"".join(chunks).decode("utf-8") + except UnicodeDecodeError: + sys.stdout.write("Status: 502 Bad Gateway\r\n\r\n") + return True + + sys.stdout.write(f"Content-type: text/plain; charset=utf-8\r\n\r\n{decoded}") return True # this is a keyboard input request @@ -560,10 +574,17 @@ def handle_short_request() -> bool: sys.stdout.write("Status: 404 Not Found\r\n\r\n") return True - # FIXME: use the correct encoding based on the request - keydata = sys.stdin.read(MAX_READ) - os.write(fd, keydata.encode("ascii")) - os.close(fd) + try: + try: + keydata = sys.stdin.read(MAX_READ) + encoded_keydata = keydata.encode(_request_charset()) + except (LookupError, UnicodeError): + sys.stdout.write("Status: 400 Bad Request\r\n\r\n") + return True + os.write(fd, encoded_keydata) + finally: + with suppress(OSError): + os.close(fd) sys.stdout.write("Content-type: text/plain\r\n\r\n") return True 

Mitigation Strategies

  • Upgrade the Urwid Python library to version 4.0.7 or later to enforce safe charset handling and deterministic file descriptor cleanup.
  • Deploy a Web Application Firewall (WAF) or reverse proxy rule to block HTTP POST bodies containing non-ASCII bytes (>= 0x80) targeting the CGI endpoint if running an unpatched version.
  • Configure persistent CGI/FastCGI worker pools with strict request limits (max-requests) to recycle worker processes and reclaim leaked file descriptors.
  • Enforce authentication in front of the Urwid web display interface to prevent unauthenticated attackers from acquiring valid X-Urwid-ID session tokens.

Remediation Steps:

  1. Identify all Python environments and application dependencies utilizing the urwid library (pip show urwid).
  2. Verify whether the application imports or exposes urwid.display.web or urwid.web_display via a CGI/HTTP server.
  3. Update the dependency specification in requirements.txt or pyproject.toml to urwid>=4.0.7 and redeploy the environment.
  4. Monitor web server error logs for UnicodeEncodeError, UnicodeDecodeError, or OSError: [Errno 24] Too many open files to verify that exceptions no longer occur.

References

Read the full report for CVE-2026-103022 on our website for more details including interactive diagrams and full exploit analysis.