Skip to content

Commit 6165d7d

Browse files
committed
sagemaker: restrict model repository paths to configured root
Signed-off-by: Sarvesh Patil <psarvesh129@gmail.com>
1 parent 1307ace commit 6165d7d

1 file changed

Lines changed: 75 additions & 1 deletion

File tree

src/sagemaker_server.cc

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2525
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2626
#include "sagemaker_server.h"
27+
#include <limits.h>
28+
#include <unistd.h>
29+
#include <sys/stat.h>
30+
#include <dirent.h>
2731

2832
namespace triton { namespace server {
2933

@@ -894,6 +898,76 @@ SagemakerAPIServer::SageMakerMMELoadModel(
894898
std::string repo_path = parse_map.at("url");
895899
std::string model_name_hash = parse_map.at("model_name_hash");
896900
std::string target_model = parse_map.at("target_model");
901+
// ================= SECURITY CONFINEMENT FIX =================
902+
903+
// Model repository root must be configured
904+
if (model_repository_path_.empty()) {
905+
EVBufferAddErrorJson(
906+
req->buffer_out,
907+
TRITONSERVER_ErrorNew(
908+
TRITONSERVER_ERROR_INTERNAL,
909+
"Model repository root is not configured"));
910+
evhtp_send_reply(req, EVHTP_RES_BADREQ);
911+
return;
912+
}
913+
914+
// Canonicalize user-supplied path
915+
char resolved_repo[PATH_MAX];
916+
if (realpath(repo_path.c_str(), resolved_repo) == nullptr) {
917+
EVBufferAddErrorJson(
918+
req->buffer_out,
919+
TRITONSERVER_ErrorNew(
920+
TRITONSERVER_ERROR_INVALID_ARG,
921+
"Invalid model repository path"));
922+
evhtp_send_reply(req, EVHTP_RES_BADREQ);
923+
return;
924+
}
925+
926+
std::string canonical_repo_path(resolved_repo);
927+
928+
// Canonicalize allowed root
929+
char resolved_root[PATH_MAX];
930+
if (realpath(model_repository_path_.c_str(), resolved_root) == nullptr) {
931+
EVBufferAddErrorJson(
932+
req->buffer_out,
933+
TRITONSERVER_ErrorNew(
934+
TRITONSERVER_ERROR_INTERNAL,
935+
"Failed to resolve model repository root"));
936+
evhtp_send_reply(req, EVHTP_RES_BADREQ);
937+
return;
938+
}
939+
940+
std::string canonical_root(resolved_root);
941+
942+
// Enforce confinement: repo must be within root
943+
if (canonical_repo_path.compare(0, canonical_root.size(), canonical_root) != 0 ||
944+
(canonical_repo_path.size() > canonical_root.size() &&
945+
canonical_repo_path[canonical_root.size()] != '/')) {
946+
EVBufferAddErrorJson(
947+
req->buffer_out,
948+
TRITONSERVER_ErrorNew(
949+
TRITONSERVER_ERROR_INVALID_ARG,
950+
"Model repository path escapes allowed root"));
951+
evhtp_send_reply(req, EVHTP_RES_BADREQ);
952+
return;
953+
}
954+
955+
// Enforce directory-only repositories
956+
struct stat st;
957+
if (stat(canonical_repo_path.c_str(), &st) != 0 || !S_ISDIR(st.st_mode)) {
958+
EVBufferAddErrorJson(
959+
req->buffer_out,
960+
TRITONSERVER_ErrorNew(
961+
TRITONSERVER_ERROR_INVALID_ARG,
962+
"Model repository path must be a directory"));
963+
evhtp_send_reply(req, EVHTP_RES_BADREQ);
964+
return;
965+
}
966+
967+
// Use canonicalized path from here onward
968+
repo_path = canonical_repo_path;
969+
970+
// ================= END SECURITY CONFINEMENT FIX =================
897971

898972
/* Check subdirs for models and find ensemble model within the repo_path
899973
* If only 1 model, that will be selected as model_subdir
@@ -957,7 +1031,7 @@ SagemakerAPIServer::SageMakerMMELoadModel(
9571031
closedir(dir);
9581032
}
9591033

960-
if (!strcmp(ensemble_model_subdir.c_str(), "") == 0) {
1034+
if (!ensemble_model_subdir.empty()) {
9611035
model_subdir = ensemble_model_subdir;
9621036
}
9631037

0 commit comments

Comments
 (0)