Skip to content

Commit 8ce78fb

Browse files
[High] Patch keras for CVE-2025-8747 (microsoft#14485)
1 parent e63bd61 commit 8ce78fb

File tree

2 files changed

+383
-1
lines changed

2 files changed

+383
-1
lines changed

SPECS/keras/CVE-2025-8747.patch

Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
From eec0ac4143c01736700e50c87112d8687e1cc151 Mon Sep 17 00:00:00 2001
2+
From: hertschuh <[email protected]>
3+
Date: Mon, 23 Jun 2025 18:36:47 -0700
4+
Subject: [PATCH 1/2] Disable loading functions within deserialization.
5+
(#21412)
6+
7+
Upstream source link: https://github.com/keras-team/keras/commit/3d6022ab4b79367911cede68a550bfd5b61e2f6d.patch
8+
9+
Loading files while loading a model is not allowed.
10+
---
11+
keras/src/saving/serialization_lib.py | 35 +++++++++++++++++++++------
12+
1 file changed, 27 insertions(+), 8 deletions(-)
13+
14+
diff --git a/keras/src/saving/serialization_lib.py b/keras/src/saving/serialization_lib.py
15+
index ed9f10b..1c47b70 100644
16+
--- a/keras/src/saving/serialization_lib.py
17+
+++ b/keras/src/saving/serialization_lib.py
18+
@@ -18,14 +18,27 @@ from keras.src.utils.module_utils import tensorflow as tf
19+
PLAIN_TYPES = (str, int, float, bool)
20+
21+
# List of Keras modules with built-in string representations for Keras defaults
22+
-BUILTIN_MODULES = (
23+
- "activations",
24+
- "constraints",
25+
- "initializers",
26+
- "losses",
27+
- "metrics",
28+
- "optimizers",
29+
- "regularizers",
30+
+BUILTIN_MODULES = frozenset(
31+
+ {
32+
+ "activations",
33+
+ "constraints",
34+
+ "initializers",
35+
+ "losses",
36+
+ "metrics",
37+
+ "optimizers",
38+
+ "regularizers",
39+
+ }
40+
+)
41+
+
42+
+LOADING_APIS = frozenset(
43+
+ {
44+
+ "keras.models.load_model",
45+
+ "keras.preprocessing.image.load_img",
46+
+ "keras.saving.load_model",
47+
+ "keras.saving.load_weights",
48+
+ "keras.utils.get_file",
49+
+ "keras.utils.load_img",
50+
+ }
51+
)
52+
53+
54+
@@ -765,6 +778,12 @@ def _retrieve_class_or_fn(
55+
if module == "keras" or module.startswith("keras."):
56+
api_name = module + "." + name
57+
58+
+ if api_name in LOADING_APIS:
59+
+ raise ValueError(
60+
+ f"Cannot deserialize `{api_name}`, loading functions are "
61+
+ "not allowed during deserialization"
62+
+ )
63+
+
64+
obj = api_export.get_symbol_from_name(api_name)
65+
if obj is not None:
66+
return obj
67+
--
68+
2.34.1
69+
70+
71+
From d64692c8d18ea3a4a253159b3f25bc6c06cec6be Mon Sep 17 00:00:00 2001
72+
From: hertschuh <[email protected]>
73+
Date: Sun, 29 Jun 2025 10:32:40 -0700
74+
Subject: [PATCH 2/2] Only allow deserialization of `KerasSaveable`s by module
75+
and name. (#21429)
76+
77+
Upstream source link: https://github.com/keras-team/keras/commit/713172ab56b864e59e2aa79b1a51b0e728bba858.patch
78+
Backported by <[email protected]> for azurelinux
79+
80+
Arbitrary functions and classes are not allowed.
81+
82+
- Made `Operation` extend `KerasSaveable`, this required moving imports to avoid circular imports
83+
- `Layer` no longer need to extend `KerasSaveable` directly
84+
- Made feature space `Cross` and `Feature` extend `KerasSaveable`
85+
- Also dissallow public function `enable_unsafe_deserialization`
86+
---
87+
keras/src/layers/layer.py | 3 +-
88+
.../src/layers/preprocessing/feature_space.py | 11 ++++--
89+
keras/src/legacy/saving/legacy_h5_format.py | 5 ++-
90+
keras/src/legacy/saving/saving_utils.py | 8 +++--
91+
keras/src/ops/operation.py | 6 +++-
92+
keras/src/saving/saving_lib.py | 35 +++++++++++++++----
93+
keras/src/saving/serialization_lib.py | 9 ++++-
94+
7 files changed, 61 insertions(+), 16 deletions(-)
95+
96+
diff --git a/keras/src/layers/layer.py b/keras/src/layers/layer.py
97+
index 7d67339..e103cdb 100644
98+
--- a/keras/src/layers/layer.py
99+
+++ b/keras/src/layers/layer.py
100+
@@ -36,7 +36,6 @@ from keras.src.distribution import distribution_lib
101+
from keras.src.layers import input_spec
102+
from keras.src.metrics.metric import Metric
103+
from keras.src.ops.operation import Operation
104+
-from keras.src.saving.keras_saveable import KerasSaveable
105+
from keras.src.utils import python_utils
106+
from keras.src.utils import summary_utils
107+
from keras.src.utils import traceback_utils
108+
@@ -57,7 +56,7 @@ else:
109+
110+
111+
@keras_export(["keras.Layer", "keras.layers.Layer"])
112+
-class Layer(BackendLayer, Operation, KerasSaveable):
113+
+class Layer(BackendLayer, Operation):
114+
"""This is the class from which all layers inherit.
115+
116+
A layer is a callable object that takes as input one or more tensors and
117+
diff --git a/keras/src/layers/preprocessing/feature_space.py b/keras/src/layers/preprocessing/feature_space.py
118+
index f66b032..a02dafc 100644
119+
--- a/keras/src/layers/preprocessing/feature_space.py
120+
+++ b/keras/src/layers/preprocessing/feature_space.py
121+
@@ -5,12 +5,13 @@ from keras.src.api_export import keras_export
122+
from keras.src.layers.layer import Layer
123+
from keras.src.saving import saving_lib
124+
from keras.src.saving import serialization_lib
125+
+from keras.src.saving.keras_saveable import KerasSaveable
126+
from keras.src.utils import backend_utils
127+
from keras.src.utils.module_utils import tensorflow as tf
128+
from keras.src.utils.naming import auto_name
129+
130+
131+
-class Cross:
132+
+class Cross(KerasSaveable):
133+
def __init__(self, feature_names, crossing_dim, output_mode="one_hot"):
134+
if output_mode not in {"int", "one_hot"}:
135+
raise ValueError(
136+
@@ -22,6 +23,9 @@ class Cross:
137+
self.crossing_dim = crossing_dim
138+
self.output_mode = output_mode
139+
140+
+ def _obj_type(self):
141+
+ return "Cross"
142+
+
143+
@property
144+
def name(self):
145+
return "_X_".join(self.feature_names)
146+
@@ -38,7 +42,7 @@ class Cross:
147+
return cls(**config)
148+
149+
150+
-class Feature:
151+
+class Feature(KerasSaveable):
152+
def __init__(self, dtype, preprocessor, output_mode):
153+
if output_mode not in {"int", "one_hot", "float"}:
154+
raise ValueError(
155+
@@ -54,6 +58,9 @@ class Feature:
156+
self.preprocessor = preprocessor
157+
self.output_mode = output_mode
158+
159+
+ def _obj_type(self):
160+
+ return "Feature"
161+
+
162+
def get_config(self):
163+
return {
164+
"dtype": self.dtype,
165+
diff --git a/keras/src/legacy/saving/legacy_h5_format.py b/keras/src/legacy/saving/legacy_h5_format.py
166+
index 05f7cec..24b89bf 100644
167+
--- a/keras/src/legacy/saving/legacy_h5_format.py
168+
+++ b/keras/src/legacy/saving/legacy_h5_format.py
169+
@@ -6,7 +6,6 @@ import numpy as np
170+
from absl import logging
171+
172+
from keras.src import backend
173+
-from keras.src import optimizers
174+
from keras.src.backend.common import global_state
175+
from keras.src.legacy.saving import json_utils
176+
from keras.src.legacy.saving import saving_options
177+
@@ -161,6 +160,8 @@ def load_model_from_hdf5(filepath, custom_objects=None, compile=True):
178+
# Set optimizer weights.
179+
if "optimizer_weights" in f:
180+
try:
181+
+ from keras.src import optimizers
182+
+
183+
if isinstance(model.optimizer, optimizers.Optimizer):
184+
model.optimizer.build(model._trainable_variables)
185+
else:
186+
@@ -249,6 +250,8 @@ def save_optimizer_weights_to_hdf5_group(hdf5_group, optimizer):
187+
hdf5_group: HDF5 group.
188+
optimizer: optimizer instance.
189+
"""
190+
+ from keras.src import optimizers
191+
+
192+
if isinstance(optimizer, optimizers.Optimizer):
193+
symbolic_weights = optimizer.variables
194+
else:
195+
diff --git a/keras/src/legacy/saving/saving_utils.py b/keras/src/legacy/saving/saving_utils.py
196+
index aec1078..525cd3d 100644
197+
--- a/keras/src/legacy/saving/saving_utils.py
198+
+++ b/keras/src/legacy/saving/saving_utils.py
199+
@@ -4,11 +4,8 @@ import threading
200+
from absl import logging
201+
202+
from keras.src import backend
203+
-from keras.src import layers
204+
from keras.src import losses
205+
from keras.src import metrics as metrics_module
206+
-from keras.src import models
207+
-from keras.src import optimizers
208+
from keras.src import tree
209+
from keras.src.legacy.saving import serialization
210+
from keras.src.saving import object_registration
211+
@@ -49,6 +46,9 @@ def model_from_config(config, custom_objects=None):
212+
global MODULE_OBJECTS
213+
214+
if not hasattr(MODULE_OBJECTS, "ALL_OBJECTS"):
215+
+ from keras.src import layers
216+
+ from keras.src import models
217+
+
218+
MODULE_OBJECTS.ALL_OBJECTS = layers.__dict__
219+
MODULE_OBJECTS.ALL_OBJECTS["InputLayer"] = layers.InputLayer
220+
MODULE_OBJECTS.ALL_OBJECTS["Functional"] = models.Functional
221+
@@ -129,6 +129,8 @@ def compile_args_from_training_config(training_config, custom_objects=None):
222+
custom_objects = {}
223+
224+
with object_registration.CustomObjectScope(custom_objects):
225+
+ from keras.src import optimizers
226+
+
227+
optimizer_config = training_config["optimizer_config"]
228+
optimizer = optimizers.deserialize(optimizer_config)
229+
# Ensure backwards compatibility for optimizers in legacy H5 files
230+
diff --git a/keras/src/ops/operation.py b/keras/src/ops/operation.py
231+
index 10b79d5..6c738f3 100644
232+
--- a/keras/src/ops/operation.py
233+
+++ b/keras/src/ops/operation.py
234+
@@ -7,13 +7,14 @@ from keras.src import tree
235+
from keras.src.api_export import keras_export
236+
from keras.src.backend.common.keras_tensor import any_symbolic_tensors
237+
from keras.src.ops.node import Node
238+
+from keras.src.saving.keras_saveable import KerasSaveable
239+
from keras.src.utils import python_utils
240+
from keras.src.utils import traceback_utils
241+
from keras.src.utils.naming import auto_name
242+
243+
244+
@keras_export("keras.Operation")
245+
-class Operation:
246+
+class Operation(KerasSaveable):
247+
def __init__(self, dtype=None, name=None):
248+
if name is None:
249+
name = auto_name(self.__class__.__name__)
250+
@@ -272,6 +273,9 @@ class Operation:
251+
else:
252+
return values
253+
254+
+ def _obj_type(self):
255+
+ return "Operation"
256+
+
257+
# Hooks for backend layer classes
258+
def _post_build(self):
259+
"""Can be overridden for per backend post build actions."""
260+
diff --git a/keras/src/saving/saving_lib.py b/keras/src/saving/saving_lib.py
261+
index c16d2ff..e71e33b 100644
262+
--- a/keras/src/saving/saving_lib.py
263+
+++ b/keras/src/saving/saving_lib.py
264+
@@ -12,14 +12,9 @@ import numpy as np
265+
266+
from keras.src import backend
267+
from keras.src.backend.common import global_state
268+
-from keras.src.layers.layer import Layer
269+
-from keras.src.losses.loss import Loss
270+
-from keras.src.metrics.metric import Metric
271+
-from keras.src.optimizers.optimizer import Optimizer
272+
from keras.src.saving.serialization_lib import ObjectSharingScope
273+
from keras.src.saving.serialization_lib import deserialize_keras_object
274+
from keras.src.saving.serialization_lib import serialize_keras_object
275+
-from keras.src.trainers.compile_utils import CompileMetrics
276+
from keras.src.utils import file_utils
277+
from keras.src.utils import naming
278+
from keras.src.version import __version__ as keras_version
279+
@@ -773,32 +768,60 @@ def get_attr_skiplist(obj_type):
280+
skiplist = [
281+
"_self_unconditional_dependency_names",
282+
]
283+
+ if obj_type == "Operation":
284+
+ from keras.src.ops.operation import Operation
285+
+
286+
+ ref_obj = Operation()
287+
+ skipset.update(dir(ref_obj))
288+
if obj_type == "Layer":
289+
+ from keras.src.layers.layer import Layer
290+
+
291+
ref_obj = Layer()
292+
skiplist += dir(ref_obj)
293+
elif obj_type == "Functional":
294+
+ from keras.src.layers.layer import Layer
295+
+
296+
ref_obj = Layer()
297+
skiplist += dir(ref_obj) + ["operations", "_operations"]
298+
elif obj_type == "Sequential":
299+
+ from keras.src.layers.layer import Layer
300+
+
301+
ref_obj = Layer()
302+
skiplist += dir(ref_obj) + ["_functional"]
303+
elif obj_type == "Metric":
304+
+ from keras.src.metrics.metric import Metric
305+
+ from keras.src.trainers.compile_utils import CompileMetrics
306+
+
307+
ref_obj_a = Metric()
308+
ref_obj_b = CompileMetrics([], [])
309+
skiplist += dir(ref_obj_a) + dir(ref_obj_b)
310+
elif obj_type == "Optimizer":
311+
+ from keras.src.optimizers.optimizer import Optimizer
312+
+
313+
ref_obj = Optimizer(1.0)
314+
skiplist += dir(ref_obj)
315+
skiplist.remove("variables")
316+
elif obj_type == "Loss":
317+
+ from keras.src.losses.loss import Loss
318+
+
319+
ref_obj = Loss()
320+
skiplist += dir(ref_obj)
321+
+ elif obj_type == "Cross":
322+
+ from keras.src.layers.preprocessing.feature_space import Cross
323+
+
324+
+ ref_obj = Cross((), 1)
325+
+ skipset.update(dir(ref_obj))
326+
+ elif obj_type == "Feature":
327+
+ from keras.src.layers.preprocessing.feature_space import Feature
328+
+
329+
+ ref_obj = Feature("int32", lambda x: x, "int")
330+
+ skipset.update(dir(ref_obj))
331+
else:
332+
raise ValueError(
333+
f"get_attr_skiplist got invalid {obj_type=}. "
334+
"Accepted values for `obj_type` are "
335+
"['Layer', 'Functional', 'Sequential', 'Metric', "
336+
- "'Optimizer', 'Loss']"
337+
+ "'Optimizer', 'Loss', 'Cross', 'Feature']"
338+
)
339+
340+
global_state.set_global_attribute(
341+
diff --git a/keras/src/saving/serialization_lib.py b/keras/src/saving/serialization_lib.py
342+
index 1c47b70..e680d04 100644
343+
--- a/keras/src/saving/serialization_lib.py
344+
+++ b/keras/src/saving/serialization_lib.py
345+
@@ -12,6 +12,7 @@ from keras.src import backend
346+
from keras.src.api_export import keras_export
347+
from keras.src.backend.common import global_state
348+
from keras.src.saving import object_registration
349+
+from keras.src.saving.keras_saveable import KerasSaveable
350+
from keras.src.utils import python_utils
351+
from keras.src.utils.module_utils import tensorflow as tf
352+
353+
@@ -32,6 +33,7 @@ BUILTIN_MODULES = frozenset(
354+
355+
LOADING_APIS = frozenset(
356+
{
357+
+ "keras.config.enable_unsafe_deserialization",
358+
"keras.models.load_model",
359+
"keras.preprocessing.image.load_img",
360+
"keras.saving.load_model",
361+
@@ -815,8 +817,13 @@ def _retrieve_class_or_fn(
362+
try:
363+
mod = importlib.import_module(module)
364+
obj = vars(mod).get(name, None)
365+
- if obj is not None:
366+
+ if isinstance(obj, type) and issubclass(obj, KerasSaveable):
367+
return obj
368+
+ else:
369+
+ raise ValueError(
370+
+ f"Could not deserialize '{module}.{name}' because "
371+
+ "it is not a KerasSaveable subclass"
372+
+ )
373+
except ModuleNotFoundError:
374+
raise TypeError(
375+
f"Could not deserialize {obj_type} '{name}' because "
376+
--
377+
2.34.1
378+

0 commit comments

Comments
 (0)