v0.0.59 - Fix MCP segfault: drop redundant g_main_context_acquire/release in nested JS-eval/snapshot loops (was corrupting owner_count); allow ws:// WebSocket from https:// pages by registering ws as a secure scheme
This commit is contained in:
+32
-12
@@ -12,10 +12,31 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
/* ── Synchronous JS evaluation ─────────────────────────────────────── *
|
||||
* WebKitGTK's evaluate_javascript is async. We use a nested GMainLoop
|
||||
* on the default context to wait for the result. The key is to acquire
|
||||
* the context before creating the loop so that the WebKit IPC sources
|
||||
* are properly dispatched.
|
||||
* WebKitGTK's evaluate_javascript is async. We run a nested GMainLoop
|
||||
* on the default main context until the JS callback (or a timeout)
|
||||
* quits it.
|
||||
*
|
||||
* This function is routinely called from within a Soup server source
|
||||
* callback (the MCP HTTP handler) that is already dispatching on the
|
||||
* default main context. Running a nested GMainLoop on the same context
|
||||
* is the standard GTK modal pattern (gtk_dialog_run does the same) and
|
||||
* is safe: g_main_loop_run() manages context ownership internally for
|
||||
* the nested run, and the outer loop's dispatch is suspended until the
|
||||
* inner loop quits.
|
||||
*
|
||||
* The PREVIOUS code additionally called g_main_context_acquire()/
|
||||
* g_main_context_release() around the loop. That was the bug: the
|
||||
* default context is already owned by the outer dispatch, so acquire()
|
||||
* fails silently (owner_count is NOT incremented), yet release() then
|
||||
* decrements it unconditionally — producing
|
||||
* "g_main_context_release_unlocked: assertion 'context->owner_count > 0'"
|
||||
* and, combined with the recursive check, a segfault. The fix is simply
|
||||
* to drop the explicit acquire/release and let g_main_loop_run() handle
|
||||
* ownership. We must NOT use a g_main_context_iteration() pump instead,
|
||||
* because that would re-dispatch unrelated ready sources (other Soup
|
||||
* requests, WebKit load events, GTK UI events) re-entrantly while a
|
||||
* tool call is in progress, which can deadlock when an MCP tool runs
|
||||
* during page loading.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
@@ -48,6 +69,7 @@ static gboolean on_js_timeout(gpointer user_data) {
|
||||
js_eval_ctx_t *ctx = (js_eval_ctx_t *)user_data;
|
||||
if (!ctx->done) {
|
||||
g_printerr("[agent] JS evaluation timed out\n");
|
||||
ctx->done = TRUE;
|
||||
if (ctx->loop && g_main_loop_is_running(ctx->loop)) {
|
||||
g_main_loop_quit(ctx->loop);
|
||||
}
|
||||
@@ -61,26 +83,24 @@ char *agent_js_eval_sync(WebKitWebView *webview, const char *script,
|
||||
|
||||
js_eval_ctx_t ctx = {0};
|
||||
|
||||
/* Use the default main context for the nested loop. */
|
||||
GMainContext *main_ctx = g_main_context_default();
|
||||
g_main_context_acquire(main_ctx);
|
||||
|
||||
ctx.loop = g_main_loop_new(main_ctx, FALSE);
|
||||
/* Nested loop on the default context. Do NOT acquire/release the
|
||||
* context here — see the block comment above. */
|
||||
ctx.loop = g_main_loop_new(g_main_context_default(), FALSE);
|
||||
|
||||
/* Add a timeout source to the same context. */
|
||||
guint timeout_id = g_timeout_add(timeout_ms, on_js_timeout, &ctx);
|
||||
|
||||
/* Start async evaluation. */
|
||||
/* Start async evaluation. The completion callback fires on the
|
||||
* default main context (where WebKit schedules its async work). */
|
||||
webkit_web_view_evaluate_javascript(webview, script, -1, NULL, NULL, NULL,
|
||||
on_js_finished, &ctx);
|
||||
|
||||
/* Run the nested loop until JS completes or timeout. */
|
||||
/* Run the nested loop until JS completes or the timeout quits it. */
|
||||
g_main_loop_run(ctx.loop);
|
||||
|
||||
/* Cleanup. */
|
||||
g_source_remove(timeout_id);
|
||||
g_main_loop_unref(ctx.loop);
|
||||
g_main_context_release(main_ctx);
|
||||
|
||||
return ctx.result;
|
||||
}
|
||||
|
||||
+16
-12
@@ -1005,8 +1005,16 @@ static cJSON *tool_eval(cJSON *params) {
|
||||
/* ── Screenshot tool ──────────────────────────────────────────────── */
|
||||
|
||||
/* Context for the async webkit_web_view_snapshot() call. We use a
|
||||
* nested GMainLoop to turn the async API into a synchronous one,
|
||||
* matching the pattern in agent_js_eval_sync(). */
|
||||
* async API into a synchronous one.
|
||||
*
|
||||
* We run a nested GMainLoop on the default main context (the standard
|
||||
* GTK modal pattern) but do NOT call g_main_context_acquire()/
|
||||
* g_main_context_release() around it — the default context is already
|
||||
* owned by the outer dispatch (these tools are invoked from the MCP
|
||||
* HTTP handler), so an explicit acquire fails silently and a matching
|
||||
* release corrupts the owner_count, causing a segfault. The nested
|
||||
* g_main_loop_run() handles ownership internally. See the block comment
|
||||
* in agent_js_eval_sync() for the full rationale. */
|
||||
typedef struct {
|
||||
cairo_surface_t *surface;
|
||||
gboolean done;
|
||||
@@ -1034,6 +1042,7 @@ static gboolean snapshot_timeout_cb(gpointer user_data) {
|
||||
snapshot_ctx_t *ctx = (snapshot_ctx_t *)user_data;
|
||||
if (!ctx->done) {
|
||||
g_printerr("[agent] screenshot timed out\n");
|
||||
ctx->done = TRUE;
|
||||
if (ctx->loop && g_main_loop_is_running(ctx->loop)) {
|
||||
g_main_loop_quit(ctx->loop);
|
||||
}
|
||||
@@ -1054,11 +1063,10 @@ static cJSON *tool_screenshot(cJSON *params) {
|
||||
WebKitWebView *wv = get_active_webview();
|
||||
if (!wv) return make_error("NO_TAB", "No active tab");
|
||||
|
||||
/* Set up the nested GMainLoop to wait for the async snapshot. */
|
||||
/* Nested GMainLoop on the default context. No acquire/release —
|
||||
* see the note on snapshot_ctx_t above. */
|
||||
snapshot_ctx_t ctx = {0};
|
||||
GMainContext *main_ctx = g_main_context_default();
|
||||
g_main_context_acquire(main_ctx);
|
||||
ctx.loop = g_main_loop_new(main_ctx, FALSE);
|
||||
ctx.loop = g_main_loop_new(g_main_context_default(), FALSE);
|
||||
|
||||
guint timeout_id = g_timeout_add(10000, snapshot_timeout_cb, &ctx);
|
||||
|
||||
@@ -1074,7 +1082,6 @@ static cJSON *tool_screenshot(cJSON *params) {
|
||||
|
||||
g_source_remove(timeout_id);
|
||||
g_main_loop_unref(ctx.loop);
|
||||
g_main_context_release(main_ctx);
|
||||
|
||||
if (!ctx.done || ctx.surface == NULL) {
|
||||
if (ctx.surface) cairo_surface_destroy(ctx.surface);
|
||||
@@ -4036,11 +4043,9 @@ static cJSON *tool_screenshot_annotated(cJSON *params) {
|
||||
/* We don't strictly need the result, but wait for it to complete. */
|
||||
g_free(ann_result);
|
||||
|
||||
/* Step 3: Take the WebKit snapshot (same pattern as tool_screenshot). */
|
||||
/* Step 3: Take the WebKit snapshot (same nested-loop pattern as tool_screenshot). */
|
||||
snapshot_ctx_t ctx = {0};
|
||||
GMainContext *main_ctx = g_main_context_default();
|
||||
g_main_context_acquire(main_ctx);
|
||||
ctx.loop = g_main_loop_new(main_ctx, FALSE);
|
||||
ctx.loop = g_main_loop_new(g_main_context_default(), FALSE);
|
||||
|
||||
guint timeout_id = g_timeout_add(10000, snapshot_timeout_cb, &ctx);
|
||||
|
||||
@@ -4053,7 +4058,6 @@ static cJSON *tool_screenshot_annotated(cJSON *params) {
|
||||
|
||||
g_source_remove(timeout_id);
|
||||
g_main_loop_unref(ctx.loop);
|
||||
g_main_context_release(main_ctx);
|
||||
|
||||
if (!ctx.done || ctx.surface == NULL) {
|
||||
if (ctx.surface) cairo_surface_destroy(ctx.surface);
|
||||
|
||||
+2
-2
@@ -11,9 +11,9 @@
|
||||
#ifndef SOVEREIGN_BROWSER_VERSION_H
|
||||
#define SOVEREIGN_BROWSER_VERSION_H
|
||||
|
||||
#define SB_VERSION "v0.0.58"
|
||||
#define SB_VERSION "v0.0.59"
|
||||
#define SB_VERSION_MAJOR 0
|
||||
#define SB_VERSION_MINOR 0
|
||||
#define SB_VERSION_PATCH 58
|
||||
#define SB_VERSION_PATCH 59
|
||||
|
||||
#endif /* SOVEREIGN_BROWSER_VERSION_H */
|
||||
|
||||
+10
-1
@@ -27,12 +27,21 @@ static void configure_context(WebKitWebContext *ctx) {
|
||||
|
||||
/* Security strip: register sovereign://, tor://, file:// as secure
|
||||
* (no CORS enforcement), and file:// as local. Same as the original
|
||||
* main.c configuration. */
|
||||
* main.c configuration.
|
||||
*
|
||||
* Also register ws (insecure WebSocket) as secure. Without this,
|
||||
* WebKitGTK blocks ws:// connections opened from https:// pages as
|
||||
* mixed active content (a secure page may not connect to an
|
||||
* insecure origin). This browser is intentionally open and should
|
||||
* be able to connect to anywhere, including local plaintext
|
||||
* WebSocket services, so we treat ws:// as a secure scheme — the
|
||||
* same status wss:// already has by default. */
|
||||
WebKitSecurityManager *sec_mgr =
|
||||
webkit_web_context_get_security_manager(ctx);
|
||||
webkit_security_manager_register_uri_scheme_as_secure(sec_mgr, "sovereign");
|
||||
webkit_security_manager_register_uri_scheme_as_secure(sec_mgr, "tor");
|
||||
webkit_security_manager_register_uri_scheme_as_secure(sec_mgr, "file");
|
||||
webkit_security_manager_register_uri_scheme_as_secure(sec_mgr, "ws");
|
||||
webkit_security_manager_register_uri_scheme_as_local(sec_mgr, "file");
|
||||
|
||||
/* Accept any TLS certificate (FIPS uses Noise IK, not TLS CAs). */
|
||||
|
||||
Reference in New Issue
Block a user