W19: plugin_loader hardening — ABI try/catch, path validation, atomic IDs, CLI exit codes (W19.1-W19.5)
Some checks failed
CI / Determine matrix (push) Has been cancelled
CI / ${{ matrix.os }} / ${{ matrix.build_type }} (push) Has been cancelled

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:
2026-05-27 19:34:43 +08:00
parent c545d16120
commit 3250b5a8bf
15 changed files with 273 additions and 30 deletions

View File

@@ -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;
}