From 8eca70d7b0d8dac7578766aafe10ec17aa463b7a Mon Sep 17 00:00:00 2001 From: J Wyman Date: Fri, 13 Feb 2026 13:53:12 -0500 Subject: [PATCH 1/6] fix: Path Traversal Bug in SageMaker Server This change prevents the /dev/, /proc/, or /sys/ folders being used to load models from in `SagemakerAPIServer` to avoid issues with interacting with pseudo-filesystem paths. --- src/sagemaker_server.cc | 23 +++++++++++++++++++++-- src/sagemaker_server.h | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/sagemaker_server.cc b/src/sagemaker_server.cc index cf379e910e..f47a1e59ef 100644 --- a/src/sagemaker_server.cc +++ b/src/sagemaker_server.cc @@ -23,6 +23,7 @@ // OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + #include "sagemaker_server.h" namespace triton { namespace server { @@ -291,6 +292,15 @@ SagemakerAPIServer::ParseSageMakerRequest( } } + if (url_string.find("/dev/") == 0 || url_string.find("/proc/") == 0 || + url_string.find("/sys/") == 0) { + LOG_ERROR << "Invalid URL: " << url_string + << ". \"url\" property value cannot start with /dev/, /proc/, or " + "/sys/." << std::endl; + evhtp_send_reply(req, EVHTP_RES_BADREQ); + return; + } + if (action == "load") { (*parse_map)["url"] = url_string.c_str(); } @@ -895,6 +905,15 @@ SagemakerAPIServer::SageMakerMMELoadModel( std::string model_name_hash = parse_map.at("model_name_hash"); std::string target_model = parse_map.at("target_model"); + if (repo_path.find("/dev/") == 0 || repo_path.find("/proc/") == 0 || + repo_path.find("/sys/") == 0) { + LOG_ERROR << "Invalid repository path: " << repo_path + << ". \"url\" property of `parse_map`cannot start with /dev/, " + "/proc/, or /sys/." << std::endl; + evhtp_send_reply(req, EVHTP_RES_BADREQ); + return; + } + /* Check subdirs for models and find ensemble model within the repo_path * If only 1 model, that will be selected as model_subdir * Else ensemble model directory is set as model_subdir @@ -905,6 +924,7 @@ SagemakerAPIServer::SageMakerMMELoadModel( std::string model_subdir, ensemble_model_subdir; if ((dir = opendir(repo_path.c_str())) != NULL) { + std::shared_ptr dir_ptr{dir, closedir}; while ((ent = readdir(dir)) != NULL) { if ((ent->d_type == DT_DIR) && (!strcmp(ent->d_name, ".") == 0) && (!strcmp(ent->d_name, "..") == 0)) { @@ -923,7 +943,7 @@ SagemakerAPIServer::SageMakerMMELoadModel( // Read the config.pbtxt file at each path, if available std::string ensemble_config_path = - repo_path + "/" + model_subdir + "/" + "config.pbtxt"; + repo_path + "/" + model_subdir + "/config.pbtxt"; std::ifstream config_fstream(ensemble_config_path); std::stringstream ensemble_config_content; @@ -954,7 +974,6 @@ SagemakerAPIServer::SageMakerMMELoadModel( << std::endl; } } - closedir(dir); } if (!strcmp(ensemble_model_subdir.c_str(), "") == 0) { diff --git a/src/sagemaker_server.h b/src/sagemaker_server.h index dcd40e66ac..b1767dad6e 100644 --- a/src/sagemaker_server.h +++ b/src/sagemaker_server.h @@ -23,6 +23,7 @@ // OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + #pragma once #include From 27cfb62d9d7f44e93d2303d28a35775e17e00db9 Mon Sep 17 00:00:00 2001 From: J Wyman Date: Wed, 18 Feb 2026 16:12:43 -0500 Subject: [PATCH 2/6] lexically_normal-ize path before testing --- src/sagemaker_server.cc | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/sagemaker_server.cc b/src/sagemaker_server.cc index f47a1e59ef..50a255ac44 100644 --- a/src/sagemaker_server.cc +++ b/src/sagemaker_server.cc @@ -26,6 +26,8 @@ #include "sagemaker_server.h" +#include + namespace triton { namespace server { #define HTTP_RESPOND_IF_ERR(REQ, X) \ @@ -292,8 +294,12 @@ SagemakerAPIServer::ParseSageMakerRequest( } } - if (url_string.find("/dev/") == 0 || url_string.find("/proc/") == 0 || - url_string.find("/sys/") == 0) { + std::filesystem::path url_path(url_string); + url_path = std::filesystem::absolute(url_path.lexically_normal()); // Normalize the path to remove any redundant components. + auto repo_path = url_path.string(); + + if (repo_path.find("/dev/") == 0 || repo_path.find("/proc/") == 0 + || repo_path.find("/sys/") == 0) { LOG_ERROR << "Invalid URL: " << url_string << ". \"url\" property value cannot start with /dev/, /proc/, or " "/sys/." << std::endl; @@ -901,13 +907,17 @@ SagemakerAPIServer::SageMakerMMELoadModel( evhtp_request_t* req, const std::unordered_map parse_map) { - std::string repo_path = parse_map.at("url"); + std::string url_string = parse_map.at("url"); std::string model_name_hash = parse_map.at("model_name_hash"); std::string target_model = parse_map.at("target_model"); - if (repo_path.find("/dev/") == 0 || repo_path.find("/proc/") == 0 || - repo_path.find("/sys/") == 0) { - LOG_ERROR << "Invalid repository path: " << repo_path + std::filesystem::path url_path(url_string); + url_path = std::filesystem::absolute(url_path.lexically_normal()); // Normalize the path to remove any redundant components. + std::string repo_path = url_path.string(); + + if (repo_path.find("/dev/") == 0 || repo_path.find("/proc/") == 0 + || repo_path.find("/sys/") == 0) { + LOG_ERROR << "Invalid repository path: " << url_string << ". \"url\" property of `parse_map`cannot start with /dev/, " "/proc/, or /sys/." << std::endl; evhtp_send_reply(req, EVHTP_RES_BADREQ); From 44115cb025aa98de94ac6b28904c9eb538d62e6e Mon Sep 17 00:00:00 2001 From: J Wyman Date: Thu, 19 Feb 2026 11:45:24 -0500 Subject: [PATCH 3/6] fixup pre-commit complaints --- src/sagemaker_server.cc | 22 ++++++++++++++-------- src/sagemaker_server.h | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/sagemaker_server.cc b/src/sagemaker_server.cc index 50a255ac44..c24012e016 100644 --- a/src/sagemaker_server.cc +++ b/src/sagemaker_server.cc @@ -295,14 +295,17 @@ SagemakerAPIServer::ParseSageMakerRequest( } std::filesystem::path url_path(url_string); - url_path = std::filesystem::absolute(url_path.lexically_normal()); // Normalize the path to remove any redundant components. + url_path = std::filesystem::absolute( + url_path.lexically_normal()); // Normalize the path to remove any + // redundant components. auto repo_path = url_path.string(); - if (repo_path.find("/dev/") == 0 || repo_path.find("/proc/") == 0 - || repo_path.find("/sys/") == 0) { + if (repo_path.find("/dev/") == 0 || repo_path.find("/proc/") == 0 || + repo_path.find("/sys/") == 0) { LOG_ERROR << "Invalid URL: " << url_string << ". \"url\" property value cannot start with /dev/, /proc/, or " - "/sys/." << std::endl; + "/sys/." + << std::endl; evhtp_send_reply(req, EVHTP_RES_BADREQ); return; } @@ -912,14 +915,17 @@ SagemakerAPIServer::SageMakerMMELoadModel( std::string target_model = parse_map.at("target_model"); std::filesystem::path url_path(url_string); - url_path = std::filesystem::absolute(url_path.lexically_normal()); // Normalize the path to remove any redundant components. + url_path = std::filesystem::absolute( + url_path.lexically_normal()); // Normalize the path to remove any + // redundant components. std::string repo_path = url_path.string(); - if (repo_path.find("/dev/") == 0 || repo_path.find("/proc/") == 0 - || repo_path.find("/sys/") == 0) { + if (repo_path.find("/dev/") == 0 || repo_path.find("/proc/") == 0 || + repo_path.find("/sys/") == 0) { LOG_ERROR << "Invalid repository path: " << url_string << ". \"url\" property of `parse_map`cannot start with /dev/, " - "/proc/, or /sys/." << std::endl; + "/proc/, or /sys/." + << std::endl; evhtp_send_reply(req, EVHTP_RES_BADREQ); return; } diff --git a/src/sagemaker_server.h b/src/sagemaker_server.h index b1767dad6e..1aef149605 100644 --- a/src/sagemaker_server.h +++ b/src/sagemaker_server.h @@ -1,4 +1,4 @@ -// Copyright 2021-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// Copyright 2021-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions From 94207efaf47fc19c72f7f631ce27da7fc62793d9 Mon Sep 17 00:00:00 2001 From: J Wyman Date: Mon, 23 Feb 2026 13:29:13 -0500 Subject: [PATCH 4/6] Update src/sagemaker_server.cc Co-authored-by: Yingge He <157551214+yinggeh@users.noreply.github.com> --- src/sagemaker_server.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sagemaker_server.cc b/src/sagemaker_server.cc index c24012e016..8a0efabc70 100644 --- a/src/sagemaker_server.cc +++ b/src/sagemaker_server.cc @@ -298,7 +298,7 @@ SagemakerAPIServer::ParseSageMakerRequest( url_path = std::filesystem::absolute( url_path.lexically_normal()); // Normalize the path to remove any // redundant components. - auto repo_path = url_path.string(); + auto url_abspath = url_path.string(); if (repo_path.find("/dev/") == 0 || repo_path.find("/proc/") == 0 || repo_path.find("/sys/") == 0) { From 1b13522345b5019a9342bc3b8298e466473c9a95 Mon Sep 17 00:00:00 2001 From: J Wyman Date: Mon, 23 Feb 2026 13:29:23 -0500 Subject: [PATCH 5/6] Update src/sagemaker_server.cc Co-authored-by: Yingge He <157551214+yinggeh@users.noreply.github.com> --- src/sagemaker_server.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sagemaker_server.cc b/src/sagemaker_server.cc index 8a0efabc70..8765dbbba1 100644 --- a/src/sagemaker_server.cc +++ b/src/sagemaker_server.cc @@ -918,7 +918,7 @@ SagemakerAPIServer::SageMakerMMELoadModel( url_path = std::filesystem::absolute( url_path.lexically_normal()); // Normalize the path to remove any // redundant components. - std::string repo_path = url_path.string(); + std::string url_abspath = url_path.string(); if (repo_path.find("/dev/") == 0 || repo_path.find("/proc/") == 0 || repo_path.find("/sys/") == 0) { From 425bccaa967340af0b822a8a51ec844e6a3cf304 Mon Sep 17 00:00:00 2001 From: J Wyman Date: Tue, 24 Feb 2026 13:33:19 -0500 Subject: [PATCH 6/6] fix build break after GH suggestion acceptance --- src/sagemaker_server.cc | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/sagemaker_server.cc b/src/sagemaker_server.cc index 8765dbbba1..637be6fc9c 100644 --- a/src/sagemaker_server.cc +++ b/src/sagemaker_server.cc @@ -300,8 +300,8 @@ SagemakerAPIServer::ParseSageMakerRequest( // redundant components. auto url_abspath = url_path.string(); - if (repo_path.find("/dev/") == 0 || repo_path.find("/proc/") == 0 || - repo_path.find("/sys/") == 0) { + if (url_abspath.find("/dev/") == 0 || url_abspath.find("/proc/") == 0 || + url_abspath.find("/sys/") == 0) { LOG_ERROR << "Invalid URL: " << url_string << ". \"url\" property value cannot start with /dev/, /proc/, or " "/sys/." @@ -920,8 +920,8 @@ SagemakerAPIServer::SageMakerMMELoadModel( // redundant components. std::string url_abspath = url_path.string(); - if (repo_path.find("/dev/") == 0 || repo_path.find("/proc/") == 0 || - repo_path.find("/sys/") == 0) { + if (url_abspath.find("/dev/") == 0 || url_abspath.find("/proc/") == 0 || + url_abspath.find("/sys/") == 0) { LOG_ERROR << "Invalid repository path: " << url_string << ". \"url\" property of `parse_map`cannot start with /dev/, " "/proc/, or /sys/." @@ -930,7 +930,7 @@ SagemakerAPIServer::SageMakerMMELoadModel( return; } - /* Check subdirs for models and find ensemble model within the repo_path + /* Check subdirs for models and find ensemble model within the url_abspath * If only 1 model, that will be selected as model_subdir * Else ensemble model directory is set as model_subdir */ @@ -939,7 +939,7 @@ SagemakerAPIServer::SageMakerMMELoadModel( int dir_count = 0; std::string model_subdir, ensemble_model_subdir; - if ((dir = opendir(repo_path.c_str())) != NULL) { + if ((dir = opendir(url_abspath.c_str())) != NULL) { std::shared_ptr dir_ptr{dir, closedir}; while ((ent = readdir(dir)) != NULL) { if ((ent->d_type == DT_DIR) && (!strcmp(ent->d_name, ".") == 0) && @@ -959,7 +959,7 @@ SagemakerAPIServer::SageMakerMMELoadModel( // Read the config.pbtxt file at each path, if available std::string ensemble_config_path = - repo_path + "/" + model_subdir + "/config.pbtxt"; + url_abspath + "/" + model_subdir + "/config.pbtxt"; std::ifstream config_fstream(ensemble_config_path); std::stringstream ensemble_config_content; @@ -1007,10 +1007,10 @@ SagemakerAPIServer::SageMakerMMELoadModel( std::string repo_parent_path, subdir, customer_subdir; RE2::FullMatch( - repo_path, model_path_regex_, &repo_parent_path, &subdir, + url_abspath, model_path_regex_, &repo_parent_path, &subdir, &customer_subdir); - std::string config_path = repo_path + "/config.pbtxt"; + std::string config_path = url_abspath + "/config.pbtxt"; struct stat buffer; /* If config.pbtxt is at repo root, @@ -1021,7 +1021,7 @@ SagemakerAPIServer::SageMakerMMELoadModel( if (stat(config_path.c_str(), &buffer) == 0) { model_subdir = subdir; } else { - repo_parent_path = repo_path; + repo_parent_path = url_abspath; } auto param = TRITONSERVER_ParameterNew(