|
24 | 24 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
25 | 25 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | 26 | #include "sagemaker_server.h" |
| 27 | +#include <limits.h> |
| 28 | +#include <unistd.h> |
| 29 | +#include <sys/stat.h> |
| 30 | +#include <dirent.h> |
27 | 31 |
|
28 | 32 | namespace triton { namespace server { |
29 | 33 |
|
@@ -894,6 +898,76 @@ SagemakerAPIServer::SageMakerMMELoadModel( |
894 | 898 | std::string repo_path = parse_map.at("url"); |
895 | 899 | std::string model_name_hash = parse_map.at("model_name_hash"); |
896 | 900 | 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 ================= |
897 | 971 |
|
898 | 972 | /* Check subdirs for models and find ensemble model within the repo_path |
899 | 973 | * If only 1 model, that will be selected as model_subdir |
@@ -957,7 +1031,7 @@ SagemakerAPIServer::SageMakerMMELoadModel( |
957 | 1031 | closedir(dir); |
958 | 1032 | } |
959 | 1033 |
|
960 | | - if (!strcmp(ensemble_model_subdir.c_str(), "") == 0) { |
| 1034 | + if (!ensemble_model_subdir.empty()) { |
961 | 1035 | model_subdir = ensemble_model_subdir; |
962 | 1036 | } |
963 | 1037 |
|
|
0 commit comments