CVE-2024-27318: Insufficient Information (NVD-CWE-noinfo)
value: Versions of the package onnx before and including 1.15.0 are vulnerable to Directory Traversal as the external_data field of the tensor proto can have a path to the file which is outside the model current directory or user-provided directory. The vulnerability occurs as a bypass for the patch added for CVE-2022-25882.

CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-02-23T18:15Z
Repository: https://github.com/onnx/onnx
Language: Python
Files changed: onnx/checker.cc, onnx/checker.h, onnx/common/path.h, onnx/cpp2py_export.cc, onnx/external_data_helper.py, onnx/model_container.py, onnx/test/test_external_data.py
Commit date: 2024-02-19 19:12:40 +0000
Commit message: Fix path sanitization bypass leading to arbitrary read (#5917)

Signed-off-by: liqunfu <liqun.fu@microsoft.com>
Signed-off-by: liqun Fu <liqun.fu@microsoft.com>
Co-authored-by: Justin Chu <justinchuby@users.noreply.github.com>
Diff: diff --git a/onnx/checker.cc b/onnx/checker.cc
index e2736e365..a8623c606 100644
--- a/onnx/checker.cc
+++ b/onnx/checker.cc
@@ -11,11 +11,10 @@
 #include <string>
 #include <unordered_set>
 #include <vector>
 
 #include "onnx/common/file_utils.h"
-#include "onnx/common/path.h"
 #include "onnx/defs/schema.h"
 #include "onnx/defs/tensor_proto_util.h"
 #include "onnx/proto_utils.h"
 #include "onnx/shape_inference/implementation.h"
 #include "onnx/string_utils.h"
@@ -126,89 +125,11 @@ void check_tensor(const TensorProto& tensor, const CheckerContext& ctx) {
 
     bool has_location = false;
     for (const StringStringEntryProto& entry : tensor.external_data()) {
       if (entry.has_key() && entry.has_value() && entry.key() == "location") {
         has_location = true;
-#ifdef _WIN32
-        auto file_path = std::filesystem::path(utf8str_to_wstring(entry.value()));
-        if (file_path.is_absolute()) {
-          fail_check(
-              "Location of external TensorProto ( tensor name: ",
-              tensor.name(),
-              ") should be a relative path, but it is an absolute path: ",
-              entry.value());
-        }
-        auto relative_path = file_path.lexically_normal().make_preferred().wstring();
-        // Check that normalized relative path contains ".." on Windows.
-        if (relative_path.find(L"..", 0) != std::string::npos) {
-          fail_check(
-              "Data of TensorProto ( tensor name: ",
-              tensor.name(),
-              ") should be file inside the ",
-              ctx.get_model_dir(),
-              ", but the '",
-              entry.value(),
-              "' points outside the directory");
-        }
-        std::wstring data_path = path_join(utf8str_to_wstring(ctx.get_model_dir()), relative_path);
-        struct _stat64 buff;
-        if (data_path.empty() || (data_path[0] != '#' && _wstat64(data_path.c_str(), &buff) != 0)) {
-          fail_check(
-              "Data of TensorProto ( tensor name: ",
-              tensor.name(),
-              ") should be stored in ",
-              entry.value(),
-              ", but it doesn't exist or is not accessible.");
-        }
-#else // POSIX
-        if (entry.value().empty()) {
-          fail_check("Location of external TensorProto ( tensor name: ", tensor.name(), ") should not be empty.");
-        } else if (entry.value()[0] == '/') {
-          fail_check(
-              "Location of external TensorProto ( tensor name: ",
-              tensor.name(),
-              ") should be a relative path, but it is an absolute path: ",
-              entry.value());
-        }
-        std::string relative_path = clean_relative_path(entry.value());
-        // Check that normalized relative path contains ".." on POSIX
-        if (relative_path.find("..", 0) != std::string::npos) {
-          fail_check(
-              "Data of TensorProto ( tensor name: ",
-              tensor.name(),
-              ") should be file inside the ",
-              ctx.get_model_dir(),
-              ", but the '",
-              entry.value(),
-              "' points outside the directory");
-        }
-        std::string data_path = path_join(ctx.get_model_dir(), relative_path);
-        // use stat64 to check whether the file exists
-#if defined(__APPLE__) || defined(__wasm__) || !defined(__GLIBC__)
-        struct stat buffer; // APPLE, wasm and non-glic stdlibs do not have stat64
-        if (data_path.empty() || (data_path[0] != '#' && stat((data_path).c_str(), &buffer) != 0)) {
-#else
-        struct stat64 buffer; // All POSIX under glibc except APPLE and wasm have stat64
-        if (data_path.empty() || (data_path[0] != '#' && stat64((data_path).c_str(), &buffer) != 0)) {
-#endif
-          fail_check(
-              "Data of TensorProto ( tensor name: ",
-              tensor.name(),
-              ") should be stored in ",
-              data_path,
-              ", but it doesn't exist or is not accessible.");
-        }
-        // Do not allow symlinks or directories.
-        if (data_path.empty() || (data_path[0] != '#' && !S_ISREG(buffer.st_mode))) {
-          fail_check(
-              "Data of TensorProto ( tensor name: ",
-              tensor.name(),
-              ") should be stored in ",
-              data_path,
-              ", but it is not regular file.");
-        }
-#endif
+        resolve_external_data_location(ctx.get_model_dir(), entry.value(), tensor.name());
       }
     }
     if (!has_location) {
       fail_check("TensorProto ( tensor name: ", tensor.name(), ") is stored externally but doesn't have a location.");
     }
@@ -1047,10 +968,97 @@ void check_model(const ModelProto& model, bool full_check, bool skip_opset_compa
     ModelProto copy = model;
     ONNX_NAMESPACE::shape_inference::InferShapes(copy, ctx.get_schema_registry(), options);
   }
 }
 
+std::string resolve_external_data_location(
+    const std::string& base_dir,
+    const std::string& location,
+    const std::string& tensor_name) {
+#ifdef _WIN32
+  auto file_path = std::filesystem::path(utf8str_to_wstring(location));
+  if (file_path.is_absolute()) {
+    fail_check(
+        "Location of external TensorProto ( tensor name: ",
+        tensor_name,
+        ") should be a relative path, but it is an absolute path: ",
+        location);
+  }
+  auto relative_path = file_path.lexically_normal().make_preferred().wstring();
+  // Check that normalized relative path contains ".." on Windows.
+  if (relative_path.find(L"..", 0) != std::string::npos) {
+    fail_check(
+        "Data of TensorProto ( tensor name: ",
+        tensor_name,
+        ") should be file inside the ",
+        base_dir,
+        ", but the '",
+        location,
+        "' points outside the directory");
+  }
+  std::wstring data_path = path_join(utf8str_to_wstring(base_dir), relative_path);
+  struct _stat64 buff;
+  if (data_path.empty() || (data_path[0] != '#' && _wstat64(data_path.c_str(), &buff) != 0)) {
+    fail_check(
+        "Data of TensorProto ( tensor name: ",
+        tensor_name,
+        ") should be stored in ",
+        location,
+        ", but it doesn't exist or is not accessible.");
+  }
+  return wstring_to_utf8str(data_path);
+#else // POSIX
+  if (location.empty()) {
+    fail_check("Location of external TensorProto ( tensor name: ", tensor_name, ") should not be empty.");
+  } else if (location[0] == '/') {
+    fail_check(
+        "Location of external TensorProto ( tensor name: ",
+        tensor_name,
+        ") should be a relative path, but it is an absolute path: ",
+        location);
+  }
+  std::string relative_path = clean_relative_path(location);
+  // Check that normalized relative path contains ".." on POSIX
+  if (relative_path.find("..", 0) != std::string::npos) {
+    fail_check(
+        "Data of TensorProto ( tensor name: ",
+        tensor_name,
+        ") should be file inside the ",
+        base_dir,
+        ", but the '",
+        location,
+        "' points outside the directory");
+  }
+  std::string data_path = path_join(base_dir, relative_path);
+  // use stat64 to check whether the file exists
+#if defined(__APPLE__) || defined(__wasm__) || !defined(__GLIBC__)
+  struct stat buffer; // APPLE, wasm and non-glic stdlibs do not have stat64
+  if (data_path.empty() || (data_path[0] != '#' && stat((data_path).c_str(), &buffer) != 0)) {
+#else
+  struct stat64 buffer; // All POSIX under glibc except APPLE and wasm have stat64
+  if (data_path.empty() || (data_path[0] != '#' && stat64((data_path).c_str(), &buffer) != 0)) {
+#endif
+    fail_check(
+        "Data of TensorProto ( tensor name: ",
+        tensor_name,
+        ") should be stored in ",
+        data_path,
+        ", but it doesn't exist or is not accessible.");
+  }
+  // Do not allow symlinks or directories.
+  if (data_path.empty() || (data_path[0] != '#' && !S_ISREG(buffer.st_mode))) {
+    fail_check(
+        "Data of TensorProto ( tensor name: ",
+        tensor_name,
+        ") should be stored in ",
+        data_path,
+        ", but it is not regular file.");
+  }
+  return data_path;
+#endif
+}
+
 std::set<std::string> experimental_ops = {
     "ATen",
     "Affine",
     "ConstantFill",
     "Crop",
diff --git a/onnx/checker.h b/onnx/checker.h
index 6796acab2..830122134 100644
--- a/onnx/checker.h
+++ b/onnx/checker.h
@@ -158,10 +158,13 @@ void check_model_local_functions(
     const CheckerContext& ctx,
     const LexicalScopeContext& parent_lex);
 
 void check_model(const ModelProto& model, bool full_check = false, bool skip_opset_compatibility_check = false);
 void check_model(const std::string& model_path, bool full_check = false, bool skip_opset_compatibility_check = false);
-
+std::string resolve_external_data_location(
+    const std::string& base_dir,
+    const std::string& location,
+    const std::string& tensor_name);
 bool check_is_experimental_op(const NodeProto& node);
 
 } // namespace checker
 } // namespace ONNX_NAMESPACE
diff --git a/onnx/common/path.h b/onnx/common/path.h
index e231f44a2..14bc01ce4 100644
--- a/onnx/common/path.h
+++ b/onnx/common/path.h
@@ -34,15 +34,26 @@ inline std::wstring path_join(const std::wstring& origin, const std::wstring& ap
 }
 inline std::wstring utf8str_to_wstring(const std::string& utf8str) {
   if (utf8str.size() > INT_MAX) {
     fail_check("utf8str_to_wstring: string is too long for converting to wstring.");
   }
-  int size_required = MultiByteToWideChar(CP_UTF8, 0, utf8str.c_str(), (int)utf8str.size(), NULL, 0);
+  int size_required = MultiByteToWideChar(CP_UTF8, 0, utf8str.c_str(), static_cast<int>(utf8str.size()), NULL, 0);
   std::wstring ws_str(size_required, 0);
-  MultiByteToWideChar(CP_UTF8, 0, utf8str.c_str(), (int)utf8str.size(), &ws_str[0], size_required);
+  MultiByteToWideChar(CP_UTF8, 0, utf8str.c_str(), static_cast<int>(utf8str.size()), &ws_str[0], size_required);
   return ws_str;
 }
+inline std::string wstring_to_utf8str(const std::wstring& ws_str) {
+  if (ws_str.size() > INT_MAX) {
+    fail_check("wstring_to_utf8str: string is too long for converting to UTF-8.");
+  }
+  int size_required =
+      WideCharToMultiByte(CP_UTF8, 0, ws_str.c_str(), static_cast<int>(ws_str.size()), NULL, 0, NULL, NULL);
+  std::string utf8str(size_required, 0);
+  WideCharToMultiByte(
+      CP_UTF8, 0, ws_str.c_str(), static_cast<int>(ws_str.size()), &utf8str[0], size_required, NULL, NULL);
+  return utf8str;
+}
 
 #else
 std::string path_join(const std::string& origin, const std::string& append);
 // TODO: also use std::filesystem::path for clean_relative_path after ONNX has supported C++17 for POSIX
 // Clean up relative path when there is ".." in the path, e.g.: a/b/../c -> a/c
diff --git a/onnx/cpp2py_export.cc b/onnx/cpp2py_export.cc
index 44c2f2529..ff896da16 100644
--- a/onnx/cpp2py_export.cc
+++ b/onnx/cpp2py_export.cc
@@ -533,10 +533,12 @@ PYBIND11_MODULE(onnx_cpp2py_export, onnx_cpp2py_export) {
       (void (*)(const std::string& path, bool full_check, bool skip_opset_compatibility_check)) & checker::check_model,
       "path"_a,
       "full_check"_a = false,
       "skip_opset_compatibility_check"_a = false);
 
+  checker.def("_resolve_external_data_location", &checker::resolve_external_data_location);
+
   // Submodule `version_converter`
   auto version_converter = onnx_cpp2py_export.def_submodule("version_converter");
   version_converter.doc() = "VersionConverter submodule";
   py::register_exception<ConvertError>(version_converter, "ConvertError");
 
diff --git a/onnx/external_data_helper.py b/onnx/external_data_helper.py
index 227f21565..9895be888 100644
--- a/onnx/external_data_helper.py
+++ b/onnx/external_data_helper.py
@@ -6,10 +6,11 @@ import re
 import sys
 import uuid
 from itertools import chain
 from typing import Callable, Iterable, Optional
 
+import onnx.onnx_cpp2py_export.checker as c_checker
 from onnx.onnx_pb import AttributeProto, GraphProto, ModelProto, TensorProto
 
 
 class ExternalDataInfo:
     def __init__(self, tensor: TensorProto) -> None:
@@ -36,13 +37,13 @@ def load_external_data_for_tensor(tensor: TensorProto, base_dir: str) -> None:
     Arguments:
         tensor: a TensorProto object.
         base_dir: directory that contains the external data.
     """
     info = ExternalDataInfo(tensor)
-    file_location = _sanitize_path(info.location)
-    external_data_file_path = os.path.join(base_dir, file_location)
-
+    external_data_file_path = c_checker._resolve_external_data_location(  # type: ignore[attr-defined]
+        base_dir, info.location, tensor.name
+    )
     with open(external_data_file_path, "rb") as data_file:
         if info.offset:
             data_file.seek(info.offset)
 
         if info.length:
@@ -252,18 +253,10 @@ def _get_attribute_tensors_from_graph(
 def _get_attribute_tensors(onnx_model_proto: ModelProto) -> Iterable[TensorProto]:
     """Create an iterator of tensors from node attributes of an ONNX model."""
     yield from _get_attribute_tensors_from_graph(onnx_model_proto.graph)
 
 
-def _sanitize_path(path: str) -> str:
-    """Remove path components which would allow traversing up a directory tree from a base path.
-
-    Note: This method is currently very basic and should be expanded.
-    """
-    return path.lstrip("/.")
-
-
 def _is_valid_filename(filename: str) -> bool:
     """Utility to check whether the provided filename is valid."""
     exp = re.compile('^[^<>:;,?"*|/]+$')
     match = exp.match(filename)
     return bool(match)
diff --git a/onnx/model_container.py b/onnx/model_container.py
index a2f60a430..288b5f720 100644
--- a/onnx/model_container.py
+++ b/onnx/model_container.py
@@ -13,10 +13,11 @@ from typing import Any, Iterable
 import numpy as np
 
 import onnx
 import onnx.external_data_helper as ext_data
 import onnx.helper
+import onnx.onnx_cpp2py_export.checker as c_checker
 
 
 def _set_external_data(
     tensor: onnx.TensorProto,
     location: str,
@@ -286,12 +287,13 @@ class ModelContainer:
         for i, tensor in enumerate(ext_data._get_all_tensors(self.model_proto_)):
             if not ext_data.uses_external_data(tensor):
                 continue
 
             info = ext_data.ExternalDataInfo(tensor)
-            file_location = ext_data._sanitize_path(info.location)
-            external_data_file_path = os.path.join(base_dir, file_location)
+            external_data_file_path = c_checker._resolve_external_data_location(  # type: ignore[attr-defined]
+                base_dir, info.location, tensor.name
+            )
             key = f"#t{i}"
             _set_external_data(tensor, location=key)
 
             with open(external_data_file_path, "rb") as data_file:
                 if info.offset:
diff --git a/onnx/test/test_external_data.py b/onnx/test/test_external_data.py
index 5716667cb..c606b088a 100644
--- a/onnx/test/test_external_data.py
+++ b/onnx/test/test_external_data.py
@@ -1,10 +1,11 @@
 # Copyright (c) ONNX Project Contributors
 
 # SPDX-License-Identifier: Apache-2.0
 from __future__ import annotations
 
+import itertools
 import os
 import pathlib
 import tempfile
 import unittest
 import uuid
@@ -202,10 +203,56 @@ class TestLoadExternalDataSingleFile(TestLoadExternalDataBase):
         np.testing.assert_allclose(to_array(initializer_tensor), self.initializer_value)
 
         attribute_tensor = new_model.graph.node[0].attribute[0].t
         np.testing.assert_allclose(to_array(attribute_tensor), self.attribute_value)
 
+    @parameterized.parameterized.expand(itertools.product((True, False), (True, False)))
+    def test_save_external_invalid_single_file_data_and_check(
+        self, use_absolute_path: bool, use_model_path: bool
+    ) -> None:
+        model = onnx.load_model(self.model_filename, self.serialization_format)
+
+        model_dir = os.path.join(self.temp_dir, "save_copy")
+        os.mkdir(model_dir)
+
+        traversal_external_data_dir = os.path.join(
+            self.temp_dir, "invlid_external_data"
+        )
+        os.mkdir(traversal_external_data_dir)
+
+        if use_absolute_path:
+            traversal_external_data_location = os.path.join(
+                traversal_external_data_dir, "tensors.bin"
+            )
+        else:
+            traversal_external_data_location = "../invlid_external_data/tensors.bin"
+
+        external_data_dir = os.path.join(self.temp_dir, "external_data")
+        os.mkdir(external_data_dir)
+        new_model_filepath = os.path.join(model_dir, "model.onnx")
+
+        def convert_model_to_external_data_no_check(model: ModelProto, location: str):
+            for tensor in model.graph.initializer:
+                if tensor.HasField("raw_data"):
+                    set_external_data(tensor, location)
+
+        convert_model_to_external_data_no_check(
+            model,
+            location=traversal_external_data_location,
+        )
+
+        onnx.save_model(model, new_model_filepath, self.serialization_format)
+        if use_model_path:
+            with self.assertRaises(onnx.checker.ValidationError):
+                _ = onnx.load_model(new_model_filepath, self.serialization_format)
+        else:
+            onnx_model = onnx.load_model(
+                new_model_filepath, self.serialization_format, load_external_data=False
+            )
+            with self.assertRaises(onnx.checker.ValidationError):
+                load_external_data_for_model(onnx_model, external_data_dir)
+
 
 @parameterized.parameterized_class(
     [
         {"serialization_format": "protobuf"},
         {"serialization_format": "textproto"},

Vulnerable code: #include "onnx/common/path.h"
#ifdef _WIN32
        auto file_path = std::filesystem::path(utf8str_to_wstring(entry.value()));
        if (file_path.is_absolute()) {
          fail_check(
              "Location of external TensorProto ( tensor name: ",
              tensor.name(),
              ") should be a relative path, but it is an absolute path: ",
              entry.value());
        }
        auto relative_path = file_path.lexically_normal().make_preferred().wstring();
        // Check that normalized relative path contains ".." on Windows.
        if (relative_path.find(L"..", 0) != std::string::npos) {
          fail_check(
              "Data of TensorProto ( tensor name: ",
              tensor.name(),
              ") should be file inside the ",
              ctx.get_model_dir(),
              ", but the '",
              entry.value(),
              "' points outside the directory");
        }
        std::wstring data_path = path_join(utf8str_to_wstring(ctx.get_model_dir()), relative_path);
        struct _stat64 buff;
        if (data_path.empty() || (data_path[0] != '#' && _wstat64(data_path.c_str(), &buff) != 0)) {
          fail_check(
              "Data of TensorProto ( tensor name: ",
              tensor.name(),
              ") should be stored in ",
              entry.value(),
              ", but it doesn't exist or is not accessible.");
        }
#else // POSIX
        if (entry.value().empty()) {
          fail_check("Location of external TensorProto ( tensor name: ", tensor.name(), ") should not be empty.");
        } else if (entry.value()[0] == '/') {
          fail_check(
              "Location of external TensorProto ( tensor name: ",
              tensor.name(),
              ") should be a relative path, but it is an absolute path: ",
              entry.value());
        }
        std::string relative_path = clean_relative_path(entry.value());
        // Check that normalized relative path contains ".." on POSIX
        if (relative_path.find("..", 0) != std::string::npos) {
          fail_check(
              "Data of TensorProto ( tensor name: ",
              tensor.name(),
              ") should be file inside the ",
              ctx.get_model_dir(),
              ", but the '",
              entry.value(),
              "' points outside the directory");
        }
        std::string data_path = path_join(ctx.get_model_dir(), relative_path);
        // use stat64 to check whether the file exists
#if defined(__APPLE__) || defined(__wasm__) || !defined(__GLIBC__)
        struct stat buffer; // APPLE, wasm and non-glic stdlibs do not have stat64
        if (data_path.empty() || (data_path[0] != '#' && stat((data_path).c_str(), &buffer) != 0)) {
#else
        struct stat64 buffer; // All POSIX under glibc except APPLE and wasm have stat64
        if (data_path.empty() || (data_path[0] != '#' && stat64((data_path).c_str(), &buffer) != 0)) {
#endif
          fail_check(
              "Data of TensorProto ( tensor name: ",
              tensor.name(),
              ") should be stored in ",
              data_path,
              ", but it doesn't exist or is not accessible.");
        }
        // Do not allow symlinks or directories.
        if (data_path.empty() || (data_path[0] != '#' && !S_ISREG(buffer.st_mode))) {
          fail_check(
              "Data of TensorProto ( tensor name: ",
              tensor.name(),
              ") should be stored in ",
              data_path,
              ", but it is not regular file.");
        }
#endif

  int size_required = MultiByteToWideChar(CP_UTF8, 0, utf8str.c_str(), (int)utf8str.size(), NULL, 0);
  MultiByteToWideChar(CP_UTF8, 0, utf8str.c_str(), (int)utf8str.size(), &ws_str[0], size_required);
    file_location = _sanitize_path(info.location)
    external_data_file_path = os.path.join(base_dir, file_location)

def _sanitize_path(path: str) -> str:
    """Remove path components which would allow traversing up a directory tree from a base path.

    Note: This method is currently very basic and should be expanded.
    """
    return path.lstrip("/.")


            file_location = ext_data._sanitize_path(info.location)
            external_data_file_path = os.path.join(base_dir, file_location)
Fixed code:         resolve_external_data_location(ctx.get_model_dir(), entry.value(), tensor.name());
std::string resolve_external_data_location(
    const std::string& base_dir,
    const std::string& location,
    const std::string& tensor_name) {
#ifdef _WIN32
  auto file_path = std::filesystem::path(utf8str_to_wstring(location));
  if (file_path.is_absolute()) {
    fail_check(
        "Location of external TensorProto ( tensor name: ",
        tensor_name,
        ") should be a relative path, but it is an absolute path: ",
        location);
  }
  auto relative_path = file_path.lexically_normal().make_preferred().wstring();
  // Check that normalized relative path contains ".." on Windows.
  if (relative_path.find(L"..", 0) != std::string::npos) {
    fail_check(
        "Data of TensorProto ( tensor name: ",
        tensor_name,
        ") should be file inside the ",
        base_dir,
        ", but the '",
        location,
        "' points outside the directory");
  }
  std::wstring data_path = path_join(utf8str_to_wstring(base_dir), relative_path);
  struct _stat64 buff;
  if (data_path.empty() || (data_path[0] != '#' && _wstat64(data_path.c_str(), &buff) != 0)) {
    fail_check(
        "Data of TensorProto ( tensor name: ",
        tensor_name,
        ") should be stored in ",
        location,
        ", but it doesn't exist or is not accessible.");
  }
  return wstring_to_utf8str(data_path);
#else // POSIX
  if (location.empty()) {
    fail_check("Location of external TensorProto ( tensor name: ", tensor_name, ") should not be empty.");
  } else if (location[0] == '/') {
    fail_check(
        "Location of external TensorProto ( tensor name: ",
        tensor_name,
        ") should be a relative path, but it is an absolute path: ",
        location);
  }
  std::string relative_path = clean_relative_path(location);
  // Check that normalized relative path contains ".." on POSIX
  if (relative_path.find("..", 0) != std::string::npos) {
    fail_check(
        "Data of TensorProto ( tensor name: ",
        tensor_name,
        ") should be file inside the ",
        base_dir,
        ", but the '",
        location,
        "' points outside the directory");
  }
  std::string data_path = path_join(base_dir, relative_path);
  // use stat64 to check whether the file exists
#if defined(__APPLE__) || defined(__wasm__) || !defined(__GLIBC__)
  struct stat buffer; // APPLE, wasm and non-glic stdlibs do not have stat64
  if (data_path.empty() || (data_path[0] != '#' && stat((data_path).c_str(), &buffer) != 0)) {
#else
  struct stat64 buffer; // All POSIX under glibc except APPLE and wasm have stat64
  if (data_path.empty() || (data_path[0] != '#' && stat64((data_path).c_str(), &buffer) != 0)) {
#endif
    fail_check(
        "Data of TensorProto ( tensor name: ",
        tensor_name,
        ") should be stored in ",
        data_path,
        ", but it doesn't exist or is not accessible.");
  }
  // Do not allow symlinks or directories.
  if (data_path.empty() || (data_path[0] != '#' && !S_ISREG(buffer.st_mode))) {
    fail_check(
        "Data of TensorProto ( tensor name: ",
        tensor_name,
        ") should be stored in ",
        data_path,
        ", but it is not regular file.");
  }
  return data_path;
#endif
}

std::string resolve_external_data_location(
    const std::string& base_dir,
    const std::string& location,
    const std::string& tensor_name);
  int size_required = MultiByteToWideChar(CP_UTF8, 0, utf8str.c_str(), static_cast<int>(utf8str.size()), NULL, 0);
  MultiByteToWideChar(CP_UTF8, 0, utf8str.c_str(), static_cast<int>(utf8str.size()), &ws_str[0], size_required);
inline std::string wstring_to_utf8str(const std::wstring& ws_str) {
  if (ws_str.size() > INT_MAX) {
    fail_check("wstring_to_utf8str: string is too long for converting to UTF-8.");
  }
  int size_required =
      WideCharToMultiByte(CP_UTF8, 0, ws_str.c_str(), static_cast<int>(ws_str.size()), NULL, 0, NULL, NULL);
  std::string utf8str(size_required, 0);
  WideCharToMultiByte(
      CP_UTF8, 0, ws_str.c_str(), static_cast<int>(ws_str.size()), &utf8str[0], size_required, NULL, NULL);
  return utf8str;
}
  checker.def("_resolve_external_data_location", &checker::resolve_external_data_location);

import onnx.onnx_cpp2py_export.checker as c_checker
    external_data_file_path = c_checker._resolve_external_data_location(  # type: ignore[attr-defined]
        base_dir, info.location, tensor.name
    )
import onnx.onnx_cpp2py_export.checker as c_checker
            external_data_file_path = c_checker._resolve_external_data_location(  # type: ignore[attr-defined]
                base_dir, info.location, tensor.name
            )
import itertools
    @parameterized.parameterized.expand(itertools.product((True, False), (True, False)))
    def test_save_external_invalid_single_file_data_and_check(
        self, use_absolute_path: bool, use_model_path: bool
    ) -> None:
        model = onnx.load_model(self.model_filename, self.serialization_format)

        model_dir = os.path.join(self.temp_dir, "save_copy")
        os.mkdir(model_dir)

        traversal_external_data_dir = os.path.join(
            self.temp_dir, "invlid_external_data"
        )
        os.mkdir(traversal_external_data_dir)

        if use_absolute_path:
            traversal_external_data_location = os.path.join(
                traversal_external_data_dir, "tensors.bin"
            )
        else:
            traversal_external_data_location = "../invlid_external_data/tensors.bin"

        external_data_dir = os.path.join(self.temp_dir, "external_data")
        os.mkdir(external_data_dir)
        new_model_filepath = os.path.join(model_dir, "model.onnx")

        def convert_model_to_external_data_no_check(model: ModelProto, location: str):
            for tensor in model.graph.initializer:
                if tensor.HasField("raw_data"):
                    set_external_data(tensor, location)

        convert_model_to_external_data_no_check(
            model,
            location=traversal_external_data_location,
        )

        onnx.save_model(model, new_model_filepath, self.serialization_format)
        if use_model_path:
            with self.assertRaises(onnx.checker.ValidationError):
                _ = onnx.load_model(new_model_filepath, self.serialization_format)
        else:
            onnx_model = onnx.load_model(
                new_model_filepath, self.serialization_format, load_external_data=False
            )
            with self.assertRaises(onnx.checker.ValidationError):
                load_external_data_for_model(onnx_model, external_data_dir)