已开启
Fix CVE-2025-12060 in python-Keras for openEuler-24.03-LTS #26
Fix CVE-2025-12060 in python-Keras for openEuler-24.03-LTS #26
已开启
jixiang创建于 7月20日
共 2 个文件变更+244-1
@@ -0,0 +1,239 @@
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+@@ -535,7 +535,7 @@ 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+@@ -23,6 +23,7 @@ import pathlib
41+ import queue
42+ import random
43+ import shutil
44++import sys
45+ import tarfile
46+ import threading
47+ import time
48+@@ -116,24 +117,69 @@ 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(".")
55+ for finfo in members:
56+ valid_path = False
57+- if _is_path_in_dir(finfo.name, base_dir):
58++ if _is_path_in_dir(finfo.filename, base_dir):
59+ valid_path = True
60+ yield finfo
61+- elif finfo.issym() or finfo.islnk():
62++ if not valid_path:
63++ warnings.warn(
64++ "Skipping invalid path during archive extraction: "
65++ f"'{finfo.name}'.",
66++ stacklevel=2,
67++ )
68++
69++
70++def filter_safe_tarinfos(members):
71++ base_dir = _resolve_path(".")
72++ for finfo in members:
73++ valid_path = False
74++ if finfo.issym() or finfo.islnk():
75+ if _is_link_in_dir(finfo, base_dir):
76+ valid_path = True
77+ yield finfo
78++ elif _is_path_in_dir(finfo.name, base_dir):
79++ valid_path = True
80++ yield finfo
81+ if not valid_path:
82+ warnings.warn(
83+ "Skipping invalid path during archive extraction: "
84+- f"'{finfo.name}'."
85++ f"'{finfo.name}'.",
86++ stacklevel=2,
87+ )
88+ 
89+ 
90++def extract_open_archive(archive, path="."):
91++ """Extracts an open tar or zip archive to the provided directory.
92++
93++ This function filters unsafe paths during extraction.
94++
95++ Args:
96++ archive: The archive object, either a `TarFile` or a `ZipFile`.
97++ path: Where to extract the archive file.
98++ """
99++ if isinstance(archive, zipfile.ZipFile):
100++ # Zip archive.
101++ archive.extractall(
102++ path, members=filter_safe_zipinfos(archive.infolist())
103++ )
104++ else:
105++ # Tar archive.
106++ extractall_kwargs = {}
107++ # The `filter="data"` option was added in Python 3.12. It became the
108++ # default starting from Python 3.14. So we only specify it between
109++ # those two versions.
110++ if sys.version_info >= (3, 12) and sys.version_info < (3, 14):
111++ extractall_kwargs = {"filter": "data"}
112++ archive.extractall(
113++ path,
114++ members=filter_safe_tarinfos(archive),
115++ **extractall_kwargs,
116++ )
117++
118++
119+ def _extract_archive(file_path, path=".", archive_format="auto"):
120+ """Extracts an archive if it matches tar, tar.gz, tar.bz, or zip formats.
121+ 
122+@@ -171,14 +217,7 @@ def _extract_archive(file_path, path=".", archive_format="auto"):
123+ if is_match_fn(file_path):
124+ with open_fn(file_path) as archive:
125+ try:
126+- if zipfile.is_zipfile(file_path):
127+- # Zip archive.
128+- archive.extractall(path)
129+- else:
130+- # Tar archive, perhaps unsafe. Filter paths.
131+- archive.extractall(
132+- path, members=_filter_safe_paths(archive)
133+- )
134++ extract_open_archive(archive, path)
135+ except (tarfile.TarError, RuntimeError, KeyboardInterrupt):
136+ if os.path.exists(path):
137+ if os.path.isfile(path):
138+diff --git a/keras/utils/data_utils_test.py b/keras/utils/data_utils_test.py
139+index 093281c..c1f6717 100644
140+--- a/keras/utils/data_utils_test.py
141++++ b/keras/utils/data_utils_test.py
142+@@ -17,8 +17,10 @@
143+ import os
144+ import tarfile
145+ import urllib
146++import shutil
147+ import zipfile
148+ from itertools import cycle
149++from unittest.mock import patch
150+ 
151+ import numpy as np
152+ import tensorflow.compat.v2 as tf
153+@@ -205,6 +207,86 @@ class TestGetFile(tf.test.TestCase):
154+ )
155+ 
156+ 
157++class FilterSafePathsTest(tf.test.TestCase):
158++ def setUp(self):
159++ self.base_dir = os.path.join(os.getcwd(), "temp_dir")
160++ os.makedirs(self.base_dir, exist_ok=True)
161++ self.tar_path = os.path.join(self.base_dir, "test.tar")
162++
163++ def tearDown(self):
164++ os.remove(self.tar_path)
165++ shutil.rmtree(self.base_dir)
166++
167++ def test_member_within_base_dir(self):
168++ """Test a member within the base directory."""
169++ with tarfile.open(self.tar_path, "w") as tar:
170++ tar.add(__file__, arcname="safe_path.txt")
171++ with tarfile.open(self.tar_path, "r") as tar:
172++ members = list(data_utils.filter_safe_tarinfos(tar.getmembers()))
173++ self.assertEqual(len(members), 1)
174++ self.assertEqual(members[0].name, "safe_path.txt")
175++
176++ def test_symlink_within_base_dir(self):
177++ """Test a symlink pointing within the base directory."""
178++ symlink_path = os.path.join(self.base_dir, "symlink.txt")
179++ target_path = os.path.join(self.base_dir, "target.txt")
180++ with open(target_path, "w") as f:
181++ f.write("target")
182++ os.symlink(target_path, symlink_path)
183++ with tarfile.open(self.tar_path, "w") as tar:
184++ tar.add(symlink_path, arcname="symlink.txt")
185++ with tarfile.open(self.tar_path, "r") as tar:
186++ members = list(data_utils.filter_safe_tarinfos(tar.getmembers()))
187++ self.assertEqual(len(members), 1)
188++ self.assertEqual(members[0].name, "symlink.txt")
189++ os.remove(symlink_path)
190++ os.remove(target_path)
191++
192++ def test_invalid_path_warning(self):
193++ """Test warning for an invalid path during archive extraction."""
194++ invalid_path = os.path.join(os.getcwd(), "invalid.txt")
195++ with open(invalid_path, "w") as f:
196++ f.write("invalid")
197++ with tarfile.open(self.tar_path, "w") as tar:
198++ tar.add(
199++ invalid_path, arcname="../../invalid.txt"
200++ ) # Path intended to be outside of base dir
201++ with tarfile.open(self.tar_path, "r") as tar:
202++ with patch("warnings.warn") as mock_warn:
203++ _ = list(data_utils.filter_safe_tarinfos(tar.getmembers()))
204++ warning_msg = (
205++ "Skipping invalid path during archive extraction: "
206++ "'../../invalid.txt'."
207++ )
208++ mock_warn.assert_called_with(warning_msg, stacklevel=2)
209++ os.remove(invalid_path)
210++
211++ def test_symbolic_link_in_base_dir(self):
212++ """symbolic link within the base directory is correctly processed."""
213++ symlink_path = os.path.join(self.base_dir, "symlink.txt")
214++ target_path = os.path.join(self.base_dir, "target.txt")
215++
216++ # Create a target file and then a symbolic link pointing to it.
217++ with open(target_path, "w") as f:
218++ f.write("target")
219++ os.symlink(target_path, symlink_path)
220++
221++ # Add the symbolic link to the tar archive.
222++ with tarfile.open(self.tar_path, "w") as tar:
223++ tar.add(symlink_path, arcname="symlink.txt")
224++
225++ with tarfile.open(self.tar_path, "r") as tar:
226++ members = list(data_utils.filter_safe_tarinfos(tar.getmembers()))
227++ self.assertEqual(len(members), 1)
228++ self.assertEqual(members[0].name, "symlink.txt")
229++ self.assertTrue(
230++ members[0].issym()
231++ ) # Explicitly assert it's a symbolic link.
232++
233++ os.remove(symlink_path)
234++ os.remove(target_path)
235++
236++
237+ class TestSequence(keras.utils.data_utils.Sequence):
238+ def __init__(self, shape, value=1.0):
239+ self.shape = shape
@@ -1,7 +1,7 @@
1%global _empty_manifest_terminate_build 01%global _empty_manifest_terminate_build 0
2Name: python-Keras2Name: python-Keras
3Version: 2.12.03Version: 2.12.0
4-Release: 24+Release: 3
5Summary: Deep Learning for humans5Summary: Deep Learning for humans
6License: Apache-2.06License: Apache-2.0
7URL: https://github.com/keras-team/keras7URL: https://github.com/keras-team/keras
@@ -9,6 +9,7 @@ Source0: https://github.com/keras-team/keras/archive/refs/tags/v2.12.0.tar.gz#/k
9BuildArch: noarch9BuildArch: noarch
10 10 
11Patch0: backport-CVE-2025-12058.patch11Patch0: backport-CVE-2025-12058.patch
12+Patch1: backport-Fix-CVE-2025-12060.patch
12 13 
13Requires: python3-numpy14Requires: python3-numpy
14Requires: python3-scipy15Requires: python3-scipy
@@ -98,6 +99,9 @@ mv %{buildroot}/doclist.lst .
98%{_docdir}/*99%{_docdir}/*
99 100 
100%changelog101%changelog
102+* Fri Dec 05 2025 chenjianhu <chenjianhu@kylinos.cn> - 2.12.0-3
103+- Fix CVE-2025-12060
104+ 
101* Mon Nov 10 2025 hugel <gengqihu2@h-partners.com> - 2.12.0-2105* Mon Nov 10 2025 hugel <gengqihu2@h-partners.com> - 2.12.0-2
102- Fix CVE-2025-12058106- Fix CVE-2025-12058
103 107