W19: plugin_loader hardening — ABI try/catch, path validation, atomic IDs, CLI exit codes (W19.1-W19.5)
Fixes: F-18.3-1 through F-18.3-5 (all CLOSED, findings registry at zero) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,9 @@
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cctype>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <queue>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
@@ -17,6 +19,7 @@
|
||||
namespace dstalk {
|
||||
|
||||
namespace json = boost::json;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
PluginLoader::~PluginLoader()
|
||||
{
|
||||
@@ -27,6 +30,64 @@ int PluginLoader::load_plugin(const char* path)
|
||||
{
|
||||
if (!path) return -1;
|
||||
|
||||
// === Path validation (F-18.3-3) ===
|
||||
{
|
||||
fs::path p = fs::absolute(fs::path(path)).lexically_normal();
|
||||
|
||||
// Extension check (case-insensitive)
|
||||
std::string ext = p.extension().string();
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
|
||||
bool valid_ext = false;
|
||||
#ifdef _WIN32
|
||||
valid_ext = (ext == ".dll");
|
||||
#elif defined(__APPLE__)
|
||||
valid_ext = (ext == ".dylib" || ext == ".so");
|
||||
#else
|
||||
valid_ext = (ext == ".so");
|
||||
#endif
|
||||
|
||||
if (!valid_ext) {
|
||||
if (host_api_) {
|
||||
host_api_->log(DSTALK_LOG_ERROR,
|
||||
"[plugin_loader] '%s': invalid extension '%s', expected .dll/.so/.dylib",
|
||||
path, ext.c_str());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Directory traversal check
|
||||
bool has_dotdot = false;
|
||||
bool in_plugins_dir = false;
|
||||
for (const auto& comp : p) {
|
||||
if (comp == "..") {
|
||||
has_dotdot = true;
|
||||
break;
|
||||
}
|
||||
if (comp == "plugins") {
|
||||
in_plugins_dir = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_dotdot) {
|
||||
if (host_api_) {
|
||||
host_api_->log(DSTALK_LOG_ERROR,
|
||||
"[plugin_loader] '%s': directory traversal rejected", path);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Directory constraint: must be under a 'plugins' directory or be a plain filename
|
||||
if (!in_plugins_dir && p.has_parent_path()) {
|
||||
if (host_api_) {
|
||||
host_api_->log(DSTALK_LOG_ERROR,
|
||||
"[plugin_loader] '%s': path not under a 'plugins' directory", path);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载DLL
|
||||
#ifdef _WIN32
|
||||
void* handle = LoadLibraryA(path);
|
||||
@@ -35,6 +96,16 @@ int PluginLoader::load_plugin(const char* path)
|
||||
#endif
|
||||
|
||||
if (!handle) {
|
||||
if (host_api_) {
|
||||
#ifdef _WIN32
|
||||
DWORD err = GetLastError();
|
||||
host_api_->log(DSTALK_LOG_ERROR,
|
||||
"[plugin_loader] '%s': LoadLibraryA failed (error %lu)", path, (unsigned long)err);
|
||||
#else
|
||||
host_api_->log(DSTALK_LOG_ERROR,
|
||||
"[plugin_loader] '%s': dlopen failed: %s", path, dlerror());
|
||||
#endif
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -47,6 +118,18 @@ int PluginLoader::load_plugin(const char* path)
|
||||
#endif
|
||||
|
||||
if (!init_fn) {
|
||||
if (host_api_) {
|
||||
#ifdef _WIN32
|
||||
DWORD err = GetLastError();
|
||||
host_api_->log(DSTALK_LOG_ERROR,
|
||||
"[plugin_loader] '%s': GetProcAddress(dstalk_plugin_init) failed (error %lu)",
|
||||
path, (unsigned long)err);
|
||||
#else
|
||||
host_api_->log(DSTALK_LOG_ERROR,
|
||||
"[plugin_loader] '%s': dlsym(dstalk_plugin_init) failed: %s",
|
||||
path, dlerror());
|
||||
#endif
|
||||
}
|
||||
#ifdef _WIN32
|
||||
FreeLibrary((HMODULE)handle);
|
||||
#else
|
||||
@@ -56,8 +139,19 @@ int PluginLoader::load_plugin(const char* path)
|
||||
}
|
||||
|
||||
// 调用入口函数获取插件信息
|
||||
dstalk_plugin_info_t* info = init_fn();
|
||||
dstalk_plugin_info_t* info = nullptr;
|
||||
try {
|
||||
info = init_fn();
|
||||
} catch (const std::exception& e) {
|
||||
if (host_api_) host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] %s: init_fn threw: %s", path, e.what());
|
||||
} catch (...) {
|
||||
if (host_api_) host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] %s: init_fn threw unknown exception", path);
|
||||
}
|
||||
if (!info) {
|
||||
if (host_api_) {
|
||||
host_api_->log(DSTALK_LOG_ERROR,
|
||||
"[plugin_loader] '%s': dstalk_plugin_init returned null", path);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
FreeLibrary((HMODULE)handle);
|
||||
#else
|
||||
@@ -68,6 +162,11 @@ int PluginLoader::load_plugin(const char* path)
|
||||
|
||||
// 检查API版本兼容性
|
||||
if (info->api_version != DSTALK_API_VERSION) {
|
||||
if (host_api_) {
|
||||
host_api_->log(DSTALK_LOG_ERROR,
|
||||
"[plugin_loader] '%s': API version mismatch (got %d, expected %d)",
|
||||
path, info->api_version, DSTALK_API_VERSION);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
FreeLibrary((HMODULE)handle);
|
||||
#else
|
||||
@@ -106,7 +205,15 @@ int PluginLoader::unload_plugin(int plugin_id)
|
||||
|
||||
// 调用关闭回调
|
||||
if (plugin.initialized && plugin.info->on_shutdown) {
|
||||
plugin.info->on_shutdown();
|
||||
try {
|
||||
plugin.info->on_shutdown();
|
||||
} catch (const std::exception& e) {
|
||||
if (host_api_) host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' on_shutdown threw: %s",
|
||||
plugin.name.c_str(), e.what());
|
||||
} catch (...) {
|
||||
if (host_api_) host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' on_shutdown threw unknown exception",
|
||||
plugin.name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// 卸载DLL
|
||||
@@ -202,6 +309,7 @@ std::vector<int> PluginLoader::topological_sort() const
|
||||
int PluginLoader::initialize_all(const dstalk_host_api_t* host_api)
|
||||
{
|
||||
if (!host_api) return -1;
|
||||
host_api_ = host_api;
|
||||
|
||||
try {
|
||||
std::vector<int> order = topological_sort();
|
||||
@@ -226,21 +334,36 @@ int PluginLoader::initialize_all(const dstalk_host_api_t* host_api)
|
||||
}
|
||||
|
||||
if (dep_unavailable) {
|
||||
fprintf(stderr, "[WARN] Plugin '%s' skipped: dependency unavailable\n",
|
||||
plugin.name.c_str());
|
||||
host_api->log(DSTALK_LOG_WARN, "[plugin_loader] Plugin '%s' skipped: dependency unavailable",
|
||||
plugin.name.c_str());
|
||||
failed_names.insert(plugin.name);
|
||||
failed_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (plugin.info->on_init) {
|
||||
int result = plugin.info->on_init(host_api);
|
||||
if (result != 0) {
|
||||
fprintf(stderr, "[ERROR] Plugin '%s' init failed (code %d)\n",
|
||||
plugin.name.c_str(), result);
|
||||
int result;
|
||||
try {
|
||||
result = plugin.info->on_init(host_api);
|
||||
} catch (const std::exception& e) {
|
||||
host_api->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' init threw: %s",
|
||||
plugin.name.c_str(), e.what());
|
||||
failed_names.insert(plugin.name);
|
||||
failed_count++;
|
||||
continue; // 不设置 initialized=true
|
||||
continue;
|
||||
} catch (...) {
|
||||
host_api->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' init threw unknown exception",
|
||||
plugin.name.c_str());
|
||||
failed_names.insert(plugin.name);
|
||||
failed_count++;
|
||||
continue;
|
||||
}
|
||||
if (result != 0) {
|
||||
host_api->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' init failed (code %d)",
|
||||
plugin.name.c_str(), result);
|
||||
failed_names.insert(plugin.name);
|
||||
failed_count++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
plugin.initialized = true;
|
||||
@@ -257,6 +380,7 @@ int PluginLoader::initialize_all(const dstalk_host_api_t* host_api)
|
||||
|
||||
int PluginLoader::initialize_pending(const dstalk_host_api_t* host_api)
|
||||
{
|
||||
host_api_ = host_api;
|
||||
try {
|
||||
std::vector<int> order = topological_sort();
|
||||
|
||||
@@ -269,7 +393,18 @@ int PluginLoader::initialize_pending(const dstalk_host_api_t* host_api)
|
||||
if (plugin.initialized) continue;
|
||||
|
||||
if (plugin.info->on_init) {
|
||||
int result = plugin.info->on_init(host_api);
|
||||
int result;
|
||||
try {
|
||||
result = plugin.info->on_init(host_api);
|
||||
} catch (const std::exception& e) {
|
||||
if (host_api) host_api->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' init threw: %s",
|
||||
plugin.name.c_str(), e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
if (host_api) host_api->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' init threw unknown exception",
|
||||
plugin.name.c_str());
|
||||
return -1;
|
||||
}
|
||||
if (result != 0) {
|
||||
return -1;
|
||||
}
|
||||
@@ -304,7 +439,15 @@ void PluginLoader::shutdown_all()
|
||||
|
||||
PluginInfo& plugin = it->second;
|
||||
if (plugin.initialized && plugin.info->on_shutdown) {
|
||||
plugin.info->on_shutdown();
|
||||
try {
|
||||
plugin.info->on_shutdown();
|
||||
} catch (const std::exception& e) {
|
||||
if (host_api_) host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' shutdown threw: %s",
|
||||
plugin.name.c_str(), e.what());
|
||||
} catch (...) {
|
||||
if (host_api_) host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' shutdown threw unknown exception",
|
||||
plugin.name.c_str());
|
||||
}
|
||||
}
|
||||
plugin.initialized = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user