Skip to content

Commit 6da92ad

Browse files
michalkulakowskiporlows1
authored andcommitted
Fix allowed_local_media_path psirt (#4199)
1 parent 42b8a4d commit 6da92ad

8 files changed

Lines changed: 379 additions & 12 deletions

File tree

src/capi_frontend/capi.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
#include "servablemetadata.hpp"
6363
#include "server_settings.hpp"
6464
#include "serialization.hpp"
65+
#include "../filesystem/filesystem.hpp"
6566

6667
using ovms::Buffer;
6768
using ovms::ExecutionContext;
@@ -585,7 +586,7 @@ DLL_PUBLIC OVMS_Status* OVMS_ServerSettingsSetAllowedLocalMediaPath(OVMS_ServerS
585586
return reinterpret_cast<OVMS_Status*>(new Status(StatusCode::NONEXISTENT_PTR, "log path"));
586587
}
587588
ovms::ServerSettingsImpl* serverSettings = reinterpret_cast<ovms::ServerSettingsImpl*>(settings);
588-
serverSettings->allowedLocalMediaPath = allowed_local_media_path;
589+
serverSettings->allowedLocalMediaPath = ovms::FileSystem::normalizeConfiguredPath(allowed_local_media_path);
589590
return nullptr;
590591
}
591592

src/cli_parser.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ void CLIParser::prepareServer(ServerSettingsImpl& serverSettings) {
532532
serverSettings.allowedMediaDomains = result->operator[]("allowed_media_domains").as<std::vector<std::string>>();
533533
}
534534
if (result->count("allowed_local_media_path")) {
535-
serverSettings.allowedLocalMediaPath = result->operator[]("allowed_local_media_path").as<std::string>();
535+
serverSettings.allowedLocalMediaPath = FileSystem::normalizeConfiguredPath(result->operator[]("allowed_local_media_path").as<std::string>());
536536
}
537537

538538
if (result->count("grpc_bind_address"))

src/filesystem/filesystem.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
//*****************************************************************************
1616
#include "filesystem.hpp"
1717

18+
#include <algorithm>
1819
#include <memory>
1920
#ifdef _WIN32
2021
#include <windows.h>
@@ -200,4 +201,26 @@ Status FileSystem::createFileOverwrite(const std::string& filePath, const std::s
200201
return StatusCode::OK;
201202
}
202203

204+
std::string FileSystem::normalizeConfiguredPath(const std::string& pathString) {
205+
std::string normalized = pathString;
206+
#ifdef _WIN32
207+
// Backslash is a path separator only on Windows. On POSIX it is a valid
208+
// filename character; rewriting it would let a path like
209+
// "/allowed\secret.jpg" be authorized as "/allowed/secret.jpg" while
210+
// actually opening a sibling file outside the allowlist.
211+
std::replace(normalized.begin(), normalized.end(), '\\', '/');
212+
#endif
213+
std::filesystem::path path(normalized);
214+
if (path.is_relative()) {
215+
path = std::filesystem::current_path() / path;
216+
}
217+
path = path.lexically_normal();
218+
std::error_code ec;
219+
auto weakCanonicalPath = std::filesystem::weakly_canonical(path, ec);
220+
if (!ec) {
221+
return weakCanonicalPath.lexically_normal().string();
222+
}
223+
return path.string();
224+
}
225+
203226
} // namespace ovms

src/filesystem/filesystem.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ class FileSystem {
231231
return !path.empty() && (path[0] == '/');
232232
}
233233

234+
static std::string normalizeConfiguredPath(const std::string& pathString);
235+
234236
static std::string joinPath(std::initializer_list<std::string> segments) {
235237
std::string joined;
236238

src/llm/apis/openai_api_handler.cpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include <algorithm>
2020
#include <cmath>
21+
#include <filesystem>
2122
#include <limits>
2223
#include <memory>
2324
#include <sstream>
@@ -46,6 +47,17 @@ namespace ovms {
4647

4748
constexpr size_t DEFAULT_MAX_STOP_WORDS = 16; // same as deep-seek
4849

50+
namespace {
51+
52+
bool isPathInsideDirectory(const std::filesystem::path& testedPath, const std::filesystem::path& allowedDirectory) {
53+
const auto mismatch = std::mismatch(
54+
allowedDirectory.begin(), allowedDirectory.end(),
55+
testedPath.begin(), testedPath.end());
56+
return mismatch.first == allowedDirectory.end();
57+
}
58+
59+
} // namespace
60+
4961
ov::genai::JsonContainer rapidJsonValueToJsonContainer(const rapidjson::Value& value) {
5062
if (value.IsNull()) {
5163
return ov::genai::JsonContainer(nullptr);
@@ -234,15 +246,17 @@ absl::StatusOr<ov::Tensor> loadImage(const std::string& imageSource,
234246
return absl::InvalidArgumentError(ss.str());
235247
}
236248
SPDLOG_LOGGER_TRACE(llm_calculator_logger, "Loading image from local filesystem");
237-
const auto firstMissmatch = std::mismatch(imageSource.begin(), imageSource.end(), allowedLocalMediaPath.value().begin(), allowedLocalMediaPath.value().end());
238-
if (firstMissmatch.second != allowedLocalMediaPath.value().end()) {
249+
const std::filesystem::path resolvedAllowedPath = FileSystem::normalizeConfiguredPath(allowedLocalMediaPath.value());
250+
const std::string resolvedImagePathStr = FileSystem::normalizeConfiguredPath(imageSource);
251+
const std::filesystem::path resolvedImagePath = resolvedImagePathStr;
252+
if (!isPathInsideDirectory(resolvedImagePath, resolvedAllowedPath)) {
239253
return absl::InvalidArgumentError("Given filepath is not subpath of allowed_local_media_path");
240254
}
241255
try {
242-
tensor = loadImageStbiFromFile(imageSource.c_str());
256+
tensor = loadImageStbiFromFile(resolvedImagePathStr.c_str());
243257
} catch (std::runtime_error& e) {
244258
std::stringstream ss;
245-
ss << "Image file " << imageSource.c_str() << " parsing failed: " << e.what();
259+
ss << "Image file " << resolvedImagePathStr << " parsing failed: " << e.what();
246260
SPDLOG_LOGGER_DEBUG(llm_calculator_logger, ss.str());
247261
return absl::InvalidArgumentError(ss.str());
248262
}

src/test/c_api_tests.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "../capi_frontend/capi_dag_utils.hpp"
3939
#include "../capi_frontend/servablemetadata.hpp"
4040
#include "../dags/pipelinedefinitionstatus.hpp"
41+
#include "../filesystem/filesystem.hpp"
4142
#include "src/metrics/metric_module.hpp"
4243
#include "../ovms.h"
4344
#include "../servablemanagermodule.hpp"
@@ -183,7 +184,7 @@ TEST(CAPIConfigTest, MultiModelConfiguration) {
183184
EXPECT_EQ(serverSettings->logLevel, "TRACE");
184185
EXPECT_EQ(serverSettings->logPath, getGenericFullPathForTmp("/tmp/logs"));
185186
ASSERT_TRUE(serverSettings->allowedLocalMediaPath.has_value());
186-
EXPECT_EQ(serverSettings->allowedLocalMediaPath.value(), getGenericFullPathForTmp("/tmp/path"));
187+
EXPECT_EQ(serverSettings->allowedLocalMediaPath.value(), ovms::FileSystem::normalizeConfiguredPath(getGenericFullPathForTmp("/tmp/path")));
187188
ASSERT_TRUE(serverSettings->allowedMediaDomains.has_value());
188189
EXPECT_EQ(serverSettings->allowedMediaDomains.value().size(), 3);
189190
EXPECT_EQ(serverSettings->allowedMediaDomains.value()[0], "raw.githubusercontent.com");
@@ -258,6 +259,22 @@ TEST(CAPIConfigTest, SingleModelConfiguration) {
258259
GTEST_SKIP() << "Use C-API to initialize in next stages, currently not supported";
259260
}
260261

262+
TEST(CAPIConfigTest, AllowedLocalMediaPathRelativeIsNormalized) {
263+
OVMS_ServerSettings* serverSettingsRaw = nullptr;
264+
ASSERT_CAPI_STATUS_NULL(OVMS_ServerSettingsNew(&serverSettingsRaw));
265+
ASSERT_NE(serverSettingsRaw, nullptr);
266+
ServerSettingsImpl* serverSettings = reinterpret_cast<ServerSettingsImpl*>(serverSettingsRaw);
267+
268+
ASSERT_CAPI_STATUS_NULL(OVMS_ServerSettingsSetAllowedLocalMediaPath(serverSettingsRaw, "src/test"));
269+
ASSERT_TRUE(serverSettings->allowedLocalMediaPath.has_value());
270+
271+
const auto configuredPath = std::filesystem::path(serverSettings->allowedLocalMediaPath.value());
272+
const auto expectedPath = std::filesystem::path(ovms::FileSystem::normalizeConfiguredPath("src/test"));
273+
EXPECT_EQ(configuredPath.lexically_normal(), expectedPath.lexically_normal());
274+
275+
OVMS_ServerSettingsDelete(serverSettingsRaw);
276+
}
277+
261278
TEST(CAPIStartTest, InitializingMultipleServers) {
262279
OVMS_Server* srv1 = nullptr;
263280
OVMS_Server* srv2 = nullptr;

0 commit comments

Comments
 (0)