From 189822b8814e5b5f634d0a9dc49f4c63ebfceca9 Mon Sep 17 00:00:00 2001 From: Azure Linux Security Servicing Account Date: Mon, 6 Jul 2026 20:36:05 +0000 Subject: [PATCH 1/2] Patch keras for CVE-2026-12480 --- SPECS/keras/CVE-2026-12480.patch | 453 +++++++++++++++++++++++++++++++ SPECS/keras/keras.spec | 6 +- 2 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 SPECS/keras/CVE-2026-12480.patch diff --git a/SPECS/keras/CVE-2026-12480.patch b/SPECS/keras/CVE-2026-12480.patch new file mode 100644 index 00000000000..d0910cc1616 --- /dev/null +++ b/SPECS/keras/CVE-2026-12480.patch @@ -0,0 +1,453 @@ +From f40b9a6f12c6a12a6f5e9d7b3b1807a5b3ef4788 Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Mon, 6 Jul 2026 20:30:29 +0000 +Subject: [PATCH] Refactor and share H5 validation code between legacy and new + format. + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://github.com/keras-team/keras/commit/d5a88bdb137c0d3039b8f4bbbe8c7099925cc10c.patch +--- + keras/src/legacy/saving/legacy_h5_format.py | 100 ++++++++++--------- + keras/src/saving/saving_lib.py | 102 +++++++++++++------- + 2 files changed, 121 insertions(+), 81 deletions(-) + +diff --git a/keras/src/legacy/saving/legacy_h5_format.py b/keras/src/legacy/saving/legacy_h5_format.py +index 7c8b76e..d7a87ea 100644 +--- a/keras/src/legacy/saving/legacy_h5_format.py ++++ b/keras/src/legacy/saving/legacy_h5_format.py +@@ -12,6 +12,8 @@ from keras.src.legacy.saving import saving_options + from keras.src.legacy.saving import saving_utils + from keras.src.saving import object_registration + from keras.src.saving import serialization_lib ++from keras.src.saving.saving_lib import safe_get_h5_dataset ++from keras.src.saving.saving_lib import safe_get_h5_group + from keras.src.utils import io_utils + + try: +@@ -139,7 +141,9 @@ def load_model_from_hdf5( + ) + + # set weights +- load_weights_from_hdf5_group(f["model_weights"], model) ++ load_weights_from_hdf5_group( ++ safe_get_h5_group(f, "model_weights"), model ++ ) + + if compile: + # instantiate optimizer +@@ -199,48 +203,50 @@ def load_model_from_hdf5( + return model + + +-def save_weights_to_hdf5_group(f, model): ++def save_weights_to_hdf5_group(group, model): + """Saves the weights of a list of layers to a HDF5 group. + + Args: +- f: HDF5 group. ++ group: HDF5 group. + model: Model instance. + """ + from keras.src import __version__ as keras_version + + save_attributes_to_hdf5_group( +- f, "layer_names", [layer.name.encode("utf8") for layer in model.layers] ++ group, ++ "layer_names", ++ [layer.name.encode("utf8") for layer in model.layers], + ) +- f.attrs["backend"] = backend.backend().encode("utf8") +- f.attrs["keras_version"] = str(keras_version).encode("utf8") ++ group.attrs["backend"] = backend.backend().encode("utf8") ++ group.attrs["keras_version"] = str(keras_version).encode("utf8") + + # Sort model layers by layer name to ensure that group names are strictly + # growing to avoid prefix issues. + for layer in sorted(model.layers, key=lambda x: x.name): +- g = f.create_group(layer.name) ++ layer_group = group.create_group(layer.name) + weights = _legacy_weights(layer) +- save_subset_weights_to_hdf5_group(g, weights) ++ save_subset_weights_to_hdf5_group(layer_group, weights) + weights = list( + v + for v in model._trainable_variables + model._non_trainable_variables + if v in model.weights + ) +- g = f.create_group("top_level_model_weights") +- save_subset_weights_to_hdf5_group(g, weights) ++ layer_group = group.create_group("top_level_model_weights") ++ save_subset_weights_to_hdf5_group(layer_group, weights) + + +-def save_subset_weights_to_hdf5_group(f, weights): ++def save_subset_weights_to_hdf5_group(group, weights): + """Save top-level weights of a model to a HDF5 group. + + Args: +- f: HDF5 group. ++ group: HDF5 group. + weights: List of weight variables. + """ + weight_values = [backend.convert_to_numpy(w) for w in weights] + weight_names = [str(w.path).encode("utf8") for w in weights] +- save_attributes_to_hdf5_group(f, "weight_names", weight_names) ++ save_attributes_to_hdf5_group(group, "weight_names", weight_names) + for name, val in zip(weight_names, weight_values): +- param_dset = f.create_dataset(name, val.shape, dtype=val.dtype) ++ param_dset = group.create_dataset(name, val.shape, dtype=val.dtype) + if not val.shape: + # scalar + param_dset[()] = val +@@ -248,11 +254,11 @@ def save_subset_weights_to_hdf5_group(f, weights): + param_dset[:] = val + + +-def save_optimizer_weights_to_hdf5_group(hdf5_group, optimizer): ++def save_optimizer_weights_to_hdf5_group(group, optimizer): + """Saves optimizer weights of a optimizer to a HDF5 group. + + Args: +- hdf5_group: HDF5 group. ++ group: HDF5 group. + optimizer: optimizer instance. + """ + from keras.src import optimizers +@@ -262,7 +268,7 @@ def save_optimizer_weights_to_hdf5_group(hdf5_group, optimizer): + else: + symbolic_weights = getattr(optimizer, "weights") + if symbolic_weights: +- weights_group = hdf5_group.create_group("optimizer_weights") ++ weights_group = group.create_group("optimizer_weights") + weight_names = [str(w.path).encode("utf8") for w in symbolic_weights] + save_attributes_to_hdf5_group( + weights_group, "weight_names", weight_names +@@ -334,14 +340,14 @@ def load_weights_from_hdf5_group(f, model): + ValueError: in case of mismatch between provided layers + and weights file. + """ +- if "keras_version" in f.attrs: +- original_keras_version = f.attrs["keras_version"] ++ if "keras_version" in group.attrs: ++ original_keras_version = group.attrs["keras_version"] + if hasattr(original_keras_version, "decode"): + original_keras_version = original_keras_version.decode("utf8") + else: + original_keras_version = "1" +- if "backend" in f.attrs: +- original_backend = f.attrs["backend"] ++ if "backend" in group.attrs: ++ original_backend = group.attrs["backend"] + if hasattr(original_backend, "decode"): + original_backend = original_backend.decode("utf8") + else: +@@ -353,11 +359,13 @@ def load_weights_from_hdf5_group(f, model): + if weights: + filtered_layers.append(layer) + +- layer_names = load_attributes_from_hdf5_group(f, "layer_names") ++ layer_names = load_attributes_from_hdf5_group(group, "layer_names") + filtered_layer_names = [] + for name in layer_names: +- g = f[name] +- weight_names = load_attributes_from_hdf5_group(g, "weight_names") ++ layer_group = safe_get_h5_group(group, name) ++ weight_names = load_attributes_from_hdf5_group( ++ layer_group, "weight_names" ++ ) + if weight_names: + filtered_layer_names.append(name) + layer_names = filtered_layer_names +@@ -369,10 +377,10 @@ def load_weights_from_hdf5_group(f, model): + ) + + for k, name in enumerate(layer_names): +- g = f[name] ++ layer_group = safe_get_h5_group(group, name) + layer = filtered_layers[k] + symbolic_weights = _legacy_weights(layer) +- weight_values = load_subset_weights_from_hdf5_group(g) ++ weight_values = load_subset_weights_from_hdf5_group(layer_group) + if len(weight_values) != len(symbolic_weights): + raise ValueError( + f"Weight count mismatch for layer #{k} (named {layer.name} in " +@@ -383,7 +391,7 @@ def load_weights_from_hdf5_group(f, model): + for ref_v, val in zip(symbolic_weights, weight_values): + ref_v.assign(val) + +- if "top_level_model_weights" in f: ++ if "top_level_model_weights" in group: + symbolic_weights = list( + # model.weights + v +@@ -391,7 +399,7 @@ def load_weights_from_hdf5_group(f, model): + if v in model.weights + ) + weight_values = load_subset_weights_from_hdf5_group( +- f["top_level_model_weights"] ++ safe_get_h5_group(group, "top_level_model_weights") + ) + if len(weight_values) != len(symbolic_weights): + raise ValueError( +@@ -404,7 +412,7 @@ def load_weights_from_hdf5_group(f, model): + ref_v.assign(val) + + +-def load_weights_from_hdf5_group_by_name(f, model, skip_mismatch=False): ++def load_weights_from_hdf5_group_by_name(group, model, skip_mismatch=False): + """Implements name-based weight loading (instead of topological loading). + + Layers that have no matching name are skipped. +@@ -420,21 +428,21 @@ def load_weights_from_hdf5_group_by_name(f, model, skip_mismatch=False): + ValueError: in case of mismatch between provided layers + and weights file and skip_match=False. + """ +- if "keras_version" in f.attrs: +- original_keras_version = f.attrs["keras_version"] ++ if "keras_version" in group.attrs: ++ original_keras_version = group.attrs["keras_version"] + if hasattr(original_keras_version, "decode"): + original_keras_version = original_keras_version.decode("utf8") + else: + original_keras_version = "1" +- if "backend" in f.attrs: +- original_backend = f.attrs["backend"] ++ if "backend" in group.attrs: ++ original_backend = group.attrs["backend"] + if hasattr(original_backend, "decode"): + original_backend = original_backend.decode("utf8") + else: + original_backend = None + + # New file format. +- layer_names = load_attributes_from_hdf5_group(f, "layer_names") ++ layer_names = load_attributes_from_hdf5_group(group, "layer_names") + + # Reverse index of layer name to list of layers with name. + index = {} +@@ -443,8 +451,8 @@ def load_weights_from_hdf5_group_by_name(f, model, skip_mismatch=False): + index.setdefault(layer.name, []).append(layer) + + for k, name in enumerate(layer_names): +- g = f[name] +- weight_values = load_subset_weights_from_hdf5_group(g) ++ layer_group = safe_get_h5_group(group, name) ++ weight_values = load_subset_weights_from_hdf5_group(layer_group) + for layer in index.get(name, []): + symbolic_weights = _legacy_weights(layer) + if len(weight_values) != len(symbolic_weights): +@@ -489,10 +497,10 @@ def load_weights_from_hdf5_group_by_name(f, model, skip_mismatch=False): + else: + symbolic_weights[i].assign(weight_values[i]) + +- if "top_level_model_weights" in f: ++ if "top_level_model_weights" in group: + symbolic_weights = model.trainable_weights + model.non_trainable_weights + weight_values = load_subset_weights_from_hdf5_group( +- f["top_level_model_weights"] ++ safe_get_h5_group(group, "top_level_model_weights") + ) + + if len(weight_values) != len(symbolic_weights): +@@ -539,7 +547,7 @@ def load_weights_from_hdf5_group_by_name(f, model, skip_mismatch=False): + symbolic_weights[i].assign(weight_values[i]) + + +-def load_subset_weights_from_hdf5_group(f): ++def load_subset_weights_from_hdf5_group(group): + """Load layer weights of a model from hdf5. + + Args: +@@ -552,11 +560,14 @@ def load_subset_weights_from_hdf5_group(f): + ValueError: in case of mismatch between provided model + and weights file. + """ +- weight_names = load_attributes_from_hdf5_group(f, "weight_names") +- return [np.asarray(f[weight_name]) for weight_name in weight_names] ++ weight_names = load_attributes_from_hdf5_group(group, "weight_names") ++ return [ ++ np.asarray(safe_get_h5_dataset(group, weight_name)) ++ for weight_name in weight_names ++ ] + + +-def load_optimizer_weights_from_hdf5_group(hdf5_group): ++def load_optimizer_weights_from_hdf5_group(group): + """Load optimizer weights from a HDF5 group. + + Args: +@@ -565,12 +576,13 @@ def load_optimizer_weights_from_hdf5_group(hdf5_group): + Returns: + data: List of optimizer weight names. + """ +- weights_group = hdf5_group["optimizer_weights"] ++ weights_group = safe_get_h5_group(group, "optimizer_weights") + optimizer_weight_names = load_attributes_from_hdf5_group( + weights_group, "weight_names" + ) + return [ +- weights_group[weight_name] for weight_name in optimizer_weight_names ++ safe_get_h5_dataset(weights_group, weight_name) ++ for weight_name in optimizer_weight_names + ] + + +diff --git a/keras/src/saving/saving_lib.py b/keras/src/saving/saving_lib.py +index fe5eb10..71f9c7f 100644 +--- a/keras/src/saving/saving_lib.py ++++ b/keras/src/saving/saving_lib.py +@@ -17,13 +17,9 @@ from keras.src.saving.serialization_lib import deserialize_keras_object + from keras.src.saving.serialization_lib import serialize_keras_object + from keras.src.utils import file_utils + from keras.src.utils import naming ++from keras.src.utils.module_utils import h5py + from keras.src.version import __version__ as keras_version + +-try: +- import h5py +-except ImportError: +- h5py = None +- + + # Maximum allowed HDF5 dataset size in bytes (4 GiB) + MAX_BYTES = 1 << 32 # 4 GiB +@@ -603,6 +599,64 @@ class DiskIOStore: + file_utils.rmtree(self.tmp_dir) + + ++def safe_get_h5_group(parent, name): ++ """Retrieve a Group within a given File or Group. ++ ++ Args: ++ parent: the parent h5py.File or h5py.Group. ++ name: the name of the Group to retrieve. ++ ++ Returns: ++ The child h5py.Group. ++ """ ++ # Also handles the case when the group is an empty dict initially. ++ if name not in parent: ++ raise KeyError(name) ++ ++ group_type = parent.get(name, default=None, getclass=True, getlink=True) ++ if group_type in (h5py.ExternalLink, h5py.SoftLink): ++ raise ValueError(f"Not allowed: H5 file with {group_type.__name__}") ++ ++ group = parent[name] ++ if not isinstance(group, h5py.Group): ++ raise ValueError( ++ f"Invalid H5 file, expected Group but received {type(group)}" ++ ) ++ return group ++ ++ ++def safe_get_h5_dataset(group, name): ++ """Retrieve a Dataset within a given Group. ++ ++ Args: ++ group: the parent h5py.Group. ++ name: the name of the Dataset to retrieve. ++ ++ Returns: ++ The child h5py.Dataset. ++ """ ++ # Also handles the case when the group is an empty dict initially. ++ if name not in group: ++ raise KeyError(name) ++ ++ dataset_type = group.get(name, default=None, getclass=True, getlink=True) ++ if dataset_type in (h5py.ExternalLink, h5py.SoftLink): ++ raise ValueError(f"Not allowed: H5 file with {dataset_type.__name__}") ++ ++ dataset = group[name] ++ if not isinstance(dataset, h5py.Dataset): ++ raise ValueError( ++ f"Invalid H5 file, expected Dataset, received {type(dataset)}" ++ ) ++ if dataset.external: ++ raise ValueError( ++ f"Not allowed: H5 file with external Dataset: {dataset.external}" ++ ) ++ if dataset.is_virtual: ++ raise ValueError("Not allowed: H5 file with virtual Dataset") ++ return dataset ++ ++ + class H5IOStore: + def __init__(self, root_path, archive=None, mode="r"): + """Numerical variable store backed by HDF5. +@@ -659,11 +713,11 @@ class H5Entry: + else: + found = False + if not path: +- self.group = self._verify_group(self.h5_file["vars"]) ++ self.group = safe_get_h5_group(self.h5_file, "vars") + found = True + elif path in self.h5_file and "vars" in self.h5_file[path]: +- self.group = self._verify_group( +- self._verify_group(self.h5_file[path])["vars"] ++ self.group = safe_get_h5_group( ++ safe_get_h5_group(self.h5_file, path), "vars" + ) + found = True + else: +@@ -675,37 +729,13 @@ class H5Entry: + ) + self.path = path + if path in self.h5_file and "vars" in self.h5_file[path]: +- self.group = self._verify_group( +- self._verify_group(self.h5_file[path])["vars"] ++ self.group = safe_get_h5_group( ++ safe_get_h5_group(self.h5_file, path), "vars" + ) + found = True + if not found: + self.group = {} + +- def _verify_group(self, group): +- if not isinstance(group, h5py.Group): +- raise ValueError( +- f"Invalid H5 file, expected Group but received {type(group)}" +- ) +- return group +- +- def _verify_dataset(self, dataset): +- if not isinstance(dataset, h5py.Dataset): +- raise ValueError( +- f"Invalid H5 file, expected Dataset, received {type(dataset)}" +- ) +- # Disallow external links +- try: +- external = dataset.external +- except Exception: +- external = False +- if external: +- raise ValueError( +- "Not allowed: H5 file Dataset with external links: " +- f"{dataset.external}" +- ) +- return dataset +- + def __len__(self): + return self.group.__len__() + +@@ -724,15 +754,13 @@ class H5Entry: + self.group[key] = value + + def __getitem__(self, name): +- value = self.group[name] ++ value = safe_get_h5_dataset(self.group, name) + # Not a dataset: try to read scalar content if possible + if not hasattr(value, "shape") or not hasattr(value, "dtype"): + try: + return value[()] + except Exception: + return value +- # Verify dataset and disallow external links +- value = self._verify_dataset(value) + shape = value.shape + dtype = value.dtype + # No negative dimensions +-- +2.45.4 + diff --git a/SPECS/keras/keras.spec b/SPECS/keras/keras.spec index 87afcafd98f..325f610f9db 100644 --- a/SPECS/keras/keras.spec +++ b/SPECS/keras/keras.spec @@ -3,7 +3,7 @@ Summary: Keras is a high-level neural networks API. Name: keras Version: 3.3.3 -Release: 7%{?dist} +Release: 8%{?dist} License: ASL 2.0 Vendor: Microsoft Corporation Distribution: Azure Linux @@ -18,6 +18,7 @@ Patch03: CVE-2025-9905.patch Patch4: CVE-2025-12060.patch Patch5: CVE-2026-0897.patch Patch6: CVE-2026-1669.patch +Patch7: CVE-2026-12480.patch # Fix for CVE-2025-9906 included as part of CVE-2025-8747 and kept here as nopatch # and commented out, because from patch command perspective, these files @@ -82,6 +83,9 @@ python3 pip_build.py --install %changelog +* Mon Jul 06 2026 Azure Linux Security Servicing Account - 3.3.3-8 +- Patch for CVE-2026-12480 + * Tue Apr 14 2026 Azure Linux Security Servicing Account - 3.3.3-7 - Patch for CVE-2026-1669 From 3cbc213a401f191e5a4484eeac8c394fef6754ed Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Fri, 10 Jul 2026 09:39:02 +0000 Subject: [PATCH 2/2] Updated patch for CVE-2026-12480 --- SPECS/keras/CVE-2026-12480.patch | 47 +++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/SPECS/keras/CVE-2026-12480.patch b/SPECS/keras/CVE-2026-12480.patch index d0910cc1616..ddfcc043bdc 100644 --- a/SPECS/keras/CVE-2026-12480.patch +++ b/SPECS/keras/CVE-2026-12480.patch @@ -7,12 +7,12 @@ Subject: [PATCH] Refactor and share H5 validation code between legacy and new Signed-off-by: Azure Linux Security Servicing Account Upstream-reference: AI Backport of https://github.com/keras-team/keras/commit/d5a88bdb137c0d3039b8f4bbbe8c7099925cc10c.patch --- - keras/src/legacy/saving/legacy_h5_format.py | 100 ++++++++++--------- - keras/src/saving/saving_lib.py | 102 +++++++++++++------- - 2 files changed, 121 insertions(+), 81 deletions(-) + keras/src/legacy/saving/legacy_h5_format.py | 110 +++++++++++--------- + keras/src/saving/saving_lib.py | 102 +++++++++++------- + 2 files changed, 126 insertions(+), 86 deletions(-) diff --git a/keras/src/legacy/saving/legacy_h5_format.py b/keras/src/legacy/saving/legacy_h5_format.py -index 7c8b76e..d7a87ea 100644 +index 7c8b76e..05936ef 100644 --- a/keras/src/legacy/saving/legacy_h5_format.py +++ b/keras/src/legacy/saving/legacy_h5_format.py @@ -12,6 +12,8 @@ from keras.src.legacy.saving import saving_options @@ -122,7 +122,20 @@ index 7c8b76e..d7a87ea 100644 weight_names = [str(w.path).encode("utf8") for w in symbolic_weights] save_attributes_to_hdf5_group( weights_group, "weight_names", weight_names -@@ -334,14 +340,14 @@ def load_weights_from_hdf5_group(f, model): +@@ -323,25 +329,25 @@ def save_attributes_to_hdf5_group(group, name, data): + group.attrs[name] = data + + +-def load_weights_from_hdf5_group(f, model): ++def load_weights_from_hdf5_group(group, model): + """Implements topological (order-based) weight loading. + + Args: +- f: A pointer to a HDF5 group. ++ group: HDF5 group. + model: Model instance. + + Raises: ValueError: in case of mismatch between provided layers and weights file. """ @@ -189,15 +202,22 @@ index 7c8b76e..d7a87ea 100644 ) if len(weight_values) != len(symbolic_weights): raise ValueError( -@@ -404,7 +412,7 @@ def load_weights_from_hdf5_group(f, model): +@@ -404,13 +412,13 @@ def load_weights_from_hdf5_group(f, model): ref_v.assign(val) -def load_weights_from_hdf5_group_by_name(f, model, skip_mismatch=False): +def load_weights_from_hdf5_group_by_name(group, model, skip_mismatch=False): """Implements name-based weight loading (instead of topological loading). - + Layers that have no matching name are skipped. + + Args: +- f: A pointer to a HDF5 group. ++ group: HDF5 group. + model: Model instance. + skip_mismatch: Boolean, whether to skip loading of layers + where there is a mismatch in the number of weights, @@ -420,21 +428,21 @@ def load_weights_from_hdf5_group_by_name(f, model, skip_mismatch=False): ValueError: in case of mismatch between provided layers and weights file and skip_match=False. @@ -249,7 +269,7 @@ index 7c8b76e..d7a87ea 100644 ) if len(weight_values) != len(symbolic_weights): -@@ -539,7 +547,7 @@ def load_weights_from_hdf5_group_by_name(f, model, skip_mismatch=False): +@@ -539,11 +547,11 @@ def load_weights_from_hdf5_group_by_name(f, model, skip_mismatch=False): symbolic_weights[i].assign(weight_values[i]) @@ -258,7 +278,12 @@ index 7c8b76e..d7a87ea 100644 """Load layer weights of a model from hdf5. Args: -@@ -552,11 +560,14 @@ def load_subset_weights_from_hdf5_group(f): +- f: A pointer to a HDF5 group. ++ group: HDF5 group. + + Returns: + List of NumPy arrays of the weight values. +@@ -552,25 +560,29 @@ def load_subset_weights_from_hdf5_group(f): ValueError: in case of mismatch between provided model and weights file. """ @@ -276,7 +301,9 @@ index 7c8b76e..d7a87ea 100644 """Load optimizer weights from a HDF5 group. Args: -@@ -565,12 +576,13 @@ def load_optimizer_weights_from_hdf5_group(hdf5_group): +- hdf5_group: A pointer to a HDF5 group. ++ group: HDF5 group. + Returns: data: List of optimizer weight names. """