|
| 1 | +From 19afa6e00ecfc2fc4374eff042d3eace65748eb6 Mon Sep 17 00:00:00 2001 |
| 2 | +From: AllSpark <allspark@microsoft.com> |
| 3 | +Date: Mon, 18 May 2026 14:36:19 +0000 |
| 4 | +Subject: [PATCH] Backport: validate external_data path in save_external_data, |
| 5 | + raise ValidationError, and adjust tests accordingly |
| 6 | + |
| 7 | +Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> |
| 8 | +Upstream-reference: AI Backport of https://github.com/onnx/onnx/pull/7627.patch |
| 9 | +--- |
| 10 | + third_party/onnx/onnx/external_data_helper.py | 27 ++++++++++++++++++- |
| 11 | + .../onnx/onnx/test/test_external_data.py | 20 ++++++-------- |
| 12 | + 2 files changed, 34 insertions(+), 13 deletions(-) |
| 13 | + |
| 14 | +diff --git a/third_party/onnx/onnx/external_data_helper.py b/third_party/onnx/onnx/external_data_helper.py |
| 15 | +index 2b5ea70a..fb0513f1 100644 |
| 16 | +--- a/third_party/onnx/onnx/external_data_helper.py |
| 17 | ++++ b/third_party/onnx/onnx/external_data_helper.py |
| 18 | +@@ -2,6 +2,7 @@ |
| 19 | + # |
| 20 | + # SPDX-License-Identifier: Apache-2.0 |
| 21 | + import os |
| 22 | ++import pathlib |
| 23 | + import re |
| 24 | + import sys |
| 25 | + import uuid |
| 26 | +@@ -321,12 +322,36 @@ def convert_model_from_external_data(model: ModelProto) -> None: |
| 27 | + def save_external_data(tensor: TensorProto, base_path: str) -> None: |
| 28 | + """ |
| 29 | + Writes tensor data to an external file according to information in the `external_data` field. |
| 30 | ++ The function checks the external is a valid name and located in folder `base_path`. |
| 31 | + |
| 32 | + Arguments: |
| 33 | + tensor (TensorProto): Tensor object to be serialized |
| 34 | + base_path: System path of a folder where tensor data is to be stored |
| 35 | ++ |
| 36 | ++ Raises: |
| 37 | ++ onnx.checker.ValidationError: If the external file is invalid. |
| 38 | + """ |
| 39 | + info = ExternalDataInfo(tensor) |
| 40 | ++ |
| 41 | ++ # Let's check the tensor location is valid. |
| 42 | ++ location_path = pathlib.Path(info.location) |
| 43 | ++ if location_path.is_absolute() and len(location_path.parts) > 1: |
| 44 | ++ raise onnx_checker.ValidationError( |
| 45 | ++ f"Tensor {tensor.name!r} is external and must not be defined " |
| 46 | ++ f"with an absolute path such as {info.location!r}, " |
| 47 | ++ f"base_path={base_path!r}" |
| 48 | ++ ) |
| 49 | ++ if ".." in location_path.parts: |
| 50 | ++ raise onnx_checker.ValidationError( |
| 51 | ++ f"Tensor {tensor.name!r} is external and must be placed in folder " |
| 52 | ++ f"{base_path!r}, '..' is not needed in {info.location!r}." |
| 53 | ++ ) |
| 54 | ++ if location_path.name in (".", ".."): |
| 55 | ++ raise onnx_checker.ValidationError( |
| 56 | ++ f"Tensor {tensor.name!r} is external and its name " |
| 57 | ++ f"{info.location!r} is invalid." |
| 58 | ++ ) |
| 59 | ++ |
| 60 | + external_data_file_path = os.path.join(base_path, info.location) |
| 61 | + |
| 62 | + # C++ _resolve_external_data_location() cannot be used on save path |
| 63 | +@@ -337,7 +362,7 @@ def save_external_data(tensor: TensorProto, base_path: str) -> None: |
| 64 | + |
| 65 | + # Retrieve the tensor's data from raw_data or load external file |
| 66 | + if not tensor.HasField("raw_data"): |
| 67 | +- raise ValueError("raw_data field doesn't exist.") |
| 68 | ++ raise onnx_checker.ValidationError("raw_data field doesn't exist.") |
| 69 | + |
| 70 | + # Create file if it doesn't exist |
| 71 | + if not os.path.isfile(external_data_file_path): |
| 72 | +diff --git a/third_party/onnx/onnx/test/test_external_data.py b/third_party/onnx/onnx/test/test_external_data.py |
| 73 | +index bb14d279..566992ff 100644 |
| 74 | +--- a/third_party/onnx/onnx/test/test_external_data.py |
| 75 | ++++ b/third_party/onnx/onnx/test/test_external_data.py |
| 76 | +@@ -205,9 +205,13 @@ class TestLoadExternalDataSingleFile(TestLoadExternalDataBase): |
| 77 | + attribute_tensor = new_model.graph.node[0].attribute[0].t |
| 78 | + np.testing.assert_allclose(to_array(attribute_tensor), self.attribute_value) |
| 79 | + |
| 80 | +- @parameterized.parameterized.expand(itertools.product((True, False), (True, False))) |
| 81 | ++ @parameterized.parameterized.expand( |
| 82 | ++ itertools.product( |
| 83 | ++ (True, False), |
| 84 | ++ ) |
| 85 | ++ ) |
| 86 | + def test_save_external_invalid_single_file_data_and_check( |
| 87 | +- self, use_absolute_path: bool, use_model_path: bool |
| 88 | ++ self, use_absolute_path: bool |
| 89 | + ) -> None: |
| 90 | + model = onnx.load_model(self.model_filename, self.serialization_format) |
| 91 | + |
| 92 | +@@ -240,16 +244,8 @@ class TestLoadExternalDataSingleFile(TestLoadExternalDataBase): |
| 93 | + location=traversal_external_data_location, |
| 94 | + ) |
| 95 | + |
| 96 | +- onnx.save_model(model, new_model_filepath, self.serialization_format) |
| 97 | +- if use_model_path: |
| 98 | +- with self.assertRaises(onnx.checker.ValidationError): |
| 99 | +- _ = onnx.load_model(new_model_filepath, self.serialization_format) |
| 100 | +- else: |
| 101 | +- onnx_model = onnx.load_model( |
| 102 | +- new_model_filepath, self.serialization_format, load_external_data=False |
| 103 | +- ) |
| 104 | +- with self.assertRaises(onnx.checker.ValidationError): |
| 105 | +- load_external_data_for_model(onnx_model, external_data_dir) |
| 106 | ++ with self.assertRaises(onnx.checker.ValidationError): |
| 107 | ++ onnx.save_model(model, new_model_filepath, self.serialization_format) |
| 108 | + |
| 109 | + |
| 110 | + @parameterized.parameterized_class( |
| 111 | +-- |
| 112 | +2.45.4 |
| 113 | + |
0 commit comments