Harden plugin runtime: TLS verify, LSP deadlock, path traversal, ABI exception safety (W14)
W14 addresses the five most critical findings from the W13 plugin audits: - W14.1 network: enable ssl::verify_peer + SSL_set1_host SNI hostname verification (fixes TLS bypass, W13.3 CVSS 7.4); add steady_timer DNS timeout and bottom-up catch(...) hardening (engineer-zhou) - W14.2 lsp: fix reader_loop/stop mutex deadlock via stop_nolock/stop_locked split (W13.4); wrap 11 vtable/entry functions in try/catch with cv notification on reader exit (engineer-sun) - W14.3 tools: add is_safe_path() rejecting empty/absolute/.. paths before file_io calls (fixes path traversal, W13.5 CVSS 7.5); guard g_tools and g_session/g_history under mutex; 9 vtable try/catch (security-cao) - W14.4 host: add fallback plugin search (../plugins/) so binaries run from build/tests/ load current DLLs, resolving the W13.6 R2 stale-DLL false alarm (architect-lin) - W14.5 anthropic+deepseek: wrap 12 ABI boundary functions in try/catch with log-guard, preventing exceptions from crossing the C ABI (engineer-chen) Verified: cmake build 0 error 0 warning, ctest 4/4 pass, smoke R2 now passes naturally. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/ssl.hpp>
|
||||
@@ -90,6 +91,12 @@ struct HttpClientCtx {
|
||||
|
||||
HttpClientCtx() {
|
||||
ssl_ctx.set_default_verify_paths();
|
||||
// Enable peer certificate verification (CVSS 7.4 fix).
|
||||
// set_default_verify_paths() loads system CA bundle; without verify_peer
|
||||
// the CA store is never consulted — any cert (self-signed/expired) is accepted.
|
||||
// TODO: Windows: set_default_verify_paths() may not locate system CAs;
|
||||
// if verification fails, set SSL_CERT_FILE env or bundle a cacert.pem.
|
||||
ssl_ctx.set_verify_mode(ssl::verify_peer);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,17 +146,51 @@ static int do_post_stream(
|
||||
|
||||
try {
|
||||
tcp::resolver resolver(ctx.ioc);
|
||||
auto endpoints = resolver.resolve(host, port);
|
||||
|
||||
// DNS resolve with 10-second timeout. Boost.Asio's synchronous
|
||||
// resolve() runs the io_context internally, so the timer's async_wait
|
||||
// callback executes during resolve() and calls resolver.cancel() when
|
||||
// the deadline fires.
|
||||
asio::steady_timer resolve_timer(ctx.ioc);
|
||||
resolve_timer.expires_after(std::chrono::seconds(10));
|
||||
resolve_timer.async_wait([&](const beast::error_code& ec) {
|
||||
if (!ec) resolver.cancel();
|
||||
});
|
||||
|
||||
beast::error_code resolve_ec;
|
||||
auto endpoints = resolver.resolve(host, port, resolve_ec);
|
||||
resolve_timer.cancel();
|
||||
|
||||
if (resolve_ec) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR,
|
||||
"do_post_stream: DNS resolve %s:%s failed: %s",
|
||||
host, port, resolve_ec.message().c_str());
|
||||
result_body = std::string("DNS resolve failed: ") + resolve_ec.message();
|
||||
goto done;
|
||||
}
|
||||
|
||||
beast::ssl_stream<beast::tcp_stream> stream(ctx.ioc, ctx.ssl_ctx);
|
||||
beast::flat_buffer buffer;
|
||||
|
||||
// SNI hostname
|
||||
if (!SSL_set_tlsext_host_name(stream.native_handle(), host)) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR,
|
||||
"do_post_stream: SNI hostname set failed for %s", host);
|
||||
result_body = "SNI hostname set failed";
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Hostname verification: require server certificate CN/SAN to match
|
||||
// 'host'. This works in conjunction with ssl::verify_peer on the
|
||||
// context — without it MITM with a valid CA-signed cert for a
|
||||
// different hostname would still pass.
|
||||
if (!SSL_set1_host(stream.native_handle(), host)) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR,
|
||||
"do_post_stream: SSL_set1_host failed for %s", host);
|
||||
result_body = "SSL_set1_host failed";
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Connect
|
||||
beast::get_lowest_layer(stream).expires_after(
|
||||
std::chrono::seconds(ctx.connect_timeout));
|
||||
@@ -248,9 +289,16 @@ static int do_post_stream(
|
||||
result_body = parser.get().body();
|
||||
beast::get_lowest_layer(stream).cancel();
|
||||
stream.shutdown(ec);
|
||||
} catch (std::exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR,
|
||||
"do_post_stream: %s", e.what());
|
||||
result_code = -1;
|
||||
result_body = e.what();
|
||||
} catch (...) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR,
|
||||
"do_post_stream: unknown exception (non-std::exception)");
|
||||
result_code = -1;
|
||||
result_body = "unknown exception";
|
||||
}
|
||||
|
||||
done:
|
||||
|
||||
Reference in New Issue
Block a user