security: wipe passphrases, bind hidden stash to SSID, re-register agent
Wipe passphrase memory in the auth and hidden-network dialogs (explicit_bzero on owned copies plus overwriting the elm_entry buffer before destruction) so secrets don't linger on the heap. Bind the hidden-network passphrase stash to its SSID with a 30s timeout, so a typo'd or out-of-range hidden connect can't leak its passphrase to an unrelated network whose RequestPassphrase happens to land first. Re-RegisterAgent on iwd NameOwnerChanged so PSK connects survive systemctl restart iwd instead of silently hanging. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5844e2265e
commit
0ab9561d2b
8 changed files with 161 additions and 51 deletions
|
|
@ -42,6 +42,7 @@ e_modapi_shutdown(E_Module *m EINA_UNUSED)
|
|||
if (!e_iwd) return 1;
|
||||
|
||||
e_iwd_gadget_shutdown();
|
||||
e_iwd_popup_shutdown();
|
||||
if (e_iwd->manager) iwd_manager_free(e_iwd->manager);
|
||||
e_iwd_config_save();
|
||||
if (e_iwd->conn) eldbus_connection_unref(e_iwd->conn);
|
||||
|
|
|
|||
|
|
@ -30,8 +30,39 @@ static Popup *_popup = NULL;
|
|||
static Iwd_Agent_Request *_pending_req = NULL;
|
||||
/* Tracked so iwd's Cancel(reason) can tear down the dialog. */
|
||||
static Evas_Object *_pending_dialog = NULL;
|
||||
/* One-shot passphrase pre-armed by the hidden-network dialog. */
|
||||
static char *_hidden_pending_pass = NULL;
|
||||
/* One-shot passphrase pre-armed by the hidden-network dialog. Bound to the
|
||||
* SSID it was entered for, with a timeout that wipes it if iwd never comes
|
||||
* asking — so a stashed passphrase can never leak to an unrelated network. */
|
||||
static char *_hidden_pending_pass = NULL;
|
||||
static char *_hidden_pending_ssid = NULL;
|
||||
static Ecore_Timer *_hidden_pending_timer = NULL;
|
||||
#define HIDDEN_PASS_TIMEOUT 30.0 /* seconds */
|
||||
|
||||
static void
|
||||
_hidden_pending_clear(void)
|
||||
{
|
||||
if (_hidden_pending_pass)
|
||||
{
|
||||
explicit_bzero(_hidden_pending_pass, strlen(_hidden_pending_pass));
|
||||
free(_hidden_pending_pass);
|
||||
_hidden_pending_pass = NULL;
|
||||
}
|
||||
free(_hidden_pending_ssid);
|
||||
_hidden_pending_ssid = NULL;
|
||||
if (_hidden_pending_timer)
|
||||
{
|
||||
ecore_timer_del(_hidden_pending_timer);
|
||||
_hidden_pending_timer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_hidden_pending_timeout(void *data EINA_UNUSED)
|
||||
{
|
||||
_hidden_pending_timer = NULL;
|
||||
_hidden_pending_clear();
|
||||
return ECORE_CALLBACK_CANCEL;
|
||||
}
|
||||
|
||||
/* ----- helpers --------------------------------------------------------- */
|
||||
|
||||
|
|
@ -249,10 +280,19 @@ _on_hidden_done(void *data EINA_UNUSED, const char *ssid, const char *pass, Eina
|
|||
if (!ok || !ssid || !*ssid) return;
|
||||
Iwd_Device *dev = _active_device();
|
||||
if (!dev) return;
|
||||
/* Pre-arm the agent reply so the next RequestPassphrase from iwd is
|
||||
* answered automatically. If the network turns out to be open, the
|
||||
* stashed passphrase is simply never consumed. */
|
||||
if (pass && *pass) _hidden_pending_pass = strdup(pass);
|
||||
/* Pre-arm the agent reply so the next RequestPassphrase from iwd for
|
||||
* *this SSID* is answered automatically. The stash is bound to the SSID
|
||||
* and self-clears after HIDDEN_PASS_TIMEOUT seconds — so a typo'd or
|
||||
* out-of-range SSID cannot cause the passphrase to leak to a later,
|
||||
* unrelated network whose RequestPassphrase happens to land first. */
|
||||
_hidden_pending_clear();
|
||||
if (pass && *pass)
|
||||
{
|
||||
_hidden_pending_pass = strdup(pass);
|
||||
_hidden_pending_ssid = strdup(ssid);
|
||||
_hidden_pending_timer = ecore_timer_add(HIDDEN_PASS_TIMEOUT,
|
||||
_hidden_pending_timeout, NULL);
|
||||
}
|
||||
iwd_device_connect_hidden(dev, ssid);
|
||||
}
|
||||
|
||||
|
|
@ -267,7 +307,12 @@ static void
|
|||
_on_auth_done(void *data EINA_UNUSED, const char *pass, Eina_Bool ok)
|
||||
{
|
||||
_pending_dialog = NULL;
|
||||
if (!_pending_req) return;
|
||||
if (!_pending_req)
|
||||
{
|
||||
/* Request was already canceled by iwd; nothing to do. The caller
|
||||
* (wifi_auth) wipes its own copy of `pass` after we return. */
|
||||
return;
|
||||
}
|
||||
if (ok) iwd_agent_reply (_pending_req, pass ? pass : "");
|
||||
else iwd_agent_cancel(_pending_req);
|
||||
_pending_req = NULL;
|
||||
|
|
@ -303,15 +348,27 @@ e_iwd_popup_install_passphrase_handler(void)
|
|||
static void
|
||||
_on_passphrase_request(void *data EINA_UNUSED, Iwd_Agent_Request *req, const char *netpath)
|
||||
{
|
||||
/* If the user just kicked off a hidden-network connect with a passphrase,
|
||||
* answer this request automatically without prompting. */
|
||||
if (_hidden_pending_pass)
|
||||
/* Resolve netpath -> network so we can both match the hidden stash
|
||||
* against the requested SSID *and* show a friendly label in the dialog. */
|
||||
Iwd_Network *n = NULL;
|
||||
if (e_iwd && e_iwd->manager)
|
||||
{
|
||||
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
|
||||
if (h) n = eina_hash_find(h, netpath);
|
||||
}
|
||||
const char *req_ssid = (n && n->ssid) ? n->ssid : NULL;
|
||||
|
||||
/* Use the hidden-network stash *only* if iwd is asking for the same
|
||||
* SSID we entered it for. Anything else: drop the stash on the floor
|
||||
* and prompt normally. */
|
||||
if (_hidden_pending_pass && _hidden_pending_ssid &&
|
||||
req_ssid && !strcmp(req_ssid, _hidden_pending_ssid))
|
||||
{
|
||||
iwd_agent_reply(req, _hidden_pending_pass);
|
||||
free(_hidden_pending_pass);
|
||||
_hidden_pending_pass = NULL;
|
||||
_hidden_pending_clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pending_req)
|
||||
{
|
||||
iwd_agent_cancel(req);
|
||||
|
|
@ -319,27 +376,8 @@ _on_passphrase_request(void *data EINA_UNUSED, Iwd_Agent_Request *req, const cha
|
|||
}
|
||||
_pending_req = req;
|
||||
|
||||
/* Look up the network for a friendly SSID, if we have it. */
|
||||
const char *ssid = "network";
|
||||
if (e_iwd && e_iwd->manager)
|
||||
{
|
||||
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
|
||||
if (h)
|
||||
{
|
||||
Iwd_Network *n = eina_hash_find(h, netpath);
|
||||
if (n && n->ssid) ssid = n->ssid;
|
||||
}
|
||||
}
|
||||
const char *sec = NULL;
|
||||
if (e_iwd && e_iwd->manager)
|
||||
{
|
||||
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
|
||||
if (h)
|
||||
{
|
||||
Iwd_Network *n = eina_hash_find(h, netpath);
|
||||
if (n) sec = _sec_label(n->security);
|
||||
}
|
||||
}
|
||||
const char *ssid = req_ssid ? req_ssid : "network";
|
||||
const char *sec = n ? _sec_label(n->security) : NULL;
|
||||
_pending_dialog = wifi_auth_prompt(_popup ? _popup->box : e_comp->elm,
|
||||
ssid, sec, _on_auth_done, NULL);
|
||||
}
|
||||
|
|
@ -357,6 +395,13 @@ _destroy(void)
|
|||
_popup = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
e_iwd_popup_shutdown(void)
|
||||
{
|
||||
_destroy();
|
||||
_hidden_pending_clear();
|
||||
}
|
||||
|
||||
void
|
||||
e_iwd_popup_close(void) { _destroy(); }
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@
|
|||
#include <e_gadcon.h>
|
||||
|
||||
void e_iwd_popup_install_passphrase_handler(void);
|
||||
void e_iwd_popup_toggle (E_Gadcon_Client *gcc);
|
||||
void e_iwd_popup_close (void);
|
||||
void e_iwd_popup_refresh(void);
|
||||
void e_iwd_popup_toggle (E_Gadcon_Client *gcc);
|
||||
void e_iwd_popup_close (void);
|
||||
void e_iwd_popup_refresh (void);
|
||||
void e_iwd_popup_shutdown(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -143,6 +143,14 @@ _on_register(void *data EINA_UNUSED, const Eldbus_Message *msg,
|
|||
fprintf(stderr, "e_iwd: agent register failed: %s: %s\n", en, em);
|
||||
}
|
||||
|
||||
void
|
||||
iwd_agent_register(Iwd_Agent *a)
|
||||
{
|
||||
if (!a || !a->am_proxy) return;
|
||||
eldbus_proxy_call(a->am_proxy, "RegisterAgent", _on_register, NULL, -1,
|
||||
"o", IWD_AGENT_PATH);
|
||||
}
|
||||
|
||||
Iwd_Agent *
|
||||
iwd_agent_new(Eldbus_Connection *conn, Iwd_Agent_Passphrase_Cb cb, void *data)
|
||||
{
|
||||
|
|
@ -158,12 +166,8 @@ iwd_agent_new(Eldbus_Connection *conn, Iwd_Agent_Passphrase_Cb cb, void *data)
|
|||
|
||||
a->am_obj = eldbus_object_get(conn, IWD_BUS_NAME, "/net/connman/iwd");
|
||||
if (a->am_obj)
|
||||
{
|
||||
a->am_proxy = eldbus_proxy_get(a->am_obj, IWD_IFACE_AGENT_MANAGER);
|
||||
if (a->am_proxy)
|
||||
eldbus_proxy_call(a->am_proxy, "RegisterAgent", _on_register, NULL, -1,
|
||||
"o", IWD_AGENT_PATH);
|
||||
}
|
||||
a->am_proxy = eldbus_proxy_get(a->am_obj, IWD_IFACE_AGENT_MANAGER);
|
||||
iwd_agent_register(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ Iwd_Agent *iwd_agent_new (Eldbus_Connection *conn,
|
|||
Iwd_Agent_Passphrase_Cb cb, void *data);
|
||||
|
||||
void iwd_agent_set_cancel_cb(Iwd_Agent *a, Iwd_Agent_Cancel_Cb cb, void *data);
|
||||
/* Re-issue RegisterAgent. Call after iwd reappears on the bus
|
||||
* (NameOwnerChanged) — without this, every PSK connect silently hangs
|
||||
* because no agent is registered against the new iwd instance. */
|
||||
void iwd_agent_register(Iwd_Agent *a);
|
||||
void iwd_agent_free(Iwd_Agent *a);
|
||||
|
||||
void iwd_agent_reply (Iwd_Agent_Request *req, const char *passphrase);
|
||||
|
|
|
|||
|
|
@ -222,7 +222,14 @@ _on_iface_removed(void *data, const char *path, const char *iface)
|
|||
}
|
||||
|
||||
static void
|
||||
_on_name_appeared(void *data EINA_UNUSED) { /* GetManagedObjects will populate */ }
|
||||
_on_name_appeared(void *data)
|
||||
{
|
||||
/* GetManagedObjects will repopulate adapters/devices/networks; we just
|
||||
* need to re-register our agent against the new iwd instance. Without
|
||||
* this, PSK connects silently hang after `systemctl restart iwd`. */
|
||||
Iwd_Manager *m = data;
|
||||
if (m && m->agent) iwd_agent_register(m->agent);
|
||||
}
|
||||
|
||||
static void
|
||||
_on_name_vanished(void *data)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "wifi_auth.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct _Auth_Ctx
|
||||
{
|
||||
|
|
@ -12,11 +13,27 @@ typedef struct _Auth_Ctx
|
|||
} Auth_Ctx;
|
||||
|
||||
static void
|
||||
_finish(Auth_Ctx *c, Eina_Bool ok, const char *pass)
|
||||
_finish(Auth_Ctx *c, Eina_Bool ok)
|
||||
{
|
||||
if (c->fired) return;
|
||||
c->fired = EINA_TRUE;
|
||||
|
||||
/* Copy the passphrase into a buffer we own so we can wipe it
|
||||
* after the callback returns. The elm_entry's internal buffer
|
||||
* is then overwritten before the window (and entry) are destroyed. */
|
||||
char *pass = NULL;
|
||||
if (ok && c->entry)
|
||||
{
|
||||
const char *raw = elm_entry_entry_get(c->entry);
|
||||
if (raw) pass = strdup(raw);
|
||||
}
|
||||
if (c->cb) c->cb(c->data, pass, ok);
|
||||
if (pass)
|
||||
{
|
||||
explicit_bzero(pass, strlen(pass));
|
||||
free(pass);
|
||||
}
|
||||
if (c->entry) elm_entry_entry_set(c->entry, "");
|
||||
if (c->win) evas_object_del(c->win);
|
||||
free(c);
|
||||
}
|
||||
|
|
@ -24,21 +41,25 @@ _finish(Auth_Ctx *c, Eina_Bool ok, const char *pass)
|
|||
static void
|
||||
_on_ok(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
||||
{
|
||||
Auth_Ctx *c = data;
|
||||
_finish(c, EINA_TRUE, elm_entry_entry_get(c->entry));
|
||||
_finish(data, EINA_TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
_on_cancel(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
||||
{
|
||||
_finish(data, EINA_FALSE, NULL);
|
||||
_finish(data, EINA_FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
_on_del(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
||||
{
|
||||
/* Window closed without cancel/ok — treat as cancel. */
|
||||
_finish(data, EINA_FALSE, NULL);
|
||||
Auth_Ctx *c = data;
|
||||
/* The window (and entry) are being destroyed; null entry to skip the
|
||||
* post-cb entry_set in _finish. */
|
||||
c->win = NULL;
|
||||
c->entry = NULL;
|
||||
_finish(c, EINA_FALSE);
|
||||
}
|
||||
|
||||
Evas_Object *
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "wifi_hidden.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct _Hidden_Ctx
|
||||
{
|
||||
|
|
@ -17,9 +18,31 @@ _finish(Hidden_Ctx *c, Eina_Bool ok)
|
|||
{
|
||||
if (c->fired) return;
|
||||
c->fired = EINA_TRUE;
|
||||
const char *ssid = ok ? elm_entry_entry_get(c->e_ssid) : NULL;
|
||||
const char *pass = ok ? elm_entry_entry_get(c->e_pass) : NULL;
|
||||
|
||||
/* Copy SSID + passphrase into buffers we own; wipe the passphrase
|
||||
* (and overwrite the entry) before the window is destroyed. */
|
||||
char *ssid = NULL, *pass = NULL;
|
||||
if (ok)
|
||||
{
|
||||
if (c->e_ssid)
|
||||
{
|
||||
const char *r = elm_entry_entry_get(c->e_ssid);
|
||||
if (r) ssid = strdup(r);
|
||||
}
|
||||
if (c->e_pass)
|
||||
{
|
||||
const char *r = elm_entry_entry_get(c->e_pass);
|
||||
if (r) pass = strdup(r);
|
||||
}
|
||||
}
|
||||
if (c->cb) c->cb(c->data, ssid, pass, ok);
|
||||
if (pass)
|
||||
{
|
||||
explicit_bzero(pass, strlen(pass));
|
||||
free(pass);
|
||||
}
|
||||
free(ssid);
|
||||
if (c->e_pass) elm_entry_entry_set(c->e_pass, "");
|
||||
if (c->win) evas_object_del(c->win);
|
||||
free(c);
|
||||
}
|
||||
|
|
@ -42,7 +65,11 @@ _on_cancel(void *data, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
|||
static void
|
||||
_on_del(void *data, Evas *e EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
||||
{
|
||||
_finish(data, EINA_FALSE);
|
||||
Hidden_Ctx *c = data;
|
||||
c->win = NULL;
|
||||
c->e_ssid = NULL;
|
||||
c->e_pass = NULL;
|
||||
_finish(c, EINA_FALSE);
|
||||
}
|
||||
|
||||
static Evas_Object *
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue