From patchwork Wed Aug 19 11:00:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 95753 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id EAA29C5DF6D for ; Wed, 19 Aug 2026 11:00:34 +0000 (UTC) Received: from alln-iport-1.cisco.com (alln-iport-1.cisco.com [173.37.142.88]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.4426.1787137224641219076 for ; Wed, 19 Aug 2026 04:00:24 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=HscUVVVf; spf=pass (domain: cisco.com, ip: 173.37.142.88, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=7075; q=dns/txt; s=iport01; t=1787137224; x=1788346824; h=from:to:cc:subject:date:message-id:mime-version: content-transfer-encoding; bh=oN2KopdHMKuuUTF/1qbfMqJh5m8SOTEdl/iLSszn7Lw=; b=HscUVVVfmA8Ib9x7gWAqlfH8xRS5fhoLW7GGUR606bhb0nmoUeXiSYsy vRUlePYRRv8USgZM+LEe+f5qqbN2C+bmVEqC64Rxl0gQ36u6XUYVPgb8S 0yc0nc3jSshgmesFUXJMB/Ru818MfeCDqdMSl0ES2eYx6ld3yHUQVfs4y mB/kjqVbQtFxXXApdxKeF2riOZkFaLqO7lA+JiHv3CjU0hj5yaHaoLmBW R2IF3bKaN6XWGOJSWV0UManrIdHeLeVtssFAW6mi36ZQ1kI5tVHC3DNc/ qsa+Ua1p2TNU6hw0JSr4iEzqUqyOshbTBHiPAz3xI5fvAbh46JT5E5RjX w==; X-CSE-ConnectionGUID: xDuMo2qTTmqnTGEPBamh5g== X-CSE-MsgGUID: G/dyTt35TSySfyWjt+63Cg== X-IPAS-Result: A0BLAgDpi4Vq/40QJK1aEwEBgkSCV3ReQ0mEV5EHbJ4egX4PAQEBD0QNBAEBhQWNbQImNAkOAQIEAwIDAQEBAQEBAQEBAQELAQEFAQEBAgEHBYEOE4ZPDYZaAQIBJgQLARgBGwYMLAMBAgMCJgItIxgJgwIBgnQDEQbDQ3p/M4EBgygBgVTbMAELFAEFgQUuhT+DHwGFAl0YAUSEOCcbG4FygRWDaYEFgVwChTuCagSCIoEMgVqBLZA4SIECHANZLAFVEw0KCwcFgWYDNRIqFW4yHYEjPhc1WBsGBYEdgSiENyMZNnqBCV6BKyphARIXgQmCCgKCc4IGAgFJRREKCwsYDUgRLDcUGQQ9AW4HjkkgghoNDgsHZBcTAQogARdkBIEEKR6TNB6SBoE1n1oKKIN2jCGVOhozhASBV5JAklELmH2LJ4JjgU2UORM3hGmBaDyBWXAVO4JnCUoZD44uCguDYIUTxyYnMgIJMgEBBwIHDgMLgWiQAi2BTwEB IronPort-Data: A9a23:IOtiLqkYyn4dhvolNrjlbW/o5gzXJ0RdPkR7XQ2eYbSJt1+Wr1Gzt xIXWDzXM62MZzChKIt+OYm+/UIB6p6DmtNmSgFtpCE1RltH+JHPbTi7wugcHM8zwunrFh8PA xA2M4GYRCwMZiaC4E/raf658SUUOZigHtLUEPTDNj16WThqQSIgjQMLs+Mii+aEu/Dha++2k Y20+ZC31GONgWYubDpEsvPb8XuDgdyr0N8mlg1mDRx0lAe2e0k9VPo3Oay3Jn3kdYhYdsbSb /rD1ryw4lTC9B4rDN6/+p6jGqHdauePVeQmoiM+t5mK2nCulARrukoIHKZ0hXNsttm8t4sZJ OOhGnCHYVxB0qXkwIzxWvTDes10FfUuFLTveRBTvSEPpqHLWyOE/hlgMK05FZ0058NwW3hyy fkBLgwiQyipjue2nb3uH4GAhux7RCXqFIobvnclyXTSCuwrBMifBa7L/tRfmjw3g6iiH96HO JFfMmQpNUqGOkEWUrsUIMpWcOOAhmX/ej5RsnqepLE85C7YywkZPL3FYIOKK4HWHJkJ9qqej k2B83XFHkpHCOeO8jSUzXCwxbGUtiyuDer+E5X9rJaGmma7wXQeDhATX1a3rfS1z0KzRd9bA 0gV4TY1668q+UqmS9PwUxG1rDiDpBF0ZjZLO+Q+7AfIzu/f5ByUQzBYCDVAc9ch8sQxQFTGy 2O0oj8gPhQ32JX9dJ5X3u78Qe+aUcTNEVI/WA== IronPort-HdrOrdr: A9a23:73L026/r9NNZEF1StsBuk+D6I+orL9Y04lQ7vn2ZhyY7TiX+rb HLoB17726QtN9/YhAdcLy7VZVoIkmsl6Kdn7NwAV7KZmCP0wGVxepZg7cKrQeNJ8TWzJ876U 4ZSdkcNPTASX5nkM39/A60V/wkwNWB7eSUoN229QYKcemvAJsQlzuQzW2gYzRLeDU= X-Talos-CUID: 9a23:dGynY25qWrSgtTmE1tss5GIJP8d/a0HhyW7WH2CVMUxgTb3IRgrF X-Talos-MUID: 9a23:U6m4aAWH1n/pUsjq/BCriCBeKJw135+/M04crpca+JffHiMlbg== X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.25,231,1779148800"; d="scan'208";a="817954144" Received: from alln-l-core-04.cisco.com ([173.36.16.141]) by alln-iport-1.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 19 Aug 2026 11:00:22 +0000 Received: from sjc-ads-5245.cisco.com (sjc-ads-5245.cisco.com [10.28.23.9]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by alln-l-core-04.cisco.com (Postfix) with ESMTPS id 0E0BA18000B7B; Wed, 19 Aug 2026 11:00:22 +0000 (GMT) Received: by sjc-ads-5245.cisco.com (Postfix, from userid 1887505) id 9D8B9CCA79B; Wed, 19 Aug 2026 04:00:21 -0700 (PDT) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: xe-linux-external@cisco.com, Hetvi Thakar Subject: [meta-python][wrynose][PATCH 1/6] python3-pyjwt: Fix CVE-2026-48522 Date: Wed, 19 Aug 2026 04:00:11 -0700 Message-Id: <20260819110016.252278-1-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 MIME-Version: 1.0 X-Auto-Response-Suppress: DR, OOF, AutoReply X-Outbound-Client-TLS: ANONYMOUS;sjc-ads-5245.cisco.com [10.28.23.9];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.28.23.9, sjc-ads-5245.cisco.com X-Outbound-Node: alln-l-core-04.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 11:00:34 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/129341 From: Hetvi Thakar This patch applies the upstream 2.13.0 backport for CVE-2026-48522. The upstream fix commit is referenced in [1], and the public CVE advisory is referenced in [2]. [1] https://github.com/jpadilla/pyjwt/commit/95791b1759b8aa4f2203575d344d5c78564cdc81 [2] https://github.com/advisories/GHSA-993g-76c3-p5m4 Signed-off-by: Hetvi Thakar --- .../python/files/CVE-2026-48522.patch | 126 ++++++++++++++++++ .../python/python3-pyjwt_2.12.1.bb | 2 + 2 files changed, 128 insertions(+) create mode 100644 meta-python/recipes-devtools/python/files/CVE-2026-48522.patch diff --git a/meta-python/recipes-devtools/python/files/CVE-2026-48522.patch b/meta-python/recipes-devtools/python/files/CVE-2026-48522.patch new file mode 100644 index 0000000000..c1e6466d35 --- /dev/null +++ b/meta-python/recipes-devtools/python/files/CVE-2026-48522.patch @@ -0,0 +1,126 @@ +From f369d32169410b17717d2a9c00d5bf3ac655c85d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Thu, 21 May 2026 14:11:10 -0400 +Subject: [PATCH] Bundle security fixes and hardening into 2.13.0 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Security: +- `HMACAlgorithm.prepare_key` rejects JWK JSON documents passed as raw + HMAC secrets to close an algorithm-confusion gap not covered by the + existing PEM/SSH guard. Reported by @aradona91 in GHSA-xgmm-8j9v-c9wx. +- Bind the JWT header `alg` to `PyJWK.algorithm_name` during verification + so the caller's `algorithms` allow-list cannot be bypassed when decoding + with a `PyJWK` / `PyJWKClient` key. Reported by @sushi-gif in + GHSA-jq35-7prp-9v3f. +- Skip the unconditional base64 decode of the compact-form payload + segment when `b64=false` is set, and require that segment to be empty + (RFC 7515 Appendix F detached form). Closes an unauthenticated DoS + amplifier. Reported by @thesmartshadow in GHSA-w7vc-732c-9m39. +- `PyJWKClient` rejects any URI whose scheme is not `http` or `https` so + attacker-influenced URIs cannot read local files or reach unintended + schemes via urllib's default `file://` / `ftp://` / `data:` handlers. + Reported by @KEIJOT in GHSA-993g-76c3-p5m4. +- Preserve the cached JWK Set on fetch errors in `PyJWKClient.fetch_data`. + The previous `finally`-block `put(None)` pattern cleared the cache on + any transient outage. Reported by @eddieran in GHSA-fhv5-28vv-h8m8. + +Fixes: +- Reject empty HMAC keys outright in `HMACAlgorithm.prepare_key` with + `InvalidKeyError` instead of accepting them with only a warning. + Hardening prompted by reports from @SnailSploit and @spartan8806. +- Forward per-call `options` (including `enforce_minimum_key_length`) + from `PyJWT.decode` through to `PyJWS._verify_signature`. Thanks to + @WLUB. +- RFC 7797 §3 compliance for `b64=false`: encoder auto-adds `"b64"` to + `crit`; decoder rejects tokens that set `b64=false` without listing + it in `crit`. Thanks to @MachineLearning-Nerd. + +CVE: CVE-2026-48522 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/95791b1759b8aa4f2203575d344d5c78564cdc81] + +Backport Changes: +- Split out the PyJWKClient URI-scheme validation because the upstream + commit bundles multiple CVEs. +- Omitted CHANGELOG.rst because it conflicted and is release documentation. +- Omitted the 2.13.0 version bump and other bundled fixes; applicable CVE + fixes are carried in separate patches. + +(cherry picked from commit 95791b1759b8aa4f2203575d344d5c78564cdc81) +Signed-off-by: Hetvi Thakar +--- + jwt/jwks_client.py | 11 +++++++++++ + tests/test_jwks_client.py | 30 ++++++++++++++++++++++++++++++ + 2 files changed, 41 insertions(+) + +diff --git a/jwt/jwks_client.py b/jwt/jwks_client.py +index b81e8f4..8d1b0c4 100644 +--- a/jwt/jwks_client.py ++++ b/jwt/jwks_client.py +@@ -6,6 +6,7 @@ from functools import lru_cache + from ssl import SSLContext + from typing import Any + from urllib.error import HTTPError, URLError ++from urllib.parse import urlparse + + from .api_jwk import PyJWK, PyJWKSet + from .api_jwt import decode_complete as decode_token +@@ -69,6 +70,16 @@ class PyJWKClient: + """ + if headers is None: + headers = {} ++ # urllib's default OpenerDirector also handles file://, ftp://, and ++ # data: URIs. Reject anything that isn't http(s) eagerly so a caller ++ # passing an attacker-influenced URL (e.g. taken from a `jku` token ++ # header) can't read local files or reach other unintended schemes. ++ scheme = urlparse(uri).scheme.lower() ++ if scheme not in ("http", "https"): ++ raise PyJWKClientError( ++ f"Invalid JWKS URI scheme {scheme!r}: only 'http' and 'https' " ++ f"are supported." ++ ) + self.uri = uri + self.jwk_set_cache: JWKSetCache | None = None + self.headers = headers +diff --git a/tests/test_jwks_client.py b/tests/test_jwks_client.py +index ceee672..d6793cc 100644 +--- a/tests/test_jwks_client.py ++++ b/tests/test_jwks_client.py +@@ -344,6 +344,36 @@ class TestPyJWKClient: + jwks_client = PyJWKClient(url, lifespan=-1) + assert jwks_client is None + ++ @pytest.mark.parametrize( ++ "uri", ++ [ ++ "file:///etc/passwd", ++ "ftp://example.org/keys.json", ++ 'data:application/json,{"keys":[]}', ++ "/etc/passwd", # urlparse gives scheme="" — also rejected ++ "ldap://internal.test/jwks", ++ ], ++ ) ++ def test_pyjwkclient_rejects_non_http_schemes(self, uri: str) -> None: ++ # urllib's default OpenerDirector handles file://, ftp://, and data: ++ # URIs. PyJWKClient must reject these so callers can't be tricked ++ # into reading attacker-controlled local files or other unintended ++ # schemes via a manipulated URI. ++ with pytest.raises(PyJWKClientError, match="Invalid JWKS URI scheme"): ++ PyJWKClient(uri) ++ ++ @pytest.mark.parametrize( ++ "uri", ++ [ ++ "http://localhost/jwks.json", ++ "https://example.test/jwks.json", ++ "HTTPS://Example.Test/jwks.json", # case-insensitive ++ ], ++ ) ++ def test_pyjwkclient_accepts_http_https_schemes(self, uri: str) -> None: ++ # Construction succeeds; no fetch is made until get_jwk_set(). ++ PyJWKClient(uri) ++ + def test_get_jwt_set_timeout(self) -> None: + url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" + jwks_client = PyJWKClient(url, timeout=5) diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb index eb445f9c91..ed7a280ee5 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb @@ -5,6 +5,8 @@ HOMEPAGE = "https://github.com/jpadilla/pyjwt" LICENSE = "MIT" LIC_FILES_CHKSUM = "file://LICENSE;md5=e4b56d2c9973d8cf54655555be06e551" +SRC_URI += "file://CVE-2026-48522.patch" + SRC_URI[sha256sum] = "c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b" PYPI_PACKAGE = "pyjwt" From patchwork Wed Aug 19 11:00:12 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 95755 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2CF68C5DF87 for ; Wed, 19 Aug 2026 11:00:35 +0000 (UTC) Received: from alln-iport-4.cisco.com (alln-iport-4.cisco.com [173.37.142.91]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.4431.1787137226501769204 for ; Wed, 19 Aug 2026 04:00:26 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=LkTISsv6; spf=pass (domain: cisco.com, ip: 173.37.142.91, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=6728; q=dns/txt; s=iport01; t=1787137226; x=1788346826; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=1xYXyjOp5FdViteZDDgpBWpK09q26mHP7mRNk2IHpqs=; b=LkTISsv69wILI9gPPXfRbS8afK6no32AVQxSehUbK6P2QdkWFhNdoqmW i1V4oqAEBRj9HXiR9a5SZER1qHl5YL1o/+uUsOhs89e8ZXEm9dZsr8dED IC3s/vDpDsLx2OIAn96weZ5E8ME71gFKmbNBfzZMf27Q2lQYN/MzNMZ35 +6typ5Xk6g0ZUXziH4Mui/R4XiLw2iaIrdO3v8cG+NQwbIAUdfacInaoS 7vTTAOV3wEOub5w2BdeugB4tcnODdIqlpRzQjdzBO9XvbNiCbkI42SvoB j7xMfTRhdtYLoBsrzT+hpT8VQpCLNAuhdmt6Pu87SkdSanKq4I7fi+ikL A==; X-CSE-ConnectionGUID: nso59mYyQji4ztJ5+5zSew== X-CSE-MsgGUID: tlgfKWZ9SmezmKoEfbb8OQ== X-IPAS-Result: A0BNAgBni4Vq/4sQJK1SCIJZgld0XkNJhFeRB2wDnhuBfg8BAQEPRA0EAQGFBQKNawImNAkOAQIEAwIDAQEBAQEBAQEBAQELAQEFAQEBAgEHBYEOE4ZPDYZaAQIBAyMECwEYARsGDBAcAwECAwImAgIrIwgQCYMCAYJ0AxHDRnp/M4EBgygBgVTbMAELFAEFgQUuhT+DHwGFAl0YAUSEOCcbG4FygRWDaYEFgVwCgScShAKCagSCInoSgVqBLZA4SIECHANZLAFVEw0KCwcFgWYDNRIqFW4yHYEjPhc1WBsGBYEdgSiENyMZNnqBCV6BKyphARIXgQmCCgKCc4IGAgFJRREKCwsYDUgRLDcUGQQ9AW4HjkkgghoNDhIBYyoBCiABewQhYykekzSSJIwAlQ8KKIN2jCGVOhozhASUF5JRC5h9iyeCY5YGEzeEaYFoPIFZcBU7gmcJShkPji4KC4NgzDknMgIJMgEBBwIHDgMLgWiQAAImB4FPAQE IronPort-Data: A9a23:mI5aQ6528CAeg1TK6cipHAxRtG7GchMFZxGqfqrLsTDasY5as4F+v jAaCmCHPPjZZTP9f48kaY628RtV68WAxoJhQFRur302Zn8b8sCt6fZ1gavT04J+CuWZESqLO u1HMoGowPgcFyGa/lH2dOC98RGQ7InQLpLkEunIJyttcgFtTSYlmHpLlvUw6mJSqYDR7zil5 5Wo/qUzBHf/g2QqajJNuvrawP9SlK2aVA0w7wRWic9j5Dcyp1FNZLoDKKe4KWfPQ4U8NoaSW +bZwbilyXjS9hErB8nNuu6TnpoiG+O60aCm0xK6aoD66vRwjnVaPpUTaJLwXXxqZwChxLid/ jniWauYEm/FNoWU8AgUvoIx/ytWZcWq85efSZSzXFD6I0DuKxPRL/tS4E4eO5Yfp7dyMHB39 /1EOiwvM1Peldm73+fuIgVsrpxLwMjDNYcbvDRkiDreF/tjGcqFSKTR7tge1zA17ixMNa+BP IxCN3w2MlKZOEwn1lQ/UPrSmM+omnn2cDRCgFmUvqEwpWPUyWSd1ZC9aYCLKoTRGp09ckCwu mbJ8UfGMEAhMdGNzBmC33ymjL/opHauMG4VPPjinhJwu3WU3mEVBRgcWFe3rPX8hkOgVtZ3L 00P5jFoqrA/8kGuRNTxUxC05nmesXYht8F4Guk+7kSJj6HT+QvcXjJCRT9aY9tgv8gzLdA36 mK0cxrSLWQHmNWopbi1rd94cRva1fApEFI/ IronPort-HdrOrdr: A9a23:pbO6Tq4vmJgrH/F+vQPXwBbXdLJyesId70hD6qm+c3Nom6uj5q aTdZUgpHjJYVkqOU3I9ersBEDEewK/yXcX2/h0AV7dZmnbUQKTRekIh7cKgQeQfhEWndQy6U 4PScRD4fTLfCFHZL7BkWqFOudl5sWb+6a1guqb5XJsQQZ2L5xE1W5Ce36m+okcfng9OXL/f6 DsnfZ6mw== X-Talos-CUID: 9a23:yW9Y92FSN/KqtXDCqmJh70lJS8UUY0TfzSvCP23lSjt7c7CKHAo= X-Talos-MUID: 9a23:xmg1iQXyrNEHaGDq/CPoiz9BC/U52KGjGkUulrgIscPUEhUlbg== X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.25,231,1779148800"; d="scan'208";a="812680370" Received: from alln-l-core-02.cisco.com ([173.36.16.139]) by alln-iport-4.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 19 Aug 2026 11:00:25 +0000 Received: from sjc-ads-5245.cisco.com (sjc-ads-5245.cisco.com [10.28.23.9]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by alln-l-core-02.cisco.com (Postfix) with ESMTPS id 422DB180001BD; Wed, 19 Aug 2026 11:00:25 +0000 (GMT) Received: by sjc-ads-5245.cisco.com (Postfix, from userid 1887505) id D4E35CCA79B; Wed, 19 Aug 2026 04:00:24 -0700 (PDT) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: xe-linux-external@cisco.com, Hetvi Thakar Subject: [meta-python][wrynose][PATCH 2/6] python3-pyjwt: Fix CVE-2026-48523 Date: Wed, 19 Aug 2026 04:00:12 -0700 Message-Id: <20260819110016.252278-2-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20260819110016.252278-1-hthakar@cisco.com> References: <20260819110016.252278-1-hthakar@cisco.com> MIME-Version: 1.0 X-Auto-Response-Suppress: DR, OOF, AutoReply X-Outbound-Client-TLS: ANONYMOUS;sjc-ads-5245.cisco.com [10.28.23.9];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.28.23.9, sjc-ads-5245.cisco.com X-Outbound-Node: alln-l-core-02.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 11:00:35 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/129342 From: Hetvi Thakar This patch applies the upstream 2.13.0 backport for CVE-2026-48523. The upstream fix commit is referenced in [1], and the public CVE advisory is referenced in [2]. [1] https://github.com/jpadilla/pyjwt/commit/95791b1759b8aa4f2203575d344d5c78564cdc81 [2] https://github.com/advisories/GHSA-jq35-7prp-9v3f Signed-off-by: Hetvi Thakar --- .../python/files/CVE-2026-48523.patch | 115 ++++++++++++++++++ .../python/python3-pyjwt_2.12.1.bb | 4 +- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 meta-python/recipes-devtools/python/files/CVE-2026-48523.patch diff --git a/meta-python/recipes-devtools/python/files/CVE-2026-48523.patch b/meta-python/recipes-devtools/python/files/CVE-2026-48523.patch new file mode 100644 index 0000000000..f272338979 --- /dev/null +++ b/meta-python/recipes-devtools/python/files/CVE-2026-48523.patch @@ -0,0 +1,115 @@ +From 6590add3a8d18098b107fe446ff256ae92e7238f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Thu, 21 May 2026 14:11:10 -0400 +Subject: [PATCH] Bundle security fixes and hardening into 2.13.0 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Security: +- `HMACAlgorithm.prepare_key` rejects JWK JSON documents passed as raw + HMAC secrets to close an algorithm-confusion gap not covered by the + existing PEM/SSH guard. Reported by @aradona91 in GHSA-xgmm-8j9v-c9wx. +- Bind the JWT header `alg` to `PyJWK.algorithm_name` during verification + so the caller's `algorithms` allow-list cannot be bypassed when decoding + with a `PyJWK` / `PyJWKClient` key. Reported by @sushi-gif in + GHSA-jq35-7prp-9v3f. +- Skip the unconditional base64 decode of the compact-form payload + segment when `b64=false` is set, and require that segment to be empty + (RFC 7515 Appendix F detached form). Closes an unauthenticated DoS + amplifier. Reported by @thesmartshadow in GHSA-w7vc-732c-9m39. +- `PyJWKClient` rejects any URI whose scheme is not `http` or `https` so + attacker-influenced URIs cannot read local files or reach unintended + schemes via urllib's default `file://` / `ftp://` / `data:` handlers. + Reported by @KEIJOT in GHSA-993g-76c3-p5m4. +- Preserve the cached JWK Set on fetch errors in `PyJWKClient.fetch_data`. + The previous `finally`-block `put(None)` pattern cleared the cache on + any transient outage. Reported by @eddieran in GHSA-fhv5-28vv-h8m8. + +Fixes: +- Reject empty HMAC keys outright in `HMACAlgorithm.prepare_key` with + `InvalidKeyError` instead of accepting them with only a warning. + Hardening prompted by reports from @SnailSploit and @spartan8806. +- Forward per-call `options` (including `enforce_minimum_key_length`) + from `PyJWT.decode` through to `PyJWS._verify_signature`. Thanks to + @WLUB. +- RFC 7797 §3 compliance for `b64=false`: encoder auto-adds `"b64"` to + `crit`; decoder rejects tokens that set `b64=false` without listing + it in `crit`. Thanks to @MachineLearning-Nerd. + +CVE: CVE-2026-48523 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/95791b1759b8aa4f2203575d344d5c78564cdc81] + +Backport Changes: +- Split out the PyJWK algorithm-binding validation because the upstream + commit bundles multiple CVEs. +- Omitted CHANGELOG.rst because it conflicted and is release documentation. +- Omitted the 2.13.0 version bump and other bundled fixes; applicable CVE + fixes are carried in separate patches. + +(cherry picked from commit 95791b1759b8aa4f2203575d344d5c78564cdc81) +Signed-off-by: Hetvi Thakar +--- + jwt/api_jws.py | 10 ++++++++++ + tests/test_api_jws.py | 27 +++++++++++++++++++++++++++ + 2 files changed, 37 insertions(+) + +diff --git a/jwt/api_jws.py b/jwt/api_jws.py +index 0ab7e4b..91d2ae3 100644 +--- a/jwt/api_jws.py ++++ b/jwt/api_jws.py +@@ -348,6 +348,16 @@ class PyJWS: + raise InvalidAlgorithmError("The specified alg value is not allowed") + + if isinstance(key, PyJWK): ++ # The PyJWK has a fixed algorithm bound at construction time. ++ # Verification must use that algorithm, not whatever the token ++ # header advertises, otherwise the caller's allow-list check ++ # above degenerates into a string compare with no behavioural ++ # effect on which algorithm actually verifies the signature. ++ if alg != key.algorithm_name: ++ raise InvalidAlgorithmError( ++ f"Token algorithm {alg!r} does not match the key's " ++ f"algorithm {key.algorithm_name!r}" ++ ) + alg_obj = key.Algorithm + prepared_key = key.key + else: +diff --git a/tests/test_api_jws.py b/tests/test_api_jws.py +index 9f7edc0..0715b9e 100644 +--- a/tests/test_api_jws.py ++++ b/tests/test_api_jws.py +@@ -397,6 +397,33 @@ class TestJWS: + with pytest.raises(InvalidAlgorithmError): + jws.decode(example_jws, jwk) + ++ def test_decodes_with_jwk_rejects_header_alg_outside_jwk_alg( ++ self, jws: PyJWS ++ ) -> None: ++ # Token header says HS256 and the caller's allow-list also accepts ++ # HS256, but the PyJWK is bound to HS512. Even though the allow-list ++ # would pass, verification must be locked to the PyJWK's algorithm ++ # rather than the header's — otherwise an attacker who controls a ++ # registered key can advertise a disallowed algorithm in the header ++ # and have it accepted. ++ jwk = PyJWK( ++ { ++ "kty": "oct", ++ "alg": "HS512", ++ "k": "c2VjcmV0", # "secret" ++ } ++ ) ++ example_jws = ( ++ b"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." ++ b"aGVsbG8gd29ybGQ." ++ b"gEW0pdU4kxPthjtehYdhxB9mMOGajt1xCKlGGXDJ8PM" ++ ) ++ ++ with pytest.raises( ++ InvalidAlgorithmError, match="does not match the key's algorithm" ++ ): ++ jws.decode(example_jws, jwk, algorithms=["HS256", "HS512"]) ++ + # 'Control' Elliptic Curve jws created by another library. + # Used to test for regressions that could affect both + # encoding / decoding operations equally (causing tests diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb index ed7a280ee5..e67c7bae2f 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb @@ -5,7 +5,9 @@ HOMEPAGE = "https://github.com/jpadilla/pyjwt" LICENSE = "MIT" LIC_FILES_CHKSUM = "file://LICENSE;md5=e4b56d2c9973d8cf54655555be06e551" -SRC_URI += "file://CVE-2026-48522.patch" +SRC_URI += "file://CVE-2026-48522.patch \ + file://CVE-2026-48523.patch \ + " SRC_URI[sha256sum] = "c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b" From patchwork Wed Aug 19 11:00:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 95757 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 62119C5DF88 for ; Wed, 19 Aug 2026 11:00:35 +0000 (UTC) Received: from alln-iport-2.cisco.com (alln-iport-2.cisco.com [173.37.142.89]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.4433.1787137228449757623 for ; Wed, 19 Aug 2026 04:00:28 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=lg5ZYzKs; spf=pass (domain: cisco.com, ip: 173.37.142.89, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=7864; q=dns/txt; s=iport01; t=1787137228; x=1788346828; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=vl7De/mRp26qIQqLtwko0B49VqNBxYA7pW2lTCLJ+Fo=; b=lg5ZYzKsaCNwfQaBVioWkt2ZOsj2QnUlQRl2BgWF5gv3/1R5Zqn9sKHp hJFF6LKvqfiERFKRtAJ4GH68U2Ldm2tp1p4lCFftDWCe7OnxW2cUwQvBr HVcwetvEG08VI8UlNIceJvuHe4/krLTRtEpAxxJnceSVvlNyVQTPHcxgL kjYzmiEHsV2W3M7e9DYsBOuY+o694/VuPb5CHNKkKP/TtjxFfw45ZxrSK +wsqyaKiRISZDDbeZYBiGZqqUc2e3TU23fj/M/80sMmpWjrLgUizv2TRx F+fVr9KvLPoTKZ/TzlCaxgnZk14tiiz13QkgXrsEs6/UD8ySRBpxb2ADR A==; X-CSE-ConnectionGUID: aR78gCo8RNqdaWx+PglJUQ== X-CSE-MsgGUID: 05qdfajBRaukb1n8Xu5W4g== X-IPAS-Result: A0BOAgDpi4Vq/5AQJK1aEwEBgkSCV3ReQ0mEV5EHbAOBE50IgX4PAQEBD0QNBAEBhQUCjWsCJjQJDgECBAMCAwEBAQEBAQEBAQEBCwEBBQEBAQIBBwWBDhOGTw2GWgECAQMjBAsBGAEbBgwQHAMBAgMCJgICKyMIEAmDAgGCdAMRBsNDen8zgQGDKAGBVNswAQsUAQWBBS6FP4MfAYUCXRgBRIQ4JxsbgXKBFYJzdoEFgVwChTuCagSCIoEMgVqBLZA4SIECHANZLAFVEw0KCwcFgWYDNRIqFW4yHYEjPhc1WBsGBYEdgSiENyMZNnqBCV6BKyphARIXgQmCCgKCc4IGAgFJRREKCwsYDUgRLDcUGQQ9AW4HjkkgghoNDhJkFwkKAQogAUgzBIEEKR6TNJIkoQ8KKIN2jCGVOhozhASUF5JRC5h9glmLMYFNlDMGEzeEaYFoPIFZcBU7gmcJShkPji4KC4NgzDknMgIJMgEBBwIHDgMLgWiRfgEB IronPort-Data: A9a23:dUGnG63nZrcikFH6IPbD5YVwkn2cJEfYwER7XKvMYLTBsI5bp2NWx zBJXmmGOv6JamPzedkgaY7io0IBu5bVnYIwQAJr3Hw8FHgiRegpqji6wuYcGwvIc6UvmWo+t 512huHodZ5yFjmH4E/xbtANlFEkvYmQXL3wFeXYDS54QA5gWU8JhAlq8wIDqtYAbeORXUXX5 Lsen+WFYAX7g24tYzpNg06+gEoHUMra6WtwUmMWPZinjHeG/1EJAZQWI72GLneQauF8Au6gS u/f+6qy92Xf8g1FIovNfmHTKxBirhb6ZGBiu1IOM0SQqkEqSh8ajs7XAMEhhXJ/0F1lqTzeJ OJl7vRcQS9xVkHFdX90vxNwS0mSNoUekFPLzOTWXcG7lyX7n3XQL/pGBXtpbYIz/bZNJ3hC/ MMIawgCajK+vrfjqF67YrEEasULJc3vOsYb/3pn1zycValgSpHYSKKM7thdtNsyrpkRRrCFO YxAN3w2MEiojx5nYj/7DLo+gOehhXDlWzZZs1mS46Ew5gA/ySQhiOm8aYCPIYPiqcN9xhuBq mzP/U7DCwwZPoWZmQqYr03wibqa9c/8cMdIfFGizdZtmFCVy2kZBREaWFf+rfSnh0qWX9NEN 1dS/TIjq6U3/kGnQtTxGRqirxa5UgU0Ut5UFag+rQqK0KeRu1nfDWkfRTkHY9sj3CMreQEXO payt4uBLVRSXHe9EhpxKp/8QeuOBBUo IronPort-HdrOrdr: A9a23:9k83L6AA27QVN5zlHely55DYdb4zR+YMi2TDGXofdfUzSL3/qy nOpoV96faaslcssR0b9OxofZPwI080lqQFhbX5Q43DYOCOggLBR+tfBMnZsljd8kbFmNK1u5 0NT0FWMqyIMbEDt7eY3CCIV/A93dKA7Kekwc3az3trUEVWTpsI1XYANu5eeXcGPjWvwvECZe Gh2vY= X-Talos-CUID: 9a23:CWuvcGNDQYaW1+5DURM4pEE/HZAfVGTRxUjAYHe3DURUcejA X-Talos-MUID: 9a23:jZmLvQg73butCCrEXSG048MpbJpx76qnCmo3mIgFsZCCZRxtZgjBg2Hi X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.25,231,1779148800"; d="scan'208";a="809912237" Received: from alln-l-core-07.cisco.com ([173.36.16.144]) by alln-iport-2.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 19 Aug 2026 11:00:27 +0000 Received: from sjc-ads-5245.cisco.com (sjc-ads-5245.cisco.com [10.28.23.9]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by alln-l-core-07.cisco.com (Postfix) with ESMTPS id 3BD83180004FB; Wed, 19 Aug 2026 11:00:27 +0000 (GMT) Received: by sjc-ads-5245.cisco.com (Postfix, from userid 1887505) id CEFCFCCA79B; Wed, 19 Aug 2026 04:00:26 -0700 (PDT) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: xe-linux-external@cisco.com, Hetvi Thakar Subject: [meta-python][wrynose][PATCH 3/6] python3-pyjwt: Fix CVE-2026-48524 Date: Wed, 19 Aug 2026 04:00:13 -0700 Message-Id: <20260819110016.252278-3-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20260819110016.252278-1-hthakar@cisco.com> References: <20260819110016.252278-1-hthakar@cisco.com> MIME-Version: 1.0 X-Auto-Response-Suppress: DR, OOF, AutoReply X-Outbound-Client-TLS: ANONYMOUS;sjc-ads-5245.cisco.com [10.28.23.9];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.28.23.9, sjc-ads-5245.cisco.com X-Outbound-Node: alln-l-core-07.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 11:00:35 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/129343 From: Hetvi Thakar This patch applies the upstream 2.13.0 backport for CVE-2026-48524. The upstream fix commit is referenced in [1], and the public CVE advisory is referenced in [2]. [1] https://github.com/jpadilla/pyjwt/commit/95791b1759b8aa4f2203575d344d5c78564cdc81 [2] https://github.com/advisories/GHSA-fhv5-28vv-h8m8 Signed-off-by: Hetvi Thakar --- .../python/files/CVE-2026-48524.patch | 125 ++++++++++++++++++ .../python/python3-pyjwt_2.12.1.bb | 1 + 2 files changed, 126 insertions(+) create mode 100644 meta-python/recipes-devtools/python/files/CVE-2026-48524.patch diff --git a/meta-python/recipes-devtools/python/files/CVE-2026-48524.patch b/meta-python/recipes-devtools/python/files/CVE-2026-48524.patch new file mode 100644 index 0000000000..d583ea1275 --- /dev/null +++ b/meta-python/recipes-devtools/python/files/CVE-2026-48524.patch @@ -0,0 +1,125 @@ +From 9d971fad56d6571a3bfc037ca21e3be694b9cabd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Thu, 21 May 2026 14:11:10 -0400 +Subject: [PATCH] Bundle security fixes and hardening into 2.13.0 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Security: +- `HMACAlgorithm.prepare_key` rejects JWK JSON documents passed as raw + HMAC secrets to close an algorithm-confusion gap not covered by the + existing PEM/SSH guard. Reported by @aradona91 in GHSA-xgmm-8j9v-c9wx. +- Bind the JWT header `alg` to `PyJWK.algorithm_name` during verification + so the caller's `algorithms` allow-list cannot be bypassed when decoding + with a `PyJWK` / `PyJWKClient` key. Reported by @sushi-gif in + GHSA-jq35-7prp-9v3f. +- Skip the unconditional base64 decode of the compact-form payload + segment when `b64=false` is set, and require that segment to be empty + (RFC 7515 Appendix F detached form). Closes an unauthenticated DoS + amplifier. Reported by @thesmartshadow in GHSA-w7vc-732c-9m39. +- `PyJWKClient` rejects any URI whose scheme is not `http` or `https` so + attacker-influenced URIs cannot read local files or reach unintended + schemes via urllib's default `file://` / `ftp://` / `data:` handlers. + Reported by @KEIJOT in GHSA-993g-76c3-p5m4. +- Preserve the cached JWK Set on fetch errors in `PyJWKClient.fetch_data`. + The previous `finally`-block `put(None)` pattern cleared the cache on + any transient outage. Reported by @eddieran in GHSA-fhv5-28vv-h8m8. + +Fixes: +- Reject empty HMAC keys outright in `HMACAlgorithm.prepare_key` with + `InvalidKeyError` instead of accepting them with only a warning. + Hardening prompted by reports from @SnailSploit and @spartan8806. +- Forward per-call `options` (including `enforce_minimum_key_length`) + from `PyJWT.decode` through to `PyJWS._verify_signature`. Thanks to + @WLUB. +- RFC 7797 §3 compliance for `b64=false`: encoder auto-adds `"b64"` to + `crit`; decoder rejects tokens that set `b64=false` without listing + it in `crit`. Thanks to @MachineLearning-Nerd. + +CVE: CVE-2026-48524 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/95791b1759b8aa4f2203575d344d5c78564cdc81] + +Backport Changes: +- Split out the cached-JWK-set preservation fix because the upstream + commit bundles multiple CVEs. +- Omitted CHANGELOG.rst because it conflicted and is release documentation. +- Omitted the 2.13.0 version bump and other bundled fixes; applicable CVE + fixes are carried in separate patches. + +(cherry picked from commit 95791b1759b8aa4f2203575d344d5c78564cdc81) +Signed-off-by: Hetvi Thakar +--- + jwt/jwks_client.py | 14 ++++++++------ + tests/test_jwks_client.py | 16 ++++++++++++++-- + 2 files changed, 22 insertions(+), 8 deletions(-) + +diff --git a/jwt/jwks_client.py b/jwt/jwks_client.py +index 8d1b0c4..41f333a 100644 +--- a/jwt/jwks_client.py ++++ b/jwt/jwks_client.py +@@ -113,7 +113,6 @@ class PyJWKClient: + :returns: The parsed JWK Set as a dictionary. + :raises PyJWKClientConnectionError: If the HTTP request fails. + """ +- jwk_set: Any = None + try: + r = urllib.request.Request(url=self.uri, headers=self.headers) + with urllib.request.urlopen( +@@ -126,11 +125,14 @@ class PyJWKClient: + raise PyJWKClientConnectionError( + f'Fail to fetch data from the url, err: "{e}"' + ) from e +- else: +- return jwk_set +- finally: +- if self.jwk_set_cache is not None: +- self.jwk_set_cache.put(jwk_set) ++ ++ # Only update the cache on a successful fetch. Writing in a ++ # `finally` block with `jwk_set=None` on error clears any ++ # previously-cached JWKS, turning a transient outage into a cache ++ # wipe that breaks legitimate auth. ++ if self.jwk_set_cache is not None: ++ self.jwk_set_cache.put(jwk_set) ++ return jwk_set + + def get_jwk_set(self, refresh: bool = False) -> PyJWKSet: + """Return the JWK Set, using the cache when available. +diff --git a/tests/test_jwks_client.py b/tests/test_jwks_client.py +index d6793cc..da68664 100644 +--- a/tests/test_jwks_client.py ++++ b/tests/test_jwks_client.py +@@ -288,18 +288,30 @@ class TestPyJWKClient: + + assert repeated_call.call_count == 1 + +- def test_get_jwt_set_failed_request_should_clear_cache(self) -> None: ++ def test_get_jwt_set_failed_refresh_preserves_cached_jwks(self) -> None: ++ # Regression: a transient fetch failure used to clear the cache via ++ # the previous `finally: put(jwk_set=None)` pattern, turning one bad ++ # request from the JWKS endpoint into application-wide auth failure. ++ # The cache must survive. + url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" + + jwks_client = PyJWKClient(url) + with mocked_success_response(RESPONSE_DATA_WITH_MATCHING_KID): + jwks_client.get_jwk_set() + ++ assert jwks_client.jwk_set_cache is not None ++ assert jwks_client.jwk_set_cache.get() is not None ++ + with pytest.raises(PyJWKClientError): + with mocked_failed_response(): + jwks_client.get_jwk_set(refresh=True) + +- assert jwks_client.jwk_set_cache is None ++ cached = jwks_client.jwk_set_cache.get() ++ assert cached is not None ++ # Subsequent reads still serve from cache without another fetch. ++ with mocked_success_response(RESPONSE_DATA_WITH_MATCHING_KID) as call: ++ jwks_client.get_jwk_set() ++ assert call.call_count == 0 + + def test_failed_request_should_raise_connection_error(self) -> None: + token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA" diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb index e67c7bae2f..ff3eb8814d 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb @@ -7,6 +7,7 @@ LIC_FILES_CHKSUM = "file://LICENSE;md5=e4b56d2c9973d8cf54655555be06e551" SRC_URI += "file://CVE-2026-48522.patch \ file://CVE-2026-48523.patch \ + file://CVE-2026-48524.patch \ " SRC_URI[sha256sum] = "c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b" From patchwork Wed Aug 19 11:00:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 95756 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id B0C12C5DF89 for ; Wed, 19 Aug 2026 11:00:35 +0000 (UTC) Received: from alln-iport-7.cisco.com (alln-iport-7.cisco.com [173.37.142.94]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.4434.1787137229715356396 for ; Wed, 19 Aug 2026 04:00:29 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=hhaLqr1R; spf=pass (domain: cisco.com, ip: 173.37.142.94, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=7938; q=dns/txt; s=iport01; t=1787137229; x=1788346829; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=7W97qm/+img5ntf3UCFpjXT3rIn50JVpGVShZbfMuXM=; b=hhaLqr1R+xFvR9nM87PkgmsPjUh2zWiQW2sRM8CH9OHqfVcMbxYSmqaR XziltoEE+4A/WQqjXAVl8GrBo1iNSZLuyiclD8iFH9zucrDxJz/eAuY5F kiN4sqeeL2DA7RTSnltmdkDobD6MkIJ6NBO+PVkxenNr7+/N5BVxjk4wA oSg84AML/vp3DTNE0/eJ9WV/lXMHKpFthA6o8dxkRDA/+m/6j5bJbnMtL dS0b1LtJTSMUMOBztBdIgIsCLRvTQv489RH2SxxQnBWjSDs5PAHBFnH8p Vp4D/74T65Uatwqt2v0su+Eq7Ob2G0VqU439GJS5emvaaPVNGzMXXk8Ug A==; X-CSE-ConnectionGUID: G0JrjoyuSGKEFcMg02IfDg== X-CSE-MsgGUID: BBKz6gF9TXS/voadaWVUZw== X-IPAS-Result: A0BMAgDpi4Vq/5QQJK1aglmCV3ReQ0mEV5EHbAOeG4F+DwEBAQ9EDQQBAYUFAo1rAiY0CQ4BAgQDAgMBAQEBAQEBAQEBAQsBAQUBAQECAQcFgQ4Thk8NhloBAgEDIwQLARgBGwYMEBwDAQIDAiYCAisjCBAJgwIBgnQDEcNJen8zgQGDKAGBVNswAQsUAQWBBS6FP4MfAYUCXRgBRIQ4JxsbgXKBFYNpgQWBXAKFO4JqBIIigQyBWoEtiE2Ha0iBAhwDWSwBVRMNCgsHBYFmAzUSKhVuMh2BIz4XNVgbBgWBHYEohDcjGTZ6gQlegSsqYQESF4EJggoCgnOCBgIBSUURCgsLGA1IESw3FBkEPQFuB45JIIIaDQ4SZBcTAQogARdkBIEEKR4tkm0aHpIGgTWfWgoog3aMIZU6GjOEBJQXklELmH2OCpYGEzeEaYFoPIFZcBU7gmcJShkPjioECguDYMw5JzICCTIBAQcCBw4DC4FokX4BAQ IronPort-Data: A9a23:aVJbKK6Sl6ckQlDZeyulDgxRtG7GchMFZxGqfqrLsTDasY5as4F+v msaDG+BPKuPYWOjf950a4zg9k1Tv8Dcm9VlTFFo+X0zZn8b8sCt6fZ1gavT04J+CuWZESqLO u1HMoGowPgcFyGa/lH2dOC98RGQ7InQLpLkEunIJyttcgFtTSYlmHpLlvUw6mJSqYDR7zil5 5Wo/qUzBHf/g2QqajJNuvrawP9SlK2aVA0w7wRWic9j5Dcyp1FNZLoDKKe4KWfPQ4U8NoaSW +bZwbilyXjS9hErB8nNuu6TnpoiG+O60aCm0xK6aoD66vRwjnVaPpUTaJLwXXxqZwChxLid/ jniWauYEm/FNoWU8AgUvoIx/ytWZcWq85efSZSzXFD6I0DuKxPRL/tS4E4eEtU64ORwEHx12 t81BjsoYUjZpuyaz+fuIgVsrpxLwMjDNYcbvDRkiDreF/tjGMmFSKTR7tge1zA17ixMNa+BP IxCN3w2MlKZP00n1lQ/UPrSmM+omnn2cDRCgFmUvqEwpWPUyWSd1ZC9aYOEIYXTGJ89ckCwj Ejl7Tr2LEAmBOfY1z2M/nuTuNL9gnauMG4VPPjinhJwu3WU3mEVBRgcWFe3rPX8hkOgVtZ3L 00P5jFoqrA/8kGuRNTxUxC05nmesXYht8F4Guk+7kSJj6HT+QvcXzFCRT9aY9tgv8gzLdA36 mK0cxrSLWQHmNWopbi1rN94cRva1fApEFI/ IronPort-HdrOrdr: A9a23:tiDpR65e89w7LfGW3wPXwBbXdLJyesId70hD6qm+c3Nom6uj5q aTdZUgpHjJYVkqOU3I9ersBEDEewK/yXcX2/h0AV7dZmnbUQKTRekIh7cKgQeQfhEWndQy6U 4PScRD4fTLfCFHZL7BkWqFOudl5sWb+6a1guqb5XJsQQZ2L5xE1W5Ce36m+okcfng9OXL/f6 DsnfZ6mw== X-Talos-CUID: 9a23:sYiqpmixxEnbEttqeK5dB80FUDJuXSX/1nXXMlKDJkVEZrawRgfK2a1InJ87 X-Talos-MUID: 9a23:wiorZwSRiujmxAMkRXTGrhJwashGspieMwcTlpwKsva6CB5vbmI= X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.25,231,1779148800"; d="scan'208";a="811931152" Received: from alln-l-core-11.cisco.com ([173.36.16.148]) by alln-iport-7.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 19 Aug 2026 11:00:28 +0000 Received: from sjc-ads-5245.cisco.com (sjc-ads-5245.cisco.com [10.28.23.9]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by alln-l-core-11.cisco.com (Postfix) with ESMTPS id 9D0CF1800098F; Wed, 19 Aug 2026 11:00:28 +0000 (GMT) Received: by sjc-ads-5245.cisco.com (Postfix, from userid 1887505) id 428CCCCA79B; Wed, 19 Aug 2026 04:00:28 -0700 (PDT) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: xe-linux-external@cisco.com, Hetvi Thakar Subject: [meta-python][wrynose][PATCH 4/6] python3-pyjwt: Fix CVE-2026-48525 Date: Wed, 19 Aug 2026 04:00:14 -0700 Message-Id: <20260819110016.252278-4-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20260819110016.252278-1-hthakar@cisco.com> References: <20260819110016.252278-1-hthakar@cisco.com> MIME-Version: 1.0 X-Auto-Response-Suppress: DR, OOF, AutoReply X-Outbound-Client-TLS: ANONYMOUS;sjc-ads-5245.cisco.com [10.28.23.9];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.28.23.9, sjc-ads-5245.cisco.com X-Outbound-Node: alln-l-core-11.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 11:00:35 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/129344 From: Hetvi Thakar This patch applies the upstream 2.13.0 backport for CVE-2026-48525. The upstream fix commit is referenced in [1], and the public CVE advisory is referenced in [2]. [1] https://github.com/jpadilla/pyjwt/commit/95791b1759b8aa4f2203575d344d5c78564cdc81 [2] https://github.com/advisories/GHSA-w7vc-732c-9m39 Signed-off-by: Hetvi Thakar --- .../python/files/CVE-2026-48525.patch | 146 ++++++++++++++++++ .../python/python3-pyjwt_2.12.1.bb | 1 + 2 files changed, 147 insertions(+) create mode 100644 meta-python/recipes-devtools/python/files/CVE-2026-48525.patch diff --git a/meta-python/recipes-devtools/python/files/CVE-2026-48525.patch b/meta-python/recipes-devtools/python/files/CVE-2026-48525.patch new file mode 100644 index 0000000000..9b7ffb97eb --- /dev/null +++ b/meta-python/recipes-devtools/python/files/CVE-2026-48525.patch @@ -0,0 +1,146 @@ +From e6152fb27dd439bf4541aab9b26a93134c48135f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Thu, 21 May 2026 14:11:10 -0400 +Subject: [PATCH] Bundle security fixes and hardening into 2.13.0 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Security: +- `HMACAlgorithm.prepare_key` rejects JWK JSON documents passed as raw + HMAC secrets to close an algorithm-confusion gap not covered by the + existing PEM/SSH guard. Reported by @aradona91 in GHSA-xgmm-8j9v-c9wx. +- Bind the JWT header `alg` to `PyJWK.algorithm_name` during verification + so the caller's `algorithms` allow-list cannot be bypassed when decoding + with a `PyJWK` / `PyJWKClient` key. Reported by @sushi-gif in + GHSA-jq35-7prp-9v3f. +- Skip the unconditional base64 decode of the compact-form payload + segment when `b64=false` is set, and require that segment to be empty + (RFC 7515 Appendix F detached form). Closes an unauthenticated DoS + amplifier. Reported by @thesmartshadow in GHSA-w7vc-732c-9m39. +- `PyJWKClient` rejects any URI whose scheme is not `http` or `https` so + attacker-influenced URIs cannot read local files or reach unintended + schemes via urllib's default `file://` / `ftp://` / `data:` handlers. + Reported by @KEIJOT in GHSA-993g-76c3-p5m4. +- Preserve the cached JWK Set on fetch errors in `PyJWKClient.fetch_data`. + The previous `finally`-block `put(None)` pattern cleared the cache on + any transient outage. Reported by @eddieran in GHSA-fhv5-28vv-h8m8. + +Fixes: +- Reject empty HMAC keys outright in `HMACAlgorithm.prepare_key` with + `InvalidKeyError` instead of accepting them with only a warning. + Hardening prompted by reports from @SnailSploit and @spartan8806. +- Forward per-call `options` (including `enforce_minimum_key_length`) + from `PyJWT.decode` through to `PyJWS._verify_signature`. Thanks to + @WLUB. +- RFC 7797 §3 compliance for `b64=false`: encoder auto-adds `"b64"` to + `crit`; decoder rejects tokens that set `b64=false` without listing + it in `crit`. Thanks to @MachineLearning-Nerd. + +CVE: CVE-2026-48525 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/95791b1759b8aa4f2203575d344d5c78564cdc81] + +Backport Changes: +- Split out the b64=false payload-decoding DoS fix because the upstream + commit bundles multiple CVEs. +- Omitted CHANGELOG.rst because it conflicted and is release documentation. +- Carried the focused + `test_decode_b64_false_rejects_non_empty_payload_segment` regression test. + Omitted other tests/test_api_jws.py hunks for unrelated bundled fixes. +- Omitted the 2.13.0 version bump and other bundled fixes; applicable CVE + fixes are carried in separate patches. + +(cherry picked from commit 95791b1759b8aa4f2203575d344d5c78564cdc81) +Signed-off-by: Hetvi Thakar +--- + jwt/api_jws.py | 22 ++++++++++++++++++---- + tests/test_api_jws.py | 36 +++++++++++++++++++++++++++++++++++- + 2 files changed, 53 insertions(+), 5 deletions(-) + +diff --git a/jwt/api_jws.py b/jwt/api_jws.py +index 91d2ae3..5a91bce 100644 +--- a/jwt/api_jws.py ++++ b/jwt/api_jws.py +@@ -317,10 +317,24 @@ class PyJWS: + if not isinstance(header, dict): + raise DecodeError("Invalid header string: must be a json object") + +- try: +- payload = base64url_decode(payload_segment) +- except (TypeError, binascii.Error) as err: +- raise DecodeError("Invalid payload padding") from err ++ if header.get("b64", True) is False: ++ # Detached payload form (RFC 7515 Appendix F): the compact-form ++ # payload segment must be empty; the caller supplies the actual ++ # payload via the `detached_payload` argument in decode_complete. ++ # Skipping the base64 decode here removes an unauthenticated work ++ # amplifier — otherwise an attacker can inflate the unused ++ # segment to force CPU + memory cost before the signature is ++ # even checked. ++ if payload_segment: ++ raise DecodeError( ++ "Payload segment must be empty when 'b64' is false." ++ ) ++ payload = b"" ++ else: ++ try: ++ payload = base64url_decode(payload_segment) ++ except (TypeError, binascii.Error) as err: ++ raise DecodeError("Invalid payload padding") from err + + try: + signature = base64url_decode(crypto_segment) +diff --git a/tests/test_api_jws.py b/tests/test_api_jws.py +index 0715b9e..01de813 100644 +--- a/tests/test_api_jws.py ++++ b/tests/test_api_jws.py +@@ -12,7 +12,7 @@ from jwt.exceptions import ( + InvalidSignatureError, + InvalidTokenError, + ) +-from jwt.utils import base64url_decode ++from jwt.utils import base64url_decode, base64url_encode + from jwt.warnings import RemovedInPyjwt3Warning + + from .utils import crypto_required, key_path, no_crypto_required +@@ -984,6 +984,40 @@ class TestJWS: + assert "b64" not in msg_header_obj + assert msg_payload + ++ def test_decode_b64_false_rejects_non_empty_payload_segment( ++ self, jws: PyJWS, payload: bytes ++ ) -> None: ++ # RFC 7515 Appendix F detached form: when b64=false, the compact- ++ # serialization payload segment must be empty. PyJWT must reject a ++ # non-empty middle segment without doing any base64-decoding work ++ # on it — that decode used to be the unauthenticated DoS amplifier. ++ secret = "secret" ++ import hmac as _hmac ++ import hashlib as _hashlib ++ ++ header_obj = { ++ "typ": "JWT", ++ "alg": "HS256", ++ "b64": False, ++ "crit": ["b64"], ++ } ++ header_b64 = base64url_encode( ++ json.dumps(header_obj, separators=(",", ":")).encode() ++ ) ++ # Stuff the middle segment with arbitrary attacker-controlled bytes. ++ # This should be rejected without being base64-decoded. ++ attacker_segment = b"A" * 1024 ++ signing_input = b".".join([header_b64, payload]) ++ sig = _hmac.new(secret.encode(), signing_input, _hashlib.sha256).digest() ++ token = b".".join( ++ [header_b64, attacker_segment, base64url_encode(sig)] ++ ).decode() ++ ++ with pytest.raises(DecodeError, match="Payload segment must be empty"): ++ jws.decode( ++ token, secret, algorithms=["HS256"], detached_payload=payload ++ ) ++ + def test_decode_detached_content_without_proper_argument(self, jws: PyJWS) -> None: + example_jws = ( + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2V9" diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb index ff3eb8814d..3e68f9c0b3 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb @@ -8,6 +8,7 @@ LIC_FILES_CHKSUM = "file://LICENSE;md5=e4b56d2c9973d8cf54655555be06e551" SRC_URI += "file://CVE-2026-48522.patch \ file://CVE-2026-48523.patch \ file://CVE-2026-48524.patch \ + file://CVE-2026-48525.patch \ " SRC_URI[sha256sum] = "c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b" From patchwork Wed Aug 19 11:00:15 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 95758 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id CA2DAC5DF8A for ; Wed, 19 Aug 2026 11:00:35 +0000 (UTC) Received: from alln-iport-3.cisco.com (alln-iport-3.cisco.com [173.37.142.90]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.4424.1787137230871035805 for ; Wed, 19 Aug 2026 04:00:31 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=ddYjP2M3; spf=pass (domain: cisco.com, ip: 173.37.142.90, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=7087; q=dns/txt; s=iport01; t=1787137230; x=1788346830; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=8KiUFvX9678hCjDeMZcpu4hzcF/oa38TogZs9/DK8Vo=; b=ddYjP2M3WD67AEDDCUjKMV5W7aNw0vYitDV+lspDbFBmGcmfqdA5AIXK Au1uehGr5KXSYw58pGSgCxT9OgAbFIhc0cd939yLzRJ+gZlzWBFscI+/t sJ7w7CzzCbM2XUml2BOJJvomRbScEieTjFMy8dJDeI2/anMfUGqI3gAXd XGOhRzngs2EWsnaToRAmhSkShbTXnO95QfzD3Y0+YH9jgyIKbm9TnD/3W UvyT/4ayNFYVNOrHo5RYqdCUiQQH8N6KzzTYmKunAEJp5ZTuraGL61ZH3 wGAKlToEuG7kJeCyT9//y9TbAiJBhilyJfwqyhotZO5cCVyaYThXSGCih A==; X-CSE-ConnectionGUID: z3Z/7ag0S3uxtUqTuLUvSQ== X-CSE-MsgGUID: iKGUC8j3Q9ui7TAvl60/yw== X-IPAS-Result: A0BMAgDpi4Vq/5AQJK1aglmCV3ReQ0mEV5FzA54bgX4PAQEBD0QNBAEBhQUCjWsCJjQJDgECBAMCAwEBAQEBAQEBAQEBAQoBAQUBAQECAQcFgQ4Thk8NhloBAgEDIwQLARgBGwYMEBwDAQIDAiYCAisjCBAJgwIBgnQDEcNJen8zgQGDKAGBVNswAQsUAQWBBS6FP4MfAYUCXRgBRIQ4JxsbgXKBFYNpgQWBXAKFO4JqBIINFYEMgVqBLZA4SIECHANZLAFVEw0KCwcFgWYDNRIqFW4yHYEjPhc1WBsGBYEdgSiENyMZNnqBCV6BKyphARIXgQmCCgKCc4IGAgFJRREKCwsYDUgRLDcUGQQ9AW4HjkkgghoNDhJkKgEKIAF7BBJyKR4EkzCSJKEPCiiDdowhlToaM4QEgVeSQJJRC5h9jgqWBhM3hGmBaDyBWXAVO4JnCUoZD44qDguDYIVkxlUnMgIJMgEBBwIHDgMLgWiRfgEB IronPort-Data: A9a23:P+mL/qwlcqJ1NRnwntt6t+dhxyrEfRIJ4+MujC+fZmUNrF6WrkUPy WRNXj+GOqqLNGr3fYxzbYS//BxVuZ/XytZnHlY/+VhgHilAwSbn6Xt1DatR0we6dJCroJdPt p1GAjX4BJlqCCKa/lHybuiJQUBUjcmgXqD7BPPPJhd/TAplTDZJoR94kobVuKYw6TSCK13L4 46aT/H3Ygf/hWYkazNMsspvlTs21BjMkGJA1rABTagjUG/2zxE9EJ8ZLKetGHr0KqE8NvK6X evK0Iai9Wrf+Ro3Yvv9+losWhRXKlJ6FVHmZkt+A8BOsDAbzsAB+vpT2M4nVKtio27hc+adZ zl6ncfYpQ8BZsUgkQmGOvVSO3kW0aZuoNcrLZUj2CCe5xWuTpfi/xlhJFNtD8pH8+kvOjpT5 eAlMQwQSi2dvNvjldpXSsE07igiBMDvOIVavjRryivUSK55B5vCWK7No9Rf2V/chOgXQq2YP JRfMGQpNUicC/FMEg9/5JYWnPuoj3r2aRVTqUmeouw85G27IAlZgOK9aoSJIIzWLSlTtke7i V/tr2aiO0AHNYOj4CSBrCrrl/CayEsXX6pXTtVU7MVCh0WewGEWAhAaWVa35PW0lEO6c9ZeM FAPvC02oK4/8UamQtXwU1u/unHsg/IHc9NUF+t/7ESGzbDZpl7GQGMFVTVGLtchsafaWAAX6 7NApPuxbRQHjVFfYSn1Gmu8xd9qBRUoEA== IronPort-HdrOrdr: A9a23:2nCGjKGNDr5gafo5pLqENseALOsnbusQ8zAXPo5KJiC9Ffbo8f xG/c5rsiMc5wxxZJhNo7290ey7MBHhHP1OkO0s1MmZPDUO0VHAROoJ0WKh+UyEJ8SUzIBgPM lbH5SWIeeAdGSS9fyKgzWQIpIH3MSN9ryuiKP1yndgShwvVoRbhj0Jcjpy1iZNNXN77V1TLu vm2vZ6 X-Talos-CUID: 9a23:USjPo2g/urxgwwvyUSyqyWYRwDJuYCbviyjZLBeDUnt5VICMdUK0xb1fqp87 X-Talos-MUID: 9a23:PQMQsQUHrRGrftzq/D7PqwxJZMUz2vWzCR8xuIsC5ZHDJDMlbg== X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.25,231,1779148800"; d="scan'208";a="828264284" Received: from alln-l-core-07.cisco.com ([173.36.16.144]) by alln-iport-3.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 19 Aug 2026 11:00:29 +0000 Received: from sjc-ads-5245.cisco.com (sjc-ads-5245.cisco.com [10.28.23.9]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by alln-l-core-07.cisco.com (Postfix) with ESMTPS id CB711180004FB; Wed, 19 Aug 2026 11:00:29 +0000 (GMT) Received: by sjc-ads-5245.cisco.com (Postfix, from userid 1887505) id 68CA3CCA79B; Wed, 19 Aug 2026 04:00:29 -0700 (PDT) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: xe-linux-external@cisco.com, Hetvi Thakar Subject: [meta-python][wrynose][PATCH 5/6] python3-pyjwt: Fix CVE-2026-48526 Date: Wed, 19 Aug 2026 04:00:15 -0700 Message-Id: <20260819110016.252278-5-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20260819110016.252278-1-hthakar@cisco.com> References: <20260819110016.252278-1-hthakar@cisco.com> MIME-Version: 1.0 X-Auto-Response-Suppress: DR, OOF, AutoReply X-Outbound-Client-TLS: ANONYMOUS;sjc-ads-5245.cisco.com [10.28.23.9];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.28.23.9, sjc-ads-5245.cisco.com X-Outbound-Node: alln-l-core-07.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 11:00:35 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/129345 From: Hetvi Thakar This patch applies the upstream 2.13.0 backport for CVE-2026-48526. The upstream fix commit is referenced in [1], and the public CVE advisory is referenced in [2]. [1] https://github.com/jpadilla/pyjwt/commit/95791b1759b8aa4f2203575d344d5c78564cdc81 [2] https://github.com/advisories/GHSA-xgmm-8j9v-c9wx Signed-off-by: Hetvi Thakar --- .../python/files/CVE-2026-48526.patch | 126 ++++++++++++++++++ .../python/python3-pyjwt_2.12.1.bb | 1 + 2 files changed, 127 insertions(+) create mode 100644 meta-python/recipes-devtools/python/files/CVE-2026-48526.patch diff --git a/meta-python/recipes-devtools/python/files/CVE-2026-48526.patch b/meta-python/recipes-devtools/python/files/CVE-2026-48526.patch new file mode 100644 index 0000000000..00c2a55838 --- /dev/null +++ b/meta-python/recipes-devtools/python/files/CVE-2026-48526.patch @@ -0,0 +1,126 @@ +From 336f1a0c18c04b63cab024b9f030af2b6c6b248c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Thu, 21 May 2026 14:11:10 -0400 +Subject: [PATCH] Bundle security fixes and hardening into 2.13.0 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Security: +- `HMACAlgorithm.prepare_key` rejects JWK JSON documents passed as raw + HMAC secrets to close an algorithm-confusion gap not covered by the + existing PEM/SSH guard. Reported by @aradona91 in GHSA-xgmm-8j9v-c9wx. +- Bind the JWT header `alg` to `PyJWK.algorithm_name` during verification + so the caller's `algorithms` allow-list cannot be bypassed when decoding + with a `PyJWK` / `PyJWKClient` key. Reported by @sushi-gif in + GHSA-jq35-7prp-9v3f. +- Skip the unconditional base64 decode of the compact-form payload + segment when `b64=false` is set, and require that segment to be empty + (RFC 7515 Appendix F detached form). Closes an unauthenticated DoS + amplifier. Reported by @thesmartshadow in GHSA-w7vc-732c-9m39. +- `PyJWKClient` rejects any URI whose scheme is not `http` or `https` so + attacker-influenced URIs cannot read local files or reach unintended + schemes via urllib's default `file://` / `ftp://` / `data:` handlers. + Reported by @KEIJOT in GHSA-993g-76c3-p5m4. +- Preserve the cached JWK Set on fetch errors in `PyJWKClient.fetch_data`. + The previous `finally`-block `put(None)` pattern cleared the cache on + any transient outage. Reported by @eddieran in GHSA-fhv5-28vv-h8m8. + +Fixes: +- Reject empty HMAC keys outright in `HMACAlgorithm.prepare_key` with + `InvalidKeyError` instead of accepting them with only a warning. + Hardening prompted by reports from @SnailSploit and @spartan8806. +- Forward per-call `options` (including `enforce_minimum_key_length`) + from `PyJWT.decode` through to `PyJWS._verify_signature`. Thanks to + @WLUB. +- RFC 7797 §3 compliance for `b64=false`: encoder auto-adds `"b64"` to + `crit`; decoder rejects tokens that set `b64=false` without listing + it in `crit`. Thanks to @MachineLearning-Nerd. + +CVE: CVE-2026-48526 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/95791b1759b8aa4f2203575d344d5c78564cdc81] + +Backport Changes: +- Split out the raw JWK-as-HMAC-secret rejection because the upstream + commit bundles multiple CVEs. +- Omitted CHANGELOG.rst because it conflicted and is release documentation. +- Carried `test_hmac_prepare_key_rejects_jwk_json` and + `test_hmac_prepare_key_accepts_json_without_kty` regression tests covering + the same JWK-classification security boundary. +- Omitted other tests/test_algorithms.py hunks for separate empty-key and + per-call key-length hardening. +- Omitted the 2.13.0 version bump and other bundled fixes; applicable CVE + fixes are carried in separate patches. + +(cherry picked from commit 95791b1759b8aa4f2203575d344d5c78564cdc81) +Signed-off-by: Hetvi Thakar +--- + jwt/algorithms.py | 20 ++++++++++++++++++++ + tests/test_algorithms.py | 23 +++++++++++++++++++++++ + 2 files changed, 43 insertions(+) + +diff --git a/jwt/algorithms.py b/jwt/algorithms.py +index 615dcf3..cf6da45 100644 +--- a/jwt/algorithms.py ++++ b/jwt/algorithms.py +@@ -331,6 +331,26 @@ class HMACAlgorithm(Algorithm): + " should not be used as an HMAC secret." + ) + ++ # Defense against algorithm-confusion attacks: an attacker with ++ # control over the token header can force this code path by setting ++ # alg=HS*, and HMACAlgorithm is the only algorithm that accepts ++ # arbitrary bytes as a valid secret. Other algorithms reject ++ # non-key-shaped input naturally. Even a symmetric (kty=oct) JWK ++ # should be loaded via PyJWK / from_jwk rather than fed as raw JSON ++ # bytes (whose contents are not the secret material). ++ stripped = key_bytes.lstrip() ++ if stripped.startswith(b"{"): ++ try: ++ jwk_obj = json.loads(key_bytes) ++ except ValueError: ++ jwk_obj = None ++ if isinstance(jwk_obj, dict) and "kty" in jwk_obj: ++ raise InvalidKeyError( ++ "The specified key looks like a JWK and should not be " ++ "used directly as an HMAC secret. Load it via " ++ "PyJWK / HMACAlgorithm.from_jwk first." ++ ) ++ + return key_bytes + + @overload +diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py +index 5449f3c..82659cf 100644 +--- a/tests/test_algorithms.py ++++ b/tests/test_algorithms.py +@@ -122,6 +122,29 @@ class TestAlgorithms: + with pytest.raises(InvalidKeyError): + algo.from_jwk(keyfile.read()) + ++ @pytest.mark.parametrize( ++ "jwk_file", ++ [ ++ "jwk_rsa_pub.json", ++ "jwk_ec_pub_P-256.json", ++ "jwk_okp_pub_Ed25519.json", ++ "jwk_hmac.json", ++ ], ++ ) ++ def test_hmac_prepare_key_rejects_jwk_json(self, jwk_file: str) -> None: ++ algo = HMACAlgorithm(HMACAlgorithm.SHA256) ++ ++ with open(key_path(jwk_file)) as keyfile: ++ with pytest.raises(InvalidKeyError, match="looks like a JWK"): ++ algo.prepare_key(keyfile.read()) ++ ++ def test_hmac_prepare_key_accepts_json_without_kty(self) -> None: ++ # JSON that doesn't look like a JWK (no "kty") should not be misclassified. ++ algo = HMACAlgorithm(HMACAlgorithm.SHA256) ++ ++ key = algo.prepare_key('{"this": "is just a json-shaped secret"}') ++ assert key == b'{"this": "is just a json-shaped secret"}' ++ + @crypto_required + def test_rsa_should_parse_pem_public_key(self) -> None: + algo = RSAAlgorithm(RSAAlgorithm.SHA256) diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb index 3e68f9c0b3..bae7418565 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb @@ -9,6 +9,7 @@ SRC_URI += "file://CVE-2026-48522.patch \ file://CVE-2026-48523.patch \ file://CVE-2026-48524.patch \ file://CVE-2026-48525.patch \ + file://CVE-2026-48526.patch \ " SRC_URI[sha256sum] = "c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b" From patchwork Wed Aug 19 11:00:16 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 95754 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id EF45CC5DF86 for ; Wed, 19 Aug 2026 11:00:34 +0000 (UTC) Received: from alln-iport-2.cisco.com (alln-iport-2.cisco.com [173.37.142.89]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.4433.1787137228449757623 for ; Wed, 19 Aug 2026 04:00:33 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=RmDxiOZr; spf=pass (domain: cisco.com, ip: 173.37.142.89, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=38229; q=dns/txt; s=iport01; t=1787137233; x=1788346833; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=wRbC4YaJkUvOHiF1G37d9LNqwsBXXBjUHgAdSjFuHo0=; b=RmDxiOZrFLyxlTVPPSck8ytRNDjWFywahRrobECixZXUx1GAW9jK0+QV V7dFYamdHjcY0TBcwkIio1IvO2e7NQ62a6A4fIhWTZNQ1mvk/kNkSysOH gkPtuSlm2qpPcBPiAoX6+PJJDiodWSck7Z58BsGTp/RBq1NJYQS4P0+3v 2RL3ODtX4CJoBKcbe1neOSjf0mI+aK87FQkrl/QjINtBEwHl7N130eIaT /a7rMD5/pErPczW9lB3OSjeMWJX0z0ii5ZGndwMpXgwuNptdvXynUeZ6U PtctPhQwDTp1RD9qps/hlFcrgS8/t7so3hyiHHM3gGZuKKOqVhQA847OA w==; X-CSE-ConnectionGUID: qGssM7mkSZmtRSBgrs3MmA== X-CSE-MsgGUID: gl7m/4sbRfuLOn6W/djwzQ== X-IPAS-Result: A0BAAwDpi4Vq/5QQJK1aglmCV3ReQ0mVXmwDgROKUYVmjGWBag8BAQEPRA0EAQGEP0YCjWsCJjcGDgECBAMCAwEBAQEBAQEBAQEBCwEBBQEBAQIBBwWBDhOGTw2GWgECAQMaAQwLARgBGxIQHAMBAi8gCyMIGYMCAYI6AzcDEcNJgXkzgQGCZkIBgVTYSw2BSYEPAQsUAQWBM4U/gn+FI10YAUSCBYIzJxsbgXKBFYE7gTh2gQWBGkICgScRD4ZeBIINFYEMgVqBLXJig0WDMQOHa0iBHgNZLAFVEw0KCwcFgWYDNRIqFW4yHYEjPheBDRsGBYEdgSiENyMZNnqBCV6BKyphARIXgQmCCgKCc4IGAgFJRREKCwsYDUgRLDcUGQQ+bgeOSSCBVFMOEgEsKzYBByRGAjMKFActCBcBEQ0VGzwDkmkBBwonj2WCIYE1nhxNcQoog3aMIY8+hXwaM4QEgVeSQJJRC5h9glmLMYQJkgVChGmBfiaBWXAVO4JnCUoZD1aNVAQKC4NghFBDUcZVJzICCTIBAQcCBw4DC4FokAABJ4FWAQE IronPort-Data: A9a23:yWCEXqyIYyRS0da4rJB6t+dhxyrEfRIJ4+MujC+fZmUNrF6WrkVUx mQeUDuFb/zcYmGkc4hzboji8xlTvcTSyYQ2GQo5q1hgHilAwSbn6Xt1DatR0we6dJCroJdPt p1GAjX4BJlqCCea/VH1buSJQUBUjcmgXqD7BPPPJhd/TAplTDZJoR94kobVuKYw6TSCK13L4 46aT/H3Ygf/hWYkazNMsspvlTs21BjMkGJA1rABTagjUG/2zxE9EJ8ZLKetGHr0KqE8NvK6X evK0Iai9Wrf+Ro3Yvv9+losWhRXKlJ6FVHmZkt+A8BOsDAbzsAB+vpT2M4nVKtio27hc+adZ zl6ncfYpQ8BZsUgkQmGOvVSO3kW0aZuoNcrLZUj2CCe5xWuTpfi/xlhJFgZJpY9peN0OlNL6 cYjAj8XdUumgsvjldpXSsE07igiBMDvOIVavjRryivUSK9/B5vCWK7No9Rf2V/chOgXQq2YP JRfMGQpNUiQC/FMEg9/5JYWnPuoj3r2aRVTqUmeouw85G27IAlZgOC8YYOPJoDRLSlTtkmc+ WeBo3/zOB8HEPuZlwXC/nCzlNaayEsXX6pXTtVU7MVCh0WewGEWAhAaWVa35PW0lEO6c9ZeM FAPvC02oK4/8UamQtXwU1u/unHsg/IHc9NUF+t/7ESGzbDZpl/AQGMFVTVGLtchsafaWAAX6 7NApPuxbRQHjVFfYSj1Gmu8xd9qBRUoEA== IronPort-HdrOrdr: A9a23:yYG2z6hFXmnn6k0639g+DXmYKXBQXgIji2hC6mlwRA09TyVXra +TdZMgpHjJYVkqOU3I9ersBEDEewK/yXcX2/h0AV7dZmnbUQKTRekIh7cKgQeQfhEWndQy6U 4PScRD4fTLfD5HZL7BkWqFOudl5sWb+6a1guqb5XJsQQZ2L5xE1W5Ce3+m+okcfng8OXL/f6 DsnvZ6mw== X-Talos-CUID: 9a23:gI7AYWjbSB/aYnOEOdhQA3zD5DJua1LG11KPJkyDO2N1coa/RkWLo4Zhup87 X-Talos-MUID: 9a23:NDniwgt9N+rfcEDcX82nizU9MYA0+K6UBl0ptpNYhI6INzNiAmLI X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.25,231,1779148800"; d="scan'208";a="809912443" Received: from alln-l-core-11.cisco.com ([173.36.16.148]) by alln-iport-2.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 19 Aug 2026 11:00:32 +0000 Received: from sjc-ads-5245.cisco.com (sjc-ads-5245.cisco.com [10.28.23.9]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by alln-l-core-11.cisco.com (Postfix) with ESMTPS id 0A95D18006F0E; Wed, 19 Aug 2026 11:00:32 +0000 (GMT) Received: by sjc-ads-5245.cisco.com (Postfix, from userid 1887505) id 9DA07CCA79B; Wed, 19 Aug 2026 04:00:31 -0700 (PDT) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: xe-linux-external@cisco.com, Hetvi Thakar Subject: [meta-python][wrynose][PATCH 6/6] python3-twisted: Fix CVE-2026-42304 Date: Wed, 19 Aug 2026 04:00:16 -0700 Message-Id: <20260819110016.252278-6-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20260819110016.252278-1-hthakar@cisco.com> References: <20260819110016.252278-1-hthakar@cisco.com> MIME-Version: 1.0 X-Auto-Response-Suppress: DR, OOF, AutoReply X-Outbound-Client-TLS: ANONYMOUS;sjc-ads-5245.cisco.com [10.28.23.9];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.28.23.9, sjc-ads-5245.cisco.com X-Outbound-Node: alln-l-core-11.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 19 Aug 2026 11:00:34 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/129346 From: Hetvi Thakar This patch applies the upstream 26.4.0rc2 backport for CVE-2026-42304. The upstream fix merge is referenced in [1], and the public CVE advisory is referenced in [2]. The individual backported commit links are recorded in the patch headers. [1] https://github.com/twisted/twisted/commit/2d196123264efb0027eecfe1b430be4a9babdbd8 [2] https://github.com/advisories/GHSA-grgv-6hw6-v9g4 Signed-off-by: Hetvi Thakar --- .../python/files/CVE-2026-42304_p1.patch | 299 ++++++++++++++++ .../python/files/CVE-2026-42304_p2.patch | 30 ++ .../python/files/CVE-2026-42304_p3.patch | 33 ++ .../python/files/CVE-2026-42304_p4.patch | 318 ++++++++++++++++++ .../python/files/CVE-2026-42304_p5.patch | 218 ++++++++++++ .../python/files/CVE-2026-42304_p6.patch | 23 ++ .../python/python3-twisted_25.5.0.bb | 8 + 7 files changed, 929 insertions(+) create mode 100644 meta-python/recipes-devtools/python/files/CVE-2026-42304_p1.patch create mode 100644 meta-python/recipes-devtools/python/files/CVE-2026-42304_p2.patch create mode 100644 meta-python/recipes-devtools/python/files/CVE-2026-42304_p3.patch create mode 100644 meta-python/recipes-devtools/python/files/CVE-2026-42304_p4.patch create mode 100644 meta-python/recipes-devtools/python/files/CVE-2026-42304_p5.patch create mode 100644 meta-python/recipes-devtools/python/files/CVE-2026-42304_p6.patch diff --git a/meta-python/recipes-devtools/python/files/CVE-2026-42304_p1.patch b/meta-python/recipes-devtools/python/files/CVE-2026-42304_p1.patch new file mode 100644 index 0000000000..462c4a4f8c --- /dev/null +++ b/meta-python/recipes-devtools/python/files/CVE-2026-42304_p1.patch @@ -0,0 +1,299 @@ +From a023e4192dbbdc7d8d6b391ef9b226c2cfffe97e Mon Sep 17 00:00:00 2001 +From: tomasilluminati +Date: Sun, 19 Apr 2026 05:57:33 -0300 +Subject: [PATCH] (fix): denial of service in twisted.names mitigation + +CVE: CVE-2026-42304 +Upstream-Status: Backport [https://github.com/twisted/twisted/commit/be71ecaa113f642f03083bd5ed33af47c59308c8] + +(cherry picked from commit be71ecaa113f642f03083bd5ed33af47c59308c8) +Signed-off-by: Hetvi Thakar +--- + src/twisted/names/dns.py | 116 +++++++++++++++++++++++++---- + src/twisted/names/test/test_dns.py | 91 ++++++++++++++++++++++ + 2 files changed, 193 insertions(+), 14 deletions(-) + +diff --git a/src/twisted/names/dns.py b/src/twisted/names/dns.py +index c7644ef50..7142d9e75 100644 +--- a/src/twisted/names/dns.py ++++ b/src/twisted/names/dns.py +@@ -11,6 +11,7 @@ Future Plans: + from __future__ import annotations + + # System imports ++import contextvars + import inspect + import random + import socket +@@ -126,6 +127,7 @@ __all__ = [ + "OP_UPDATE", + "PORT", + "AuthoritativeDomainError", ++ "DNSDecodeError", + "DNSQueryTimeoutError", + "DomainError", + ] +@@ -444,6 +446,64 @@ def readPrecisely(file, l): + return buff + + ++# Cap the total number of compression-pointer dereferences performed while ++# decoding a single DNS message. A hostile peer can otherwise craft a packet ++# in which every record name chases a long compression chain, forcing O(N*M) ++# work and stalling the reactor. ++MAX_COMPRESSION_POINTERS_PER_MESSAGE = 1000 ++ ++ ++class DNSDecodeError(ValueError): ++ """ ++ Raised when a DNS message cannot be decoded because it violates a ++ protocol-level safety limit ++ """ ++ ++ ++class _DecodeContext: ++ """ ++ Mutable state shared between the L{IEncodable} decoders invoked while ++ reading a single DNS message. ++ ++ The primary purpose is to bound the total number of compression-pointer ++ jumps taken across every name in the message, defending against packets ++ that fan out thousands of records pointing to deeply chained pointers. ++ ++ @ivar jumps: The number of compression pointers followed so far. ++ @ivar maxJumps: The inclusive upper bound on C{jumps}. Exceeding it ++ causes L{registerJump} to raise L{DNSDecodeError}. ++ """ ++ ++ __slots__ = ("jumps", "maxJumps") ++ ++ def __init__(self, maxJumps: int = MAX_COMPRESSION_POINTERS_PER_MESSAGE) -> None: ++ self.jumps = 0 ++ self.maxJumps = maxJumps ++ ++ def registerJump(self) -> None: ++ """ ++ Record that a compression pointer has been followed ++ ++ @raise DNSDecodeError: if the cumulative number of jumps exceeds ++ L{maxJumps} ++ """ ++ self.jumps += 1 ++ if self.jumps > self.maxJumps: ++ raise DNSDecodeError( ++ "Too many compression pointers while decoding DNS message " ++ f"(limit is {self.maxJumps})" ++ ) ++ ++ ++# Tracks state across nested calls without altering every record's signature. ++# L{Message.decode} manages the lifecycle per-message, while standalone decoders ++# default to a local context when C{_decodeContextVar} is C{None} ++ ++_decodeContextVar: contextvars.ContextVar[_DecodeContext | None] = ( ++ contextvars.ContextVar("_dnsDecodeContext", default=None) ++) ++ ++ + class IEncodable(Interface): + """ + Interface for something which can be encoded to and decoded +@@ -591,7 +651,7 @@ class Name: + strio.write(label) + strio.write(b"\x00") + +- def decode(self, strio, length=None): ++ def decode(self, strio, length=None, context=None): + """ + Decode a byte string into this Name. + +@@ -599,12 +659,27 @@ class Name: + @param strio: Bytes will be read from this file until the full Name + is decoded. + ++ @type context: L{_DecodeContext} or L{None} ++ @param context: Shared decoding state used to cap the total number ++ of compression-pointer jumps taken while decoding the enclosing ++ DNS message. When L{None}, the context installed by ++ L{Message.decode} is used if one is active; otherwise a fresh, ++ call-local context is created so that direct callers remain ++ protected and backwards compatible. ++ + @raise EOFError: Raised when there are not enough bytes available + from C{strio}. + +- @raise ValueError: Raised when the name cannot be decoded (for example, +- because it contains a loop). ++ @raise ValueError: Raised when the name cannot be decoded because it ++ contains a compression loop. ++ ++ @raise DNSDecodeError: Raised when the cumulative number of ++ compression-pointer jumps exceeds the configured limit. + """ ++ if context is None: ++ context = _decodeContextVar.get() ++ if context is None: ++ context = _DecodeContext() + visited = set() + self.name = b"" + off = 0 +@@ -616,6 +691,7 @@ class Name: + return + if (l >> 6) == 3: + new_off = (l & 63) << 8 | ord(readPrecisely(strio, 1)) ++ context.registerJump() + if new_off in visited: + raise ValueError("Compression loop in encoded name") + visited.add(new_off) +@@ -2704,19 +2780,31 @@ class Message(tputil.FancyEqMixin): + self.checkingDisabled = (byte4 >> 4) & 1 + self.rCode = byte4 & 0xF + +- self.queries = [] +- for i in range(nqueries): +- q = Query() +- try: +- q.decode(strio) +- except EOFError: +- return +- self.queries.append(q) ++ # A single shared counter bounds the total compression-pointer work ++ # performed across every name in this message. It is installed on ++ # the context variable so nested record decoders pick it up without ++ # needing to thread it through each signature. ++ token = _decodeContextVar.set(_DecodeContext()) ++ try: ++ self.queries = [] ++ for i in range(nqueries): ++ q = Query() ++ try: ++ q.decode(strio) ++ except EOFError: ++ return ++ self.queries.append(q) + +- items = ((self.answers, nans), (self.authority, nns), (self.additional, nadd)) ++ items = ( ++ (self.answers, nans), ++ (self.authority, nns), ++ (self.additional, nadd), ++ ) + +- for l, n in items: +- self.parseRecords(l, n, strio) ++ for l, n in items: ++ self.parseRecords(l, n, strio) ++ finally: ++ _decodeContextVar.reset(token) + + def parseRecords(self, list, num, strio): + for i in range(num): +diff --git a/src/twisted/names/test/test_dns.py b/src/twisted/names/test/test_dns.py +index 3b8f6e130..9b27c4b3a 100644 +--- a/src/twisted/names/test/test_dns.py ++++ b/src/twisted/names/test/test_dns.py +@@ -352,6 +352,60 @@ class NameTests(unittest.TestCase): + stream = BytesIO(b"\xc0\x00") + self.assertRaises(ValueError, name.decode, stream) + ++ def test_rejectTooManyCompressionPointers(self): ++ """ ++ L{Name.decode} raises L{dns.DNSDecodeError} when the number of ++ compression-pointer dereferences taken for a single message exceeds ++ the limit carried by the shared L{dns._DecodeContext}. ++ """ ++ # Five distinct pointers chained end-to-end, terminated by a zero ++ # label byte. With a maxJumps of three the fourth dereference must ++ # trip the safety limit. ++ payload = b"\xc0\x02\xc0\x04\xc0\x06\xc0\x08\x00" ++ context = dns._DecodeContext(maxJumps=3) ++ self.assertRaises( ++ dns.DNSDecodeError, ++ dns.Name().decode, ++ BytesIO(payload), ++ None, ++ context, ++ ) ++ ++ def test_compressionPointerCounterIsShared(self): ++ """ ++ The L{dns._DecodeContext} counter accumulates across successive ++ L{Name.decode} calls, so that a message whose individual names are ++ each within bounds is still rejected when their aggregate exceeds ++ the configured limit. ++ """ ++ payload = b"\xc0\x02\xc0\x04\x00" ++ context = dns._DecodeContext(maxJumps=3) ++ ++ stream = BytesIO(payload) ++ dns.Name().decode(stream, context=context) ++ self.assertEqual(context.jumps, 2) ++ ++ stream.seek(0) ++ self.assertRaises( ++ dns.DNSDecodeError, ++ dns.Name().decode, ++ stream, ++ None, ++ context, ++ ) ++ ++ def test_decodeWithoutContextIsBackwardsCompatible(self): ++ """ ++ L{Name.decode} continues to work when called without a context, ++ using a fresh per-call counter so existing callers are unaffected. ++ """ ++ name = dns.Name() ++ stream = BytesIO() ++ dns.Name(b"example.org").encode(stream) ++ stream.seek(0) ++ name.decode(stream) ++ self.assertEqual(name.name, b"example.org") ++ + def test_equality(self): + """ + L{Name} instances are equal as long as they have the same value for +@@ -761,6 +815,43 @@ class MessageTests(unittest.SynchronousTestCase): + """ + self.assertEqual(dns.Message().authenticData, 0) + ++ def test_rejectCompressionPointerFlood(self): ++ """ ++ L{Message.decode} installs a shared compression-pointer counter and ++ raises L{dns.DNSDecodeError} when the aggregate number of pointer ++ dereferences across every record in the message exceeds ++ L{dns.MAX_COMPRESSION_POINTERS_PER_MESSAGE}. ++ """ ++ chainLength = 100 ++ numRecords = 8000 ++ header = struct.pack( ++ "!H2B4H", 0x1234, 0x80, 0x00, 0, numRecords, 0, 0 ++ ) ++ ++ # Long compression chain inside the RDATA of an unknown ++ # record so that subsequent records can aim pointers at it. ++ owner = b"\x04rrrr\x00" ++ chainBase = len(header) + len(owner) + 10 ++ chain = bytearray() ++ for i in range(chainLength): ++ chain += struct.pack("!H", 0xC000 | (chainBase + 2 * (i + 1))) ++ chain += b"\x04test\x00" ++ ++ firstRecord = ( ++ owner ++ + struct.pack("!HHIH", 999, 1, 0, len(chain)) ++ + bytes(chain) ++ ) ++ followupRecord = ( ++ struct.pack("!H", 0xC000 | chainBase) ++ + struct.pack("!HHIH", 1, 1, 0, 4) ++ + b"\x00\x00\x00\x00" ++ ) ++ payload = header + firstRecord + followupRecord * (numRecords - 1) ++ ++ message = dns.Message() ++ self.assertRaises(dns.DNSDecodeError, message.decode, BytesIO(payload)) ++ + def test_authenticDataOverride(self): + """ + L{dns.Message.__init__} accepts a C{authenticData} argument which diff --git a/meta-python/recipes-devtools/python/files/CVE-2026-42304_p2.patch b/meta-python/recipes-devtools/python/files/CVE-2026-42304_p2.patch new file mode 100644 index 0000000000..2c5490c190 --- /dev/null +++ b/meta-python/recipes-devtools/python/files/CVE-2026-42304_p2.patch @@ -0,0 +1,30 @@ +From 094d33d4073368ab1a85fcf63e3b75addb01fd6a Mon Sep 17 00:00:00 2001 +From: Tomas Illuminati Balbin +Date: Mon, 20 Apr 2026 09:17:13 -0300 +Subject: [PATCH] Update src/twisted/names/dns.py + +Co-authored-by: Adi Roiban + +CVE: CVE-2026-42304 +Upstream-Status: Backport [https://github.com/twisted/twisted/commit/86e1b5490de5baa8ca284d1e6f4f0ade3e8ad7e0] + +(cherry picked from commit 86e1b5490de5baa8ca284d1e6f4f0ade3e8ad7e0) +Signed-off-by: Hetvi Thakar +--- + src/twisted/names/dns.py | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/src/twisted/names/dns.py b/src/twisted/names/dns.py +index 7142d9e75..93e4080bf 100644 +--- a/src/twisted/names/dns.py ++++ b/src/twisted/names/dns.py +@@ -2784,8 +2784,7 @@ class Message(tputil.FancyEqMixin): + # performed across every name in this message. It is installed on + # the context variable so nested record decoders pick it up without + # needing to thread it through each signature. +- token = _decodeContextVar.set(_DecodeContext()) +- try: ++ with _decodeContextVar.set(_DecodeContext()): + self.queries = [] + for i in range(nqueries): + q = Query() diff --git a/meta-python/recipes-devtools/python/files/CVE-2026-42304_p3.patch b/meta-python/recipes-devtools/python/files/CVE-2026-42304_p3.patch new file mode 100644 index 0000000000..9b04562ca3 --- /dev/null +++ b/meta-python/recipes-devtools/python/files/CVE-2026-42304_p3.patch @@ -0,0 +1,33 @@ +From 7213b262b9c98fe6d522dbd23fd83bb9535b54c6 Mon Sep 17 00:00:00 2001 +From: Tomas Illuminati Balbin +Date: Mon, 20 Apr 2026 09:18:05 -0300 +Subject: [PATCH] Update src/twisted/names/test/test_dns.py + +Co-authored-by: Adi Roiban + +CVE: CVE-2026-42304 +Upstream-Status: Backport [https://github.com/twisted/twisted/commit/c75d44ed81b47f8086ed801cfcdb2568b0b32301] + +(cherry picked from commit c75d44ed81b47f8086ed801cfcdb2568b0b32301) +Signed-off-by: Hetvi Thakar +--- + src/twisted/names/test/test_dns.py | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/twisted/names/test/test_dns.py b/src/twisted/names/test/test_dns.py +index 9b27c4b3a..94aa4a802 100644 +--- a/src/twisted/names/test/test_dns.py ++++ b/src/twisted/names/test/test_dns.py +@@ -389,9 +389,9 @@ class NameTests(unittest.TestCase): + self.assertRaises( + dns.DNSDecodeError, + dns.Name().decode, +- stream, +- None, +- context, ++ strio=stream, ++ length=None, ++ context=context, + ) + + def test_decodeWithoutContextIsBackwardsCompatible(self): diff --git a/meta-python/recipes-devtools/python/files/CVE-2026-42304_p4.patch b/meta-python/recipes-devtools/python/files/CVE-2026-42304_p4.patch new file mode 100644 index 0000000000..b9b6be31bd --- /dev/null +++ b/meta-python/recipes-devtools/python/files/CVE-2026-42304_p4.patch @@ -0,0 +1,318 @@ +From 3cb501679cd7f45ab49d32cf72ec546ef3a64825 Mon Sep 17 00:00:00 2001 +From: Tomas Illuminati +Date: Mon, 20 Apr 2026 10:01:39 -0300 +Subject: [PATCH] names: Refactor DNS compression mitigation + +CVE: CVE-2026-42304 +Upstream-Status: Backport [https://github.com/twisted/twisted/commit/d7d81e08d46b3f266963ea77e5f6b4a333af455f] + +Backport Changes: +- Adapted the 25.5.0 imports by moving Sequence from typing to + collections.abc and retaining the target's Optional and Union imports. + +(cherry picked from commit d7d81e08d46b3f266963ea77e5f6b4a333af455f) +Signed-off-by: Hetvi Thakar +--- + src/twisted/names/dns.py | 113 +++++++++++++------ + src/twisted/names/newsfragments/12626.bugfix | 1 + + src/twisted/names/test/test_dns.py | 47 ++++---- + 3 files changed, 105 insertions(+), 56 deletions(-) + create mode 100644 src/twisted/names/newsfragments/12626.bugfix + +diff --git a/src/twisted/names/dns.py b/src/twisted/names/dns.py +index 93e4080bf..869ffec76 100644 +--- a/src/twisted/names/dns.py ++++ b/src/twisted/names/dns.py +@@ -16,9 +16,11 @@ import inspect + import random + import socket + import struct ++from collections.abc import Sequence ++from contextlib import contextmanager + from io import BytesIO + from itertools import chain +-from typing import Optional, Sequence, SupportsInt, Union, overload ++from typing import Final, Optional, SupportsInt, Union, overload + + from zope.interface import Attribute, Interface, implementer + +@@ -446,17 +448,19 @@ def readPrecisely(file, l): + return buff + + +-# Cap the total number of compression-pointer dereferences performed while +-# decoding a single DNS message. A hostile peer can otherwise craft a packet +-# in which every record name chases a long compression chain, forcing O(N*M) +-# work and stalling the reactor. +-MAX_COMPRESSION_POINTERS_PER_MESSAGE = 1000 ++MAX_COMPRESSION_POINTERS_PER_MESSAGE: Final = 1000 ++""" ++Cap the total number of compression-pointer dereferences performed while ++decoding a single DNS message. A hostile peer can otherwise craft a packet ++in which every record name chases a long compression chain, forcing ++C{O(N*M)} work and stalling the reactor. ++""" + + + class DNSDecodeError(ValueError): + """ + Raised when a DNS message cannot be decoded because it violates a +- protocol-level safety limit ++ protocol-level safety limit. + """ + + +@@ -469,8 +473,12 @@ class _DecodeContext: + jumps taken across every name in the message, defending against packets + that fan out thousands of records pointing to deeply chained pointers. + ++ This class is private. External callers must not rely on it; the ++ per-message scope is installed and torn down by L{Message.decode} ++ through L{_decodeContextVar}. ++ + @ivar jumps: The number of compression pointers followed so far. +- @ivar maxJumps: The inclusive upper bound on C{jumps}. Exceeding it ++ @ivar maxJumps: The inclusive upper bound on L{jumps}. Exceeding it + causes L{registerJump} to raise L{DNSDecodeError}. + """ + +@@ -482,10 +490,14 @@ class _DecodeContext: + + def registerJump(self) -> None: + """ +- Record that a compression pointer has been followed ++ Record that a compression pointer has been followed. ++ ++ The check is performed before any further bytes are read so the ++ caller fails fast as soon as the aggregate limit is breached, even ++ if additional records remain in the buffer. + + @raise DNSDecodeError: if the cumulative number of jumps exceeds +- L{maxJumps} ++ L{maxJumps}. + """ + self.jumps += 1 + if self.jumps > self.maxJumps: +@@ -495,15 +507,37 @@ class _DecodeContext: + ) + + +-# Tracks state across nested calls without altering every record's signature. +-# L{Message.decode} manages the lifecycle per-message, while standalone decoders +-# default to a local context when C{_decodeContextVar} is C{None} +- ++# Private module-level L{contextvars.ContextVar} used to share a single ++# L{_DecodeContext} across the re-entrant calls performed while decoding one ++# DNS message. L{contextvars} (rather than a plain module attribute) is used ++# on purpose: although Twisted's reactor is single-threaded, message decoding ++# is re-entrant across many records in a single pass and L{ContextVar} ++# guarantees the scope is restored correctly on exit -- and remains isolated ++# per-task should a future caller decode messages from multiple ++# L{asyncio}-style contexts concurrently. + _decodeContextVar: contextvars.ContextVar[_DecodeContext | None] = ( + contextvars.ContextVar("_dnsDecodeContext", default=None) + ) + + ++@contextmanager ++def _installDecodeContext(context: _DecodeContext): ++ """ ++ Install C{context} on L{_decodeContextVar} for the duration of the ++ C{with} block and restore the previous value on exit. ++ ++ This wraps the L{contextvars.ContextVar.set} / L{contextvars.ContextVar.reset} ++ token dance so call sites can use a plain C{with} statement. ++ ++ @param context: The L{_DecodeContext} to install as the active context. ++ """ ++ token = _decodeContextVar.set(context) ++ try: ++ yield context ++ finally: ++ _decodeContextVar.reset(token) ++ ++ + class IEncodable(Interface): + """ + Interface for something which can be encoded to and decoded +@@ -609,8 +643,18 @@ class Name: + + @ivar name: A byte string giving the name. + @type name: L{bytes} ++ ++ @cvar maxCompressionPointers: Per-message cap on the total number of ++ compression-pointer dereferences L{decode} will follow before ++ raising L{DNSDecodeError}. Defined as a class attribute so ++ subclasses (and, in the future, individual instances) may override ++ it to tune the trade-off between tolerance for legitimately ++ verbose messages and resistance to denial-of-service attacks. ++ @type maxCompressionPointers: L{int} + """ + ++ maxCompressionPointers: int = MAX_COMPRESSION_POINTERS_PER_MESSAGE ++ + def __init__(self, name: bytes | str = b""): + """ + @param name: A name. +@@ -651,35 +695,37 @@ class Name: + strio.write(label) + strio.write(b"\x00") + +- def decode(self, strio, length=None, context=None): ++ def decode(self, strio, length=None): + """ + Decode a byte string into this Name. + ++ When invoked from L{Message.decode}, a shared compression-pointer ++ counter is picked up transparently from the private ++ L{_decodeContextVar}. Standalone callers get a fresh per-call ++ counter seeded from L{maxCompressionPointers}, so existing code ++ keeps working unchanged while still being protected against ++ pathological inputs. ++ + @type strio: file + @param strio: Bytes will be read from this file until the full Name +- is decoded. ++ is decoded. + +- @type context: L{_DecodeContext} or L{None} +- @param context: Shared decoding state used to cap the total number +- of compression-pointer jumps taken while decoding the enclosing +- DNS message. When L{None}, the context installed by +- L{Message.decode} is used if one is active; otherwise a fresh, +- call-local context is created so that direct callers remain +- protected and backwards compatible. ++ @type length: L{int} or L{None} ++ @param length: Present for compatibility with the L{IEncodable} ++ interface; ignored by this decoder. + + @raise EOFError: Raised when there are not enough bytes available +- from C{strio}. ++ from C{strio}. + +- @raise ValueError: Raised when the name cannot be decoded because it +- contains a compression loop. ++ @raise ValueError: Raised when the name cannot be decoded because ++ it contains a compression loop. + + @raise DNSDecodeError: Raised when the cumulative number of + compression-pointer jumps exceeds the configured limit. + """ ++ context = _decodeContextVar.get() + if context is None: +- context = _decodeContextVar.get() +- if context is None: +- context = _DecodeContext() ++ context = _DecodeContext(maxJumps=self.maxCompressionPointers) + visited = set() + self.name = b"" + off = 0 +@@ -2782,9 +2828,10 @@ class Message(tputil.FancyEqMixin): + + # A single shared counter bounds the total compression-pointer work + # performed across every name in this message. It is installed on +- # the context variable so nested record decoders pick it up without +- # needing to thread it through each signature. +- with _decodeContextVar.set(_DecodeContext()): ++ # the private context variable so nested record decoders pick it up ++ # without needing to thread it through each signature. ++ decodeContext = _DecodeContext(maxJumps=Name.maxCompressionPointers) ++ with _installDecodeContext(decodeContext): + self.queries = [] + for i in range(nqueries): + q = Query() +@@ -2802,8 +2849,6 @@ class Message(tputil.FancyEqMixin): + + for l, n in items: + self.parseRecords(l, n, strio) +- finally: +- _decodeContextVar.reset(token) + + def parseRecords(self, list, num, strio): + for i in range(num): +diff --git a/src/twisted/names/newsfragments/12626.bugfix b/src/twisted/names/newsfragments/12626.bugfix +new file mode 100644 +index 000000000..44896c3e5 +--- /dev/null ++++ b/src/twisted/names/newsfragments/12626.bugfix +@@ -0,0 +1 @@ ++twisted.names was fix for Denial of Service (DoS) attack via resource exhaustion during DNS name decompression. CVE-REFERENCE HERE +\ No newline at end of file +diff --git a/src/twisted/names/test/test_dns.py b/src/twisted/names/test/test_dns.py +index 94aa4a802..9626115ab 100644 +--- a/src/twisted/names/test/test_dns.py ++++ b/src/twisted/names/test/test_dns.py +@@ -356,48 +356,51 @@ class NameTests(unittest.TestCase): + """ + L{Name.decode} raises L{dns.DNSDecodeError} when the number of + compression-pointer dereferences taken for a single message exceeds +- the limit carried by the shared L{dns._DecodeContext}. ++ the limit carried by the shared L{dns._DecodeContext} installed ++ through the private L{dns._decodeContextVar}. + """ + # Five distinct pointers chained end-to-end, terminated by a zero + # label byte. With a maxJumps of three the fourth dereference must + # trip the safety limit. + payload = b"\xc0\x02\xc0\x04\xc0\x06\xc0\x08\x00" + context = dns._DecodeContext(maxJumps=3) +- self.assertRaises( +- dns.DNSDecodeError, +- dns.Name().decode, +- BytesIO(payload), +- None, +- context, +- ) ++ with dns._installDecodeContext(context): ++ self.assertRaises( ++ dns.DNSDecodeError, ++ dns.Name().decode, ++ BytesIO(payload), ++ ) + + def test_compressionPointerCounterIsShared(self): + """ + The L{dns._DecodeContext} counter accumulates across successive + L{Name.decode} calls, so that a message whose individual names are + each within bounds is still rejected when their aggregate exceeds +- the configured limit. ++ the configured limit. This mirrors production: L{Message.decode} ++ invokes L{Name.decode} many times against the same stream under one ++ shared context. + """ + payload = b"\xc0\x02\xc0\x04\x00" + context = dns._DecodeContext(maxJumps=3) + +- stream = BytesIO(payload) +- dns.Name().decode(stream, context=context) +- self.assertEqual(context.jumps, 2) ++ with dns._installDecodeContext(context): ++ stream = BytesIO(payload) ++ dns.Name().decode(stream) ++ self.assertEqual(context.jumps, 2) + +- stream.seek(0) +- self.assertRaises( +- dns.DNSDecodeError, +- dns.Name().decode, +- strio=stream, +- length=None, +- context=context, +- ) ++ stream.seek(0) ++ self.assertRaises( ++ dns.DNSDecodeError, ++ dns.Name().decode, ++ stream, ++ ) + + def test_decodeWithoutContextIsBackwardsCompatible(self): + """ +- L{Name.decode} continues to work when called without a context, +- using a fresh per-call counter so existing callers are unaffected. ++ L{Name.decode} continues to work when called with no active ++ L{dns._decodeContextVar}, using a fresh per-call counter seeded ++ from L{dns.Name.maxCompressionPointers} so existing callers are ++ unaffected. + """ + name = dns.Name() + stream = BytesIO() diff --git a/meta-python/recipes-devtools/python/files/CVE-2026-42304_p5.patch b/meta-python/recipes-devtools/python/files/CVE-2026-42304_p5.patch new file mode 100644 index 0000000000..7c13b42fec --- /dev/null +++ b/meta-python/recipes-devtools/python/files/CVE-2026-42304_p5.patch @@ -0,0 +1,218 @@ +From 4aa0fdb33f2a12db2d234754b9b03d74df8eaf9d Mon Sep 17 00:00:00 2001 +From: Tomas Illuminati +Date: Tue, 21 Apr 2026 17:26:49 -0300 +Subject: [PATCH] names: fix changes + +CVE: CVE-2026-42304 +Upstream-Status: Backport [https://github.com/twisted/twisted/commit/9df6d960d3569751ebb5567093fe1d1d9f63ca54] + +Backport Changes: +- Retained the 25.5.0 Optional and Union typing imports while removing + Final; Sequence remains sourced from collections.abc after p4. + +(cherry picked from commit 9df6d960d3569751ebb5567093fe1d1d9f63ca54) +Signed-off-by: Hetvi Thakar +--- + src/twisted/names/dns.py | 37 +++++++------ + src/twisted/names/test/test_dns.py | 85 +++++++++++++----------------- + 2 files changed, 56 insertions(+), 66 deletions(-) + +diff --git a/src/twisted/names/dns.py b/src/twisted/names/dns.py +index 869ffec76..ca5079454 100644 +--- a/src/twisted/names/dns.py ++++ b/src/twisted/names/dns.py +@@ -20,7 +20,7 @@ from collections.abc import Sequence + from contextlib import contextmanager + from io import BytesIO + from itertools import chain +-from typing import Final, Optional, SupportsInt, Union, overload ++from typing import Optional, SupportsInt, Union, overload + + from zope.interface import Attribute, Interface, implementer + +@@ -448,15 +448,6 @@ def readPrecisely(file, l): + return buff + + +-MAX_COMPRESSION_POINTERS_PER_MESSAGE: Final = 1000 +-""" +-Cap the total number of compression-pointer dereferences performed while +-decoding a single DNS message. A hostile peer can otherwise craft a packet +-in which every record name chases a long compression chain, forcing +-C{O(N*M)} work and stalling the reactor. +-""" +- +- + class DNSDecodeError(ValueError): + """ + Raised when a DNS message cannot be decoded because it violates a +@@ -484,7 +475,7 @@ class _DecodeContext: + + __slots__ = ("jumps", "maxJumps") + +- def __init__(self, maxJumps: int = MAX_COMPRESSION_POINTERS_PER_MESSAGE) -> None: ++ def __init__(self, maxJumps: int = 1000) -> None: + self.jumps = 0 + self.maxJumps = maxJumps + +@@ -644,16 +635,15 @@ class Name: + @ivar name: A byte string giving the name. + @type name: L{bytes} + +- @cvar maxCompressionPointers: Per-message cap on the total number of ++ @ivar maxCompressionPointers: Per-message cap on the total number of + compression-pointer dereferences L{decode} will follow before +- raising L{DNSDecodeError}. Defined as a class attribute so +- subclasses (and, in the future, individual instances) may override +- it to tune the trade-off between tolerance for legitimately +- verbose messages and resistance to denial-of-service attacks. +- @type maxCompressionPointers: L{int} ++ raising L{DNSDecodeError}. Defaults to C{1000}. Override it on ++ a subclass or individual instance to tune the trade-off between ++ tolerance for legitimately verbose messages and resistance to ++ denial-of-service attacks. + """ + +- maxCompressionPointers: int = MAX_COMPRESSION_POINTERS_PER_MESSAGE ++ maxCompressionPointers: int = 1000 + + def __init__(self, name: bytes | str = b""): + """ +@@ -2610,8 +2600,17 @@ class Message(tputil.FancyEqMixin): + header fields. + @ivar _sectionNames: The names of attributes representing the record + sections of this message. ++ ++ @ivar maxCompressionPointers: Per-message cap on the total number of ++ compression-pointer dereferences L{decode} will follow across every ++ name in the message before raising L{DNSDecodeError}. Defaults to ++ C{1000}. Override it on a subclass or individual instance to tune ++ the trade-off between tolerance for legitimately verbose messages ++ and resistance to denial-of-service attacks. + """ + ++ maxCompressionPointers: int = 1000 ++ + compareAttributes = ( + "id", + "answer", +@@ -2830,7 +2829,7 @@ class Message(tputil.FancyEqMixin): + # performed across every name in this message. It is installed on + # the private context variable so nested record decoders pick it up + # without needing to thread it through each signature. +- decodeContext = _DecodeContext(maxJumps=Name.maxCompressionPointers) ++ decodeContext = _DecodeContext(maxJumps=self.maxCompressionPointers) + with _installDecodeContext(decodeContext): + self.queries = [] + for i in range(nqueries): +diff --git a/src/twisted/names/test/test_dns.py b/src/twisted/names/test/test_dns.py +index 9626115ab..3be6b4546 100644 +--- a/src/twisted/names/test/test_dns.py ++++ b/src/twisted/names/test/test_dns.py +@@ -354,60 +354,51 @@ class NameTests(unittest.TestCase): + + def test_rejectTooManyCompressionPointers(self): + """ +- L{Name.decode} raises L{dns.DNSDecodeError} when the number of +- compression-pointer dereferences taken for a single message exceeds +- the limit carried by the shared L{dns._DecodeContext} installed +- through the private L{dns._decodeContextVar}. +- """ +- # Five distinct pointers chained end-to-end, terminated by a zero +- # label byte. With a maxJumps of three the fourth dereference must +- # trip the safety limit. ++ L{Name.decode} raises L{dns.DNSDecodeError} when it would have to ++ follow more than L{Name.maxCompressionPointers} compression ++ pointers to finish decoding a name. ++ """ ++ # Four distinct pointers chained end-to-end, terminated by a zero ++ # label byte. With maxCompressionPointers of three the fourth ++ # dereference must trip the safety limit. + payload = b"\xc0\x02\xc0\x04\xc0\x06\xc0\x08\x00" +- context = dns._DecodeContext(maxJumps=3) +- with dns._installDecodeContext(context): +- self.assertRaises( +- dns.DNSDecodeError, +- dns.Name().decode, +- BytesIO(payload), +- ) ++ name = dns.Name() ++ name.maxCompressionPointers = 3 ++ self.assertRaises( ++ dns.DNSDecodeError, name.decode, BytesIO(payload) ++ ) + +- def test_compressionPointerCounterIsShared(self): ++ def test_decodeRecoversAfterDNSDecodeError(self): + """ +- The L{dns._DecodeContext} counter accumulates across successive +- L{Name.decode} calls, so that a message whose individual names are +- each within bounds is still rejected when their aggregate exceeds +- the configured limit. This mirrors production: L{Message.decode} +- invokes L{Name.decode} many times against the same stream under one +- shared context. ++ After L{Name.decode} raises L{dns.DNSDecodeError}, subsequent ++ L{Name.decode} calls continue to work. No residual ++ compression-pointer counter leaks across calls, so a legitimate ++ name decoded right after a hostile one still succeeds. + """ +- payload = b"\xc0\x02\xc0\x04\x00" +- context = dns._DecodeContext(maxJumps=3) +- +- with dns._installDecodeContext(context): +- stream = BytesIO(payload) +- dns.Name().decode(stream) +- self.assertEqual(context.jumps, 2) +- +- stream.seek(0) +- self.assertRaises( +- dns.DNSDecodeError, +- dns.Name().decode, +- stream, +- ) ++ # First, force a DNSDecodeError by decoding a payload that ++ # exceeds the configured limit. ++ hostile = dns.Name() ++ hostile.maxCompressionPointers = 3 ++ self.assertRaises( ++ dns.DNSDecodeError, ++ hostile.decode, ++ BytesIO(b"\xc0\x02\xc0\x04\xc0\x06\xc0\x08\x00"), ++ ) + +- def test_decodeWithoutContextIsBackwardsCompatible(self): +- """ +- L{Name.decode} continues to work when called with no active +- L{dns._decodeContextVar}, using a fresh per-call counter seeded +- from L{dns.Name.maxCompressionPointers} so existing callers are +- unaffected. +- """ +- name = dns.Name() ++ # Then prove the process has not been poisoned: a legitimate ++ # name still decodes normally, both with a fresh instance and ++ # with the instance that just errored. + stream = BytesIO() + dns.Name(b"example.org").encode(stream) ++ ++ fresh = dns.Name() + stream.seek(0) +- name.decode(stream) +- self.assertEqual(name.name, b"example.org") ++ fresh.decode(stream) ++ self.assertEqual(fresh.name, b"example.org") ++ ++ stream.seek(0) ++ hostile.decode(stream) ++ self.assertEqual(hostile.name, b"example.org") + + def test_equality(self): + """ +@@ -823,7 +814,7 @@ class MessageTests(unittest.SynchronousTestCase): + L{Message.decode} installs a shared compression-pointer counter and + raises L{dns.DNSDecodeError} when the aggregate number of pointer + dereferences across every record in the message exceeds +- L{dns.MAX_COMPRESSION_POINTERS_PER_MESSAGE}. ++ L{dns.Message.maxCompressionPointers}. + """ + chainLength = 100 + numRecords = 8000 diff --git a/meta-python/recipes-devtools/python/files/CVE-2026-42304_p6.patch b/meta-python/recipes-devtools/python/files/CVE-2026-42304_p6.patch new file mode 100644 index 0000000000..2df57691e9 --- /dev/null +++ b/meta-python/recipes-devtools/python/files/CVE-2026-42304_p6.patch @@ -0,0 +1,23 @@ +From 2dc37caa8af3559e14fc1a1b36b049851d36943c Mon Sep 17 00:00:00 2001 +From: Adi Roiban +Date: Wed, 29 Apr 2026 15:55:05 +0100 +Subject: [PATCH] Update src/twisted/names/newsfragments/12626.bugfix + +CVE: CVE-2026-42304 +Upstream-Status: Backport [https://github.com/twisted/twisted/commit/9ca319ebf61386dd33354c4ade3946ef84ad58fb] + +(cherry picked from commit 9ca319ebf61386dd33354c4ade3946ef84ad58fb) +Signed-off-by: Hetvi Thakar +--- + src/twisted/names/newsfragments/12626.bugfix | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/twisted/names/newsfragments/12626.bugfix b/src/twisted/names/newsfragments/12626.bugfix +index 44896c3e5..179b92d83 100644 +--- a/src/twisted/names/newsfragments/12626.bugfix ++++ b/src/twisted/names/newsfragments/12626.bugfix +@@ -1 +1 @@ +-twisted.names was fix for Denial of Service (DoS) attack via resource exhaustion during DNS name decompression. CVE-REFERENCE HERE +\ No newline at end of file ++twisted.names was fix for Denial of Service (DoS) attack via resource exhaustion during DNS name decompression. CVE-2026-42304 +\ No newline at end of file diff --git a/meta-python/recipes-devtools/python/python3-twisted_25.5.0.bb b/meta-python/recipes-devtools/python/python3-twisted_25.5.0.bb index 8ce5740e0b..3b49f56093 100644 --- a/meta-python/recipes-devtools/python/python3-twisted_25.5.0.bb +++ b/meta-python/recipes-devtools/python/python3-twisted_25.5.0.bb @@ -6,6 +6,14 @@ HOMEPAGE = "https://twisted.org" LICENSE = "MIT" LIC_FILES_CHKSUM = "file://LICENSE;md5=5316a448a61a38d722c291f78d915d11" +SRC_URI += "file://CVE-2026-42304_p1.patch \ + file://CVE-2026-42304_p2.patch \ + file://CVE-2026-42304_p3.patch \ + file://CVE-2026-42304_p4.patch \ + file://CVE-2026-42304_p5.patch \ + file://CVE-2026-42304_p6.patch \ + " + SRC_URI[sha256sum] = "1deb272358cb6be1e3e8fc6f9c8b36f78eb0fa7c2233d2dbe11ec6fee04ea316" CVE_PRODUCT = "twisted"