impl-core: gate portal clients on stolen-fd reconnect

When the XDG camera portal steals a PW fd and hands it to an
app, the app can connect before the session manager has set up
permissions, seeing no camera nodes.

Gate portal clients in core_hello by removing PW_PERM_R from
PW_ID_CORE, triggering the busy state so daemon stops reading from the
client socket until the session manager restores permissions.

A marker property (pipewire.access.portal.gated) notifies the
session manager to ungate after attaching the PermissionManager.
The property is cleared before each gate cycle to handle repeated
fd steals on the same client.

Only gate when the session manager has set the capability property
pipewire.access.portal.gate-supported on the client, so older session
managers that cannot ungate are unaffected. Check ALL clients in the
context rather than this client specifically because the portal may
create a brand new client for each camera session and the session
manager won't have processed it yet.

Fixes: https://gitlab.freedesktop.org/pipewire/wireplumber/-/work_items/941
This commit is contained in:
Charles 2026-04-29 09:44:15 +01:00
parent 593132e434
commit c5083e7f32

View file

@ -171,7 +171,11 @@ static int core_hello(void *object, uint32_t version)
struct pw_impl_core *this = client->core; struct pw_impl_core *this = client->core;
int res; int res;
pw_log_debug("%p: hello %d from resource %p", context, version, resource); const char *access = spa_dict_lookup(client->info.props, PW_KEY_ACCESS);
pw_log_debug("%p: hello %d from resource %p access=%s",
context, version, resource, access ? access : "(null)");
pw_map_for_each(&client->objects, destroy_resource, client); pw_map_for_each(&client->objects, destroy_resource, client);
resource->version = version; resource->version = version;
@ -186,6 +190,40 @@ static int core_hello(void *object, uint32_t version)
PW_PERM_ALL, PW_VERSION_CLIENT, PW_ID_CLIENT)) < 0) PW_PERM_ALL, PW_VERSION_CLIENT, PW_ID_CLIENT)) < 0)
return res; return res;
} }
/* The portal has stolen the fd and handed it to app. Gate the
* client until the session manager sets up permissions
* by removing PW_PERM_R from PW_ID_CORE which triggers
* the busy state. */
if (access && spa_streq(access, "portal")) {
bool gate_supported = false;
struct pw_impl_client *c;
spa_list_for_each(c, &context->client_list, link) {
const char *gs = spa_dict_lookup(&c->properties->dict,
"pipewire.access.portal.gate-supported");
if (gs && spa_streq(gs, "true")) {
gate_supported = true;
break;
}
}
if (gate_supported) {
struct pw_permission perms[1];
perms[0] = PW_PERMISSION_INIT(PW_ID_CORE, PW_PERM_W|PW_PERM_X);
pw_impl_client_update_permissions(client, 1, perms);
struct spa_dict_item items[2];
items[0] = SPA_DICT_ITEM_INIT(
"pipewire.access.portal.gated", NULL);
items[1] = SPA_DICT_ITEM_INIT(
"pipewire.access.portal.gated", "true");
pw_impl_client_update_properties(client,
&SPA_DICT_INIT(items, 2));
pw_log_info("%p: portal client %p gated until session "
"manager restores PW_ID_CORE permissions",
context, client);
}
}
return 0; return 0;
} }