已合并
Fix CVE-2026-32274 #41
meiwei-space创建于 7月24日
Fix CVE-2026-32274 #41
已合并
共 2 个文件变更+177-1
| @@ -0,0 +1,172 @@ | |||
| 1 | +From 4937fe6cf241139ddbfc16b0bdbb5b422798909d Mon Sep 17 00:00:00 2001 | ||
| 2 | +From: Jelle Zijlstra <jelle.zijlstra@gmail.com> | ||
| 3 | +Date: Wed, 11 Mar 2026 19:57:24 -0700 | ||
| 4 | +Subject: [PATCH] Fix some shenanigans with the cache file and IPython (#5038) | ||
| 5 | + | ||
| 6 | +--- | ||
| 7 | + src/black/handle_ipynb_magics.py | 59 ++++++++++++++++++++++++++++++++++++++++++-- | ||
| 8 | + src/black/mode.py | 7 +++---- | ||
| 9 | + tests/test_black.py | 9 +++++++++ | ||
| 10 | + tests/test_ipynb.py | 20 ++++++++++++++++--- | ||
| 11 | + 4 files changed, 85 insertions(+), 10 deletions(-) | ||
| 12 | + | ||
| 13 | +diff --git a/src/black/handle_ipynb_magics.py b/src/black/handle_ipynb_magics.py | ||
| 14 | +index c84fe6219fb..e12537d8309 100644 | ||
| 15 | +--- a/src/black/handle_ipynb_magics.py | ||
| 16 | ++++ b/src/black/handle_ipynb_magics.py | ||
| 17 | + import collections | ||
| 18 | + import dataclasses | ||
| 19 | + import secrets | ||
| 20 | ++import string | ||
| 21 | ++from collections.abc import Collection | ||
| 22 | + import sys | ||
| 23 | + from functools import lru_cache | ||
| 24 | + from importlib.util import find_spec | ||
| 25 | + | ||
| 26 | + return transformed, replacements | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +-def get_token(src: str, magic: str) -> str: | ||
| 30 | ++def create_token(n_chars: int) -> str: | ||
| 31 | ++ """Create a randomly generated token that is n_chars characters long.""" | ||
| 32 | ++ assert n_chars > 0 | ||
| 33 | ++ if n_chars == 1: | ||
| 34 | ++ return secrets.choice(string.ascii_letters) | ||
| 35 | ++ if n_chars < 4: | ||
| 36 | ++ return "_" + "".join( | ||
| 37 | ++ secrets.choice(string.ascii_letters + string.digits + "_") | ||
| 38 | ++ for _ in range(n_chars - 1) | ||
| 39 | ++ ) | ||
| 40 | ++ n_bytes = max(n_chars // 2 - 1, 1) | ||
| 41 | ++ token = secrets.token_hex(n_bytes) | ||
| 42 | ++ if len(token) + 3 > n_chars: | ||
| 43 | ++ n_bytes = max(0, n_chars - 3) | ||
| 44 | ++ token = secrets.token_hex(n_bytes)[: n_chars - 3] | ||
| 45 | ++ return f'b"{token}"' | ||
| 46 | ++ | ||
| 47 | ++ | ||
| 48 | ++def get_token(src: str, magic: str, existing_tokens: Collection[str] = ()) -> str: | ||
| 49 | + """Return randomly generated token to mask IPython magic with. | ||
| 50 | + | ||
| 51 | + For example, if 'magic' was `%matplotlib inline`, then a possible | ||
| 52 | +- token to mask it with would be `"43fdd17f7e5ddc83"`. The token | ||
| 53 | +- will be the same length as the magic, and we make sure that it was | ||
| 54 | +- not already present anywhere else in the cell. | ||
| 55 | ++ token is `b"1234"` or `b"abcd"`. | ||
| 56 | ++ | ||
| 57 | ++ Args: | ||
| 58 | ++ src: The source string to search for existing tokens. | ||
| 59 | ++ magic: The magic command string to mask. | ||
| 60 | ++ existing_tokens: Collection of existing tokens to avoid collision. | ||
| 61 | ++ | ||
| 62 | ++ Returns: | ||
| 63 | ++ A randomly generated token to mask `magic`. | ||
| 64 | + """ | ||
| 65 | +- assert magic | ||
| 66 | +- nbytes = max(len(magic) // 2 - 1, 1) | ||
| 67 | +- token = TOKEN_HEX(nbytes) | ||
| 68 | ++ n_chars = len(magic) | ||
| 69 | ++ token = create_token(n_chars) | ||
| 70 | + counter = 0 | ||
| 71 | +- while token in src: | ||
| 72 | +- token = TOKEN_HEX(nbytes) | ||
| 73 | ++ while token in src or token in existing_tokens: | ||
| 74 | ++ token = create_token(n_chars) | ||
| 75 | + counter += 1 | ||
| 76 | + if counter > 100: | ||
| 77 | + raise AssertionError( | ||
| 78 | +- "INTERNAL ERROR: Black was not able to replace IPython magic. " | ||
| 79 | +- "Please report a bug on https://github.com/psf/black/issues. " | ||
| 80 | +- f"The magic might be helpful: {magic}" | ||
| 81 | ++ f"Could not generate a token for magic {magic!r}" | ||
| 82 | + ) from None | ||
| 83 | +- if len(token) + 2 < len(magic): | ||
| 84 | +- token = f"{token}." | ||
| 85 | +- return f'"{token}"' | ||
| 86 | ++ return f'b"{token}"' | ||
| 87 | + | ||
| 88 | + | ||
| 89 | + def replace_cell_magics(src: str) -> Tuple[str, List[Replacement]]: | ||
| 90 | + | ||
| 91 | + The replacement, along with the transformed code, are returned. | ||
| 92 | + """ | ||
| 93 | + replacements = [] | ||
| 94 | ++ existing_tokens: set[str] = set() | ||
| 95 | + magic_finder = MagicFinder() | ||
| 96 | + magic_finder.visit(ast.parse(src)) | ||
| 97 | + new_srcs = [] | ||
| 98 | + | ||
| 99 | + offsets_and_magics[0].col_offset, | ||
| 100 | + offsets_and_magics[0].magic, | ||
| 101 | + ) | ||
| 102 | +- mask = get_token(src, magic) | ||
| 103 | ++ mask = get_token(src, magic, existing_tokens) | ||
| 104 | + replacements.append(Replacement(mask=mask, src=magic)) | ||
| 105 | ++ existing_tokens.add(mask) | ||
| 106 | + line = line[:col_offset] + mask | ||
| 107 | + new_srcs.append(line) | ||
| 108 | + return "\n".join(new_srcs), replacements | ||
| 109 | + | ||
| 110 | + foo = bar | ||
| 111 | + """ | ||
| 112 | + for replacement in replacements: | ||
| 113 | +- src = src.replace(replacement.mask, replacement.src) | ||
| 114 | ++ if src.count(replacement.mask) != 1: | ||
| 115 | ++ raise NothingChanged | ||
| 116 | ++ src = src.replace(replacement.mask, replacement.src, 1) | ||
| 117 | + return src | ||
| 118 | + | ||
| 119 | + | ||
| 120 | + | ||
| 121 | +diff --git a/src/black/mode.py b/src/black/mode.py | ||
| 122 | +index 51a825f30c4..2ed82d55885 100644 | ||
| 123 | +--- a/src/black/mode.py | ||
| 124 | ++++ b/src/black/mode.py | ||
| 125 | + + "@" | ||
| 126 | + + ",".join(sorted(self.python_cell_magics)) | ||
| 127 | + ) | ||
| 128 | +- if len(features_and_magics) > _MAX_CACHE_KEY_PART_LENGTH: | ||
| 129 | +- features_and_magics = sha256(features_and_magics.encode()).hexdigest()[ | ||
| 130 | +- :_MAX_CACHE_KEY_PART_LENGTH | ||
| 131 | +- ] | ||
| 132 | ++ features_and_magics = sha256(features_and_magics.encode()).hexdigest()[ | ||
| 133 | ++ :_MAX_CACHE_KEY_PART_LENGTH | ||
| 134 | ++ ] | ||
| 135 | + parts = [ | ||
| 136 | + version_str, | ||
| 137 | + str(self.line_length), | ||
| 138 | + | ||
| 139 | +diff --git a/tests/test_black.py b/tests/test_black.py | ||
| 140 | +index a8152e5cc10..ee04a7e9408 100644 | ||
| 141 | +--- a/tests/test_black.py | ||
| 142 | ++++ b/tests/test_black.py | ||
| 143 | + | ||
| 144 | +diff --git a/tests/test_ipynb.py b/tests/test_ipynb.py | ||
| 145 | +index 12afb971e2c..aadf7058a83 100644 | ||
| 146 | +--- a/tests/test_ipynb.py | ||
| 147 | ++++ b/tests/test_ipynb.py | ||
| 148 | + from typing import ContextManager | ||
| 149 | + | ||
| 150 | + import pytest | ||
| 151 | +-from _pytest.monkeypatch import MonkeyPatch | ||
| 152 | + from click.testing import CliRunner | ||
| 153 | ++from pytest import MonkeyPatch | ||
| 154 | + | ||
| 155 | + from black import ( | ||
| 156 | + Mode, | ||
| 157 | + | ||
| 158 | + format_file_in_place, | ||
| 159 | + main, | ||
| 160 | + ) | ||
| 161 | +-from black.handle_ipynb_magics import jupyter_dependencies_are_installed | ||
| 162 | ++from black.handle_ipynb_magics import ( | ||
| 163 | ++ Replacement, | ||
| 164 | ++ create_token, | ||
| 165 | ++ jupyter_dependencies_are_installed, | ||
| 166 | ++ unmask_cell, | ||
| 167 | ++) | ||
| 168 | + from tests.util import DATA_DIR, get_case_path, read_jupyter_notebook | ||
| 169 | + | ||
| 170 | + with contextlib.suppress(ModuleNotFoundError): | ||
| 171 | +-- | ||
| 172 | +2.37.2.windows.2 | ||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | Name: python-%{pypi_name} | 4 | Name: python-%{pypi_name} |
| 5 | Version: 24.2.0 | 5 | Version: 24.2.0 |
| 6 | -Release: 2 | 6 | +Release: 3 |
| 7 | Summary: The uncompromising code formatter | 7 | Summary: The uncompromising code formatter |
| 8 | License: MIT | 8 | License: MIT |
| 9 | URL: https://github.com/psf/black | 9 | URL: https://github.com/psf/black |
| @@ -12,6 +12,7 @@ Source0: %{url}/archive/%{version}/%{pypi_name}-%{version}.tar.gz | |||
| 12 | BuildArch: noarch | 12 | BuildArch: noarch |
| 13 | 13 | ||
| 14 | Patch0: CVE-2024-21503.patch | 14 | Patch0: CVE-2024-21503.patch |
| 15 | +Patch1: CVE-2026-32274.patch | ||
| 15 | 16 | ||
| 16 | BuildRequires: python3-devel | 17 | BuildRequires: python3-devel |
| 17 | 18 | ||
| @@ -67,6 +68,9 @@ done | |||
| 67 | %{python3_sitelib}/blib2to3/* | 68 | %{python3_sitelib}/blib2to3/* |
| 68 | 69 | ||
| 69 | %changelog | 70 | %changelog |
| 71 | +* Fri Jul 24 2026 zhangchaorui <zhangchaorui@kylinos.cn> - 24.2.0-3 | ||
| 72 | +- Fix CVE-2026-32274: Cache file path manipulation vulnerability | ||
| 73 | + | ||
| 70 | * Wed Apr 17 2024 yanjianqing <yanjianqing@kylinos.cn> - 24.2.0-2 | 74 | * Wed Apr 17 2024 yanjianqing <yanjianqing@kylinos.cn> - 24.2.0-2 |
| 71 | - Fix CVE-2024-21503 | 75 | - Fix CVE-2024-21503 |
| 72 | 76 | ||