已开启
Fix CVE-2025-12060 in python-Keras for openEuler-24.03-LTS-SP1 #27
jixiang创建于 7月20日
Fix CVE-2025-12060 in python-Keras for openEuler-24.03-LTS-SP1 #27
已开启
共 2 个文件变更+253-1
| @@ -0,0 +1,248 @@ | |||
| 1 | +From 609cb47691f188133935254a8f7a98c1486e1bd4 Mon Sep 17 00:00:00 2001 | ||
| 2 | +From: chenjianhu <chenjianhu@kylinos.cn> | ||
| 3 | +Date: Tue, 2 Dec 2025 06:05:47 +0800 | ||
| 4 | +Subject: [PATCH] Fix CVE-2025-12060 | ||
| 5 | + | ||
| 6 | +Use filter="data" option of TarFile.extractall. (#21760) | ||
| 7 | +For Python versions between 3.12 (inclusive) and 3.14 (exclusive). | ||
| 8 | + | ||
| 9 | +The "data" filter performs a number of additional checks on links | ||
| 10 | +and paths. The `filter` option was added in Python 3.12. | ||
| 11 | +The `filter="data"` option became the default in Python 3.14. | ||
| 12 | + | ||
| 13 | +Also: | ||
| 14 | +- added similar path filtering when extracting zip archives | ||
| 15 | +- shared the extraction code between `file_utils` and `saving_lib` | ||
| 16 | + | ||
| 17 | +--- | ||
| 18 | + keras/saving/saving_lib.py | 2 +- | ||
| 19 | + keras/utils/data_utils.py | 63 +++++++++++++++++++++----- | ||
| 20 | + keras/utils/data_utils_test.py | 82 ++++++++++++++++++++++++++++++++++ | ||
| 21 | + 3 files changed, 134 insertions(+), 13 deletions(-) | ||
| 22 | + | ||
| 23 | +diff --git a/keras/saving/saving_lib.py b/keras/saving/saving_lib.py | ||
| 24 | +index 9e52ecf..7def015 100644 | ||
| 25 | +--- a/keras/saving/saving_lib.py | ||
| 26 | ++++ b/keras/saving/saving_lib.py | ||
| 27 | + class DiskIOStore: | ||
| 28 | + if self.archive: | ||
| 29 | + self.tmp_dir = _get_temp_dir() | ||
| 30 | + if self.mode == "r": | ||
| 31 | +- self.archive.extractall(path=self.tmp_dir) | ||
| 32 | ++ keras.utils.data_utils.extract_open_archive(self.archive, self.tmp_dir) | ||
| 33 | + self.working_dir = tf.io.gfile.join(self.tmp_dir, self.root_path) | ||
| 34 | + if self.mode == "w": | ||
| 35 | + tf.io.gfile.makedirs(self.working_dir) | ||
| 36 | +diff --git a/keras/utils/data_utils.py b/keras/utils/data_utils.py | ||
| 37 | +index 3856d42..e082e2e 100644 | ||
| 38 | +--- a/keras/utils/data_utils.py | ||
| 39 | ++++ b/keras/utils/data_utils.py | ||
| 40 | + import pathlib | ||
| 41 | + import queue | ||
| 42 | + import random | ||
| 43 | + import shutil | ||
| 44 | ++import sys | ||
| 45 | + import tarfile | ||
| 46 | + import threading | ||
| 47 | + import time | ||
| 48 | + def _is_link_in_dir(info, base): | ||
| 49 | + return _is_path_in_dir(info.linkname, base_dir=tip) | ||
| 50 | + | ||
| 51 | + | ||
| 52 | +-def _filter_safe_paths(members): | ||
| 53 | ++def filter_safe_zipinfos(members): | ||
| 54 | + base_dir = _resolve_path(".") | ||
Y | |||
| 55 | + for finfo in members: | ||
| 56 | + valid_path = False | ||
| 57 | +- if finfo.issym() or finfo.islnk(): | ||
| 58 | +- if _is_path_in_dir(finfo.name, base_dir) and _is_link_in_dir( | ||
| 59 | +- finfo, base_dir | ||
| 60 | +- ): | ||
| 61 | ++ if _is_path_in_dir(finfo.filename, base_dir): | ||
| 62 | +- valid_path = True | ||
| 63 | +- yield finfo | ||
| 64 | ++ valid_path = True | ||
| 65 | ++ yield finfo | ||
| 66 | +- elif _is_path_in_dir(finfo.name, base_dir): | ||
| 67 | +- valid_path = True | ||
| 68 | +- yield finfo | ||
| 69 | ++ if not valid_path: | ||
| 70 | ++ warnings.warn( | ||
| 71 | ++ "Skipping invalid path during archive extraction: " | ||
| 72 | ++ f"'{finfo.filename}'.", | ||
| 73 | ++ stacklevel=2, | ||
| 74 | ++ ) | ||
| 75 | ++ | ||
| 76 | ++ | ||
| 77 | ++def filter_safe_tarinfos(members): | ||
| 78 | ++ base_dir = _resolve_path(".") | ||
Y 此处base_dir = _resolve_path(".")可能涉及CVE-2026-11816问题,需要参考CVE-2026-11816补丁重新适配 ![]() ![]() | |||
| 79 | ++ for finfo in members: | ||
| 80 | ++ valid_path = False | ||
| 81 | ++ if finfo.issym() or finfo.islnk(): | ||
| 82 | ++ if _is_path_in_dir(finfo.name, base_dir) and _is_link_in_dir( | ||
| 83 | ++ finfo, base_dir | ||
| 84 | ++ ): | ||
| 85 | ++ valid_path = True | ||
| 86 | ++ yield finfo | ||
| 87 | ++ elif _is_path_in_dir(finfo.name, base_dir): | ||
| 88 | ++ valid_path = True | ||
| 89 | ++ yield finfo | ||
| 90 | + if not valid_path: | ||
| 91 | + warnings.warn( | ||
| 92 | + "Skipping invalid path during archive extraction: " | ||
| 93 | +- f"'{finfo.name}'." | ||
| 94 | ++ f"'{finfo.name}'.", | ||
| 95 | ++ stacklevel=2, | ||
| 96 | + ) | ||
| 97 | + | ||
| 98 | + | ||
| 99 | ++def extract_open_archive(archive, path="."): | ||
| 100 | ++ """Extracts an open tar or zip archive to the provided directory. | ||
| 101 | ++ | ||
| 102 | ++ This function filters unsafe paths during extraction. | ||
| 103 | ++ | ||
| 104 | ++ Args: | ||
| 105 | ++ archive: The archive object, either a `TarFile` or a `ZipFile`. | ||
| 106 | ++ path: Where to extract the archive file. | ||
| 107 | ++ """ | ||
| 108 | ++ if isinstance(archive, zipfile.ZipFile): | ||
| 109 | ++ # Zip archive. | ||
| 110 | ++ archive.extractall( | ||
| 111 | ++ path, members=filter_safe_zipinfos(archive.infolist()) | ||
| 112 | ++ ) | ||
| 113 | ++ else: | ||
| 114 | ++ # Tar archive. | ||
| 115 | ++ extractall_kwargs = {} | ||
| 116 | ++ # The `filter="data"` option was added in Python 3.12. It became the | ||
| 117 | ++ # default starting from Python 3.14. So we only specify it between | ||
| 118 | ++ # those two versions. | ||
| 119 | ++ if sys.version_info >= (3, 12) and sys.version_info < (3, 14): | ||
| 120 | ++ extractall_kwargs = {"filter": "data"} | ||
| 121 | ++ archive.extractall( | ||
| 122 | ++ path, | ||
| 123 | ++ members=filter_safe_tarinfos(archive), | ||
| 124 | ++ **extractall_kwargs, | ||
| 125 | ++ ) | ||
| 126 | ++ | ||
| 127 | ++ | ||
| 128 | + def _extract_archive(file_path, path=".", archive_format="auto"): | ||
| 129 | + """Extracts an archive if it matches tar, tar.gz, tar.bz, or zip formats. | ||
| 130 | + | ||
| 131 | + def _extract_archive(file_path, path=".", archive_format="auto"): | ||
| 132 | + if is_match_fn(file_path): | ||
| 133 | + with open_fn(file_path) as archive: | ||
| 134 | + try: | ||
| 135 | +- if zipfile.is_zipfile(file_path): | ||
| 136 | +- # Zip archive. | ||
| 137 | +- archive.extractall(path) | ||
| 138 | +- else: | ||
| 139 | +- # Tar archive, perhaps unsafe. Filter paths. | ||
| 140 | +- archive.extractall( | ||
| 141 | +- path, members=_filter_safe_paths(archive) | ||
| 142 | +- ) | ||
| 143 | ++ extract_open_archive(archive, path) | ||
| 144 | + except (tarfile.TarError, RuntimeError, KeyboardInterrupt): | ||
| 145 | + if os.path.exists(path): | ||
| 146 | + if os.path.isfile(path): | ||
| 147 | +diff --git a/keras/utils/data_utils_test.py b/keras/utils/data_utils_test.py | ||
| 148 | +index 093281c..c1f6717 100644 | ||
| 149 | +--- a/keras/utils/data_utils_test.py | ||
| 150 | ++++ b/keras/utils/data_utils_test.py | ||
| 151 | + | ||
| 152 | + import os | ||
| 153 | + import tarfile | ||
| 154 | + import urllib | ||
| 155 | ++import shutil | ||
| 156 | + import zipfile | ||
| 157 | + from itertools import cycle | ||
| 158 | ++from unittest.mock import patch | ||
| 159 | + | ||
| 160 | + import numpy as np | ||
| 161 | + import tensorflow.compat.v2 as tf | ||
| 162 | + class TestGetFile(tf.test.TestCase): | ||
| 163 | + ) | ||
| 164 | + | ||
| 165 | + | ||
| 166 | ++class FilterSafePathsTest(tf.test.TestCase): | ||
| 167 | ++ def setUp(self): | ||
| 168 | ++ self.base_dir = os.path.join(os.getcwd(), "temp_dir") | ||
| 169 | ++ os.makedirs(self.base_dir, exist_ok=True) | ||
| 170 | ++ self.tar_path = os.path.join(self.base_dir, "test.tar") | ||
| 171 | ++ | ||
| 172 | ++ def tearDown(self): | ||
| 173 | ++ os.remove(self.tar_path) | ||
| 174 | ++ shutil.rmtree(self.base_dir) | ||
| 175 | ++ | ||
| 176 | ++ def test_member_within_base_dir(self): | ||
| 177 | ++ """Test a member within the base directory.""" | ||
| 178 | ++ with tarfile.open(self.tar_path, "w") as tar: | ||
| 179 | ++ tar.add(__file__, arcname="safe_path.txt") | ||
| 180 | ++ with tarfile.open(self.tar_path, "r") as tar: | ||
| 181 | ++ members = list(data_utils.filter_safe_tarinfos(tar.getmembers())) | ||
| 182 | ++ self.assertEqual(len(members), 1) | ||
| 183 | ++ self.assertEqual(members[0].name, "safe_path.txt") | ||
| 184 | ++ | ||
| 185 | ++ def test_symlink_within_base_dir(self): | ||
| 186 | ++ """Test a symlink pointing within the base directory.""" | ||
| 187 | ++ symlink_path = os.path.join(self.base_dir, "symlink.txt") | ||
| 188 | ++ target_path = os.path.join(self.base_dir, "target.txt") | ||
| 189 | ++ with open(target_path, "w") as f: | ||
| 190 | ++ f.write("target") | ||
| 191 | ++ os.symlink(target_path, symlink_path) | ||
| 192 | ++ with tarfile.open(self.tar_path, "w") as tar: | ||
| 193 | ++ tar.add(symlink_path, arcname="symlink.txt") | ||
| 194 | ++ with tarfile.open(self.tar_path, "r") as tar: | ||
| 195 | ++ members = list(data_utils.filter_safe_tarinfos(tar.getmembers())) | ||
| 196 | ++ self.assertEqual(len(members), 1) | ||
| 197 | ++ self.assertEqual(members[0].name, "symlink.txt") | ||
| 198 | ++ os.remove(symlink_path) | ||
| 199 | ++ os.remove(target_path) | ||
| 200 | ++ | ||
| 201 | ++ def test_invalid_path_warning(self): | ||
| 202 | ++ """Test warning for an invalid path during archive extraction.""" | ||
| 203 | ++ invalid_path = os.path.join(os.getcwd(), "invalid.txt") | ||
| 204 | ++ with open(invalid_path, "w") as f: | ||
| 205 | ++ f.write("invalid") | ||
| 206 | ++ with tarfile.open(self.tar_path, "w") as tar: | ||
| 207 | ++ tar.add( | ||
| 208 | ++ invalid_path, arcname="../../invalid.txt" | ||
| 209 | ++ ) # Path intended to be outside of base dir | ||
| 210 | ++ with tarfile.open(self.tar_path, "r") as tar: | ||
| 211 | ++ with patch("warnings.warn") as mock_warn: | ||
| 212 | ++ _ = list(data_utils.filter_safe_tarinfos(tar.getmembers())) | ||
| 213 | ++ warning_msg = ( | ||
| 214 | ++ "Skipping invalid path during archive extraction: " | ||
| 215 | ++ "'../../invalid.txt'." | ||
| 216 | ++ ) | ||
| 217 | ++ mock_warn.assert_called_with(warning_msg, stacklevel=2) | ||
| 218 | ++ os.remove(invalid_path) | ||
| 219 | ++ | ||
| 220 | ++ def test_symbolic_link_in_base_dir(self): | ||
| 221 | ++ """symbolic link within the base directory is correctly processed.""" | ||
| 222 | ++ symlink_path = os.path.join(self.base_dir, "symlink.txt") | ||
| 223 | ++ target_path = os.path.join(self.base_dir, "target.txt") | ||
| 224 | ++ | ||
| 225 | ++ # Create a target file and then a symbolic link pointing to it. | ||
| 226 | ++ with open(target_path, "w") as f: | ||
| 227 | ++ f.write("target") | ||
| 228 | ++ os.symlink(target_path, symlink_path) | ||
| 229 | ++ | ||
| 230 | ++ # Add the symbolic link to the tar archive. | ||
| 231 | ++ with tarfile.open(self.tar_path, "w") as tar: | ||
| 232 | ++ tar.add(symlink_path, arcname="symlink.txt") | ||
| 233 | ++ | ||
| 234 | ++ with tarfile.open(self.tar_path, "r") as tar: | ||
| 235 | ++ members = list(data_utils.filter_safe_tarinfos(tar.getmembers())) | ||
| 236 | ++ self.assertEqual(len(members), 1) | ||
| 237 | ++ self.assertEqual(members[0].name, "symlink.txt") | ||
| 238 | ++ self.assertTrue( | ||
| 239 | ++ members[0].issym() | ||
| 240 | ++ ) # Explicitly assert it's a symbolic link. | ||
| 241 | ++ | ||
| 242 | ++ os.remove(symlink_path) | ||
| 243 | ++ os.remove(target_path) | ||
| 244 | ++ | ||
| 245 | ++ | ||
| 246 | + class TestSequence(keras.utils.data_utils.Sequence): | ||
| 247 | + def __init__(self, shape, value=1.0): | ||
| 248 | + self.shape = shape | ||
| @@ -1,7 +1,7 @@ | |||
| 1 | %global _empty_manifest_terminate_build 0 | 1 | %global _empty_manifest_terminate_build 0 |
| 2 | Name: python-Keras | 2 | Name: python-Keras |
| 3 | Version: 2.12.0 | 3 | Version: 2.12.0 |
| 4 | -Release: 3 | 4 | +Release: 4 |
| 5 | Summary: Deep Learning for humans | 5 | Summary: Deep Learning for humans |
| 6 | License: Apache-2.0 | 6 | License: Apache-2.0 |
| 7 | URL: https://github.com/keras-team/keras | 7 | URL: https://github.com/keras-team/keras |
| @@ -10,6 +10,7 @@ BuildArch: noarch | |||
| 10 | 10 | ||
| 11 | Patch0: backport-CVE-2025-12058.patch | 11 | Patch0: backport-CVE-2025-12058.patch |
| 12 | Patch1: backport-CVE-2026-12482.patch | 12 | Patch1: backport-CVE-2026-12482.patch |
| 13 | +Patch2: backport-Fix-CVE-2025-12060.patch | ||
| 13 | 14 | ||
| 14 | Requires: python3-numpy | 15 | Requires: python3-numpy |
| 15 | Requires: python3-scipy | 16 | Requires: python3-scipy |
| @@ -99,6 +100,9 @@ mv %{buildroot}/doclist.lst . | |||
| 99 | %{_docdir}/* | 100 | %{_docdir}/* |
| 100 | 101 | ||
| 101 | %changelog | 102 | %changelog |
| 103 | +* Tue Jul 21 2026 jixiang <qfmy_250803@qq.com> - 2.12.0-4 | ||
| 104 | +- Fix CVE-2025-12060 | ||
| 105 | + | ||
| 102 | * Tue Jul 14 2026 shaojiansong <shaojiansong@kylinos.cn> - 2.12.0-3 | 106 | * Tue Jul 14 2026 shaojiansong <shaojiansong@kylinos.cn> - 2.12.0-3 |
| 103 | - Fix CVE-2026-12482 | 107 | - Fix CVE-2026-12482 |
| 104 | 108 | ||


此处base_dir = _resolve_path(".")涉及CVE-2026-11816问题,已在CVE-2026-11816补丁中变更,需要重新适配