🎯 portal-probe: omitir persist_mode MATA al portal — por eso Start no contestaba nunca

Causa raíz de que ScreenCast se quedara colgado en Start, medida en metal (OptiPlex 3060).
No era el EGL por software de QEMU: en metal, con iris por hardware, pasaba exactamente
lo mismo. Esa hipótesis queda falsificada.

Lo que pasa de verdad, con cuatro segfaults reproducibles (uno por intento, todos `at 0`
y todos en el MISMO offset de libc, 0x34f64 = `strlen`):

  1. el cliente no manda `persist_mode` ⇒ el portal asume PERSIST_MODE_NONE
  2. el backend de COSMIC devuelve `restore_data` de todos modos
  3. xdg-desktop-portal 1.18.4 entra por la rama NONE de
     xdp_session_persistence_generate_and_save_restore_token, que hace
     `g_clear_pointer(in_out_restore_token, g_free)` — token = NULL
  4. a la vuelta, el llamador hace `g_variant_new_string(*in_out_restore_token)` SIN
     comprobar nada ⇒ glib llama strlen(NULL) ⇒ SIGSEGV

El portal muere JUSTO ANTES de emitir el Response. Desde el cliente eso se ve como «Start
aceptado y nunca contesta», que es indistinguible de un backend que se cuelga — y nos
mandó a buscar el problema en la GPU durante meses.

Con persist_mode >= 1 el token se genera (uuid) y no hay nulo. Es un fallo de aguas
arriba; esto lo esquiva desde el cliente sin tocar el portal. Por defecto 1 (transitorio),
que es lo que quiere cualquiera que sólo va a capturar una vez.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-06 13:05:34 -04:00
co-authored by Claude Opus 5
parent 8182e459b0
commit a5c959b618
+17 -5
View File
@@ -26,7 +26,7 @@
* el path para recién ahí hacer `AddMatch`, el portal puede haber emitido ya la señal y se pierde * el path para recién ahí hacer `AddMatch`, el portal puede haber emitido ya la señal y se pierde
* para siempre. Es una carrera real, no teórica. * para siempre. Es una carrera real, no teórica.
* *
* Uso: portal-probe screencast [--multiple] [--window] [--wait N] * Uso: portal-probe screencast [--multiple] [--window] [--wait N] [--persist 0|1|2]
* portal-probe filechooser [--save] * portal-probe filechooser [--save]
* portal-probe screenshot [--wait N] * portal-probe screenshot [--wait N]
* portal-probe version (qué interfaces sirve el portal, y en qué versión) * portal-probe version (qué interfaces sirve el portal, y en qué versión)
@@ -246,7 +246,7 @@ static int g_wait_s = 60;
/* ── ScreenCast: el handshake de CUATRO pasos ─────────────────────────────────────────────────── */ /* ── ScreenCast: el handshake de CUATRO pasos ─────────────────────────────────────────────────── */
static int do_screencast(DBusConnection *bus, int multiple, int window) static int do_screencast(DBusConnection *bus, int multiple, int window, int persist)
{ {
const char *IF = "org.freedesktop.portal.ScreenCast"; const char *IF = "org.freedesktop.portal.ScreenCast";
char tok[64], path[512], session[256] = {0}; char tok[64], path[512], session[256] = {0};
@@ -286,6 +286,17 @@ static int do_screencast(DBusConnection *bus, int multiple, int window)
opt_u32(&arr, "types", window ? 2 : 1); opt_u32(&arr, "types", window ? 2 : 1);
opt_bool(&arr, "multiple", multiple ? TRUE : FALSE); opt_bool(&arr, "multiple", multiple ? TRUE : FALSE);
opt_u32(&arr, "cursor_mode", 2); /* 1=oculto 2=embebido 4=metadatos */ opt_u32(&arr, "cursor_mode", 2); /* 1=oculto 2=embebido 4=metadatos */
/* persist_mode NO es un lujo: OMITIRLO CUELGA EL PORTAL. Medido en metal el 2026-08-06.
* Sin esta opción el portal asume PERSIST_MODE_NONE; el backend de COSMIC devuelve
* `restore_data` igual, y xdg-desktop-portal 1.18.4 entra por la rama NONE de
* xdp_session_persistence_generate_and_save_restore_token, que hace
* `g_clear_pointer(in_out_restore_token, g_free)` — deja el token en NULL — y a la vuelta
* el llamador hace `g_variant_new_string(*in_out_restore_token)` SIN comprobarlo.
* glib llama strlen(NULL) y el portal muere con SIGSEGV justo antes de emitir el Response.
* Desde el cliente se ve como «Start aceptado y nunca contesta»: el proceso que debía
* responder ya no existe. Con persist_mode >= 1 el token se genera (uuid) y no hay nulo.
* Es un fallo de aguas arriba; esto lo esquiva sin tocar el portal. */
opt_u32(&arr, "persist_mode", (dbus_uint32_t)persist);
dbus_message_iter_close_container(&it, &arr); dbus_message_iter_close_container(&it, &arr);
if (!call_request(bus, m, "SelectSources")) return 1; if (!call_request(bus, m, "SelectSources")) return 1;
} }
@@ -413,12 +424,13 @@ int main(int argc, char **argv)
setvbuf(stdout, NULL, _IOLBF, 0); setvbuf(stdout, NULL, _IOLBF, 0);
const char *cmd = argc > 1 ? argv[1] : "version"; const char *cmd = argc > 1 ? argv[1] : "version";
int multiple = 0, window = 0, save = 0; int multiple = 0, window = 0, save = 0, persist = 1; /* 0=none 1=transitorio 2=persistente */
for (int i = 2; i < argc; i++) { for (int i = 2; i < argc; i++) {
if (!strcmp(argv[i], "--multiple")) multiple = 1; if (!strcmp(argv[i], "--multiple")) multiple = 1;
else if (!strcmp(argv[i], "--window")) window = 1; else if (!strcmp(argv[i], "--window")) window = 1;
else if (!strcmp(argv[i], "--save")) save = 1; else if (!strcmp(argv[i], "--save")) save = 1;
else if (!strcmp(argv[i], "--wait") && i + 1 < argc) g_wait_s = atoi(argv[++i]); else if (!strcmp(argv[i], "--wait") && i + 1 < argc) g_wait_s = atoi(argv[++i]);
else if (!strcmp(argv[i], "--persist") && i + 1 < argc) persist = atoi(argv[++i]);
} }
DBusError err; dbus_error_init(&err); DBusError err; dbus_error_init(&err);
@@ -472,11 +484,11 @@ int main(int argc, char **argv)
probe_version(bus, "org.freedesktop.portal.Access"); probe_version(bus, "org.freedesktop.portal.Access");
probe_version(bus, "org.freedesktop.portal.RemoteDesktop"); probe_version(bus, "org.freedesktop.portal.RemoteDesktop");
rc = 0; rc = 0;
} else if (!strcmp(cmd, "screencast")) rc = do_screencast(bus, multiple, window); } else if (!strcmp(cmd, "screencast")) rc = do_screencast(bus, multiple, window, persist);
else if (!strcmp(cmd, "filechooser")) rc = do_filechooser(bus, save); else if (!strcmp(cmd, "filechooser")) rc = do_filechooser(bus, save);
else if (!strcmp(cmd, "screenshot")) rc = do_screenshot(bus); else if (!strcmp(cmd, "screenshot")) rc = do_screenshot(bus);
else { else {
fprintf(stderr, "uso: portal-probe {version|screencast|filechooser|screenshot} [--multiple|--window|--save] [--wait N]\n"); fprintf(stderr, "uso: portal-probe {version|screencast|filechooser|screenshot} [--multiple|--window|--save] [--wait N] [--persist 0|1|2]\n");
rc = 2; rc = 2;
} }
printf("\n== portal-probe %s: %s ==\n", cmd, rc == 0 ? "OK" : "FALLÓ"); printf("\n== portal-probe %s: %s ==\n", cmd, rc == 0 ? "OK" : "FALLÓ");