From 11ae69de561c75b65f16c6195c95d7d1acf7c913 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Thu, 2 Jul 2026 11:44:59 +0800 Subject: [PATCH] wpasupplicant: Disable D-Bus auto-activation on all calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every method call and property access to fi.w1.wpa_supplicant1 used flag 0, leaving D-Bus service activation enabled. Any stray request — a startup race before `service wpa_supplicant start` takes effect, or a leaked call while the Ethernet uplink is the chosen path — would make the bus daemon spawn `wpa_supplicant -u` from its .service file, bypassing repeater's own launch of the daemon. Route every interaction through callNoAutoStart/getPropNoAutoStart/ setPropNoAutoStart, which set dbus.FlagNoAutoStart. A leaked call now fails cleanly with ServiceUnknown instead of resurrecting the daemon, leaving repeater as the sole launcher. Co-Authored-By: Claude Opus 4.8 --- internal/wifi/wpasupplicant/backend.go | 4 +- internal/wifi/wpasupplicant/bss.go | 20 ++++---- internal/wifi/wpasupplicant/dbusutil.go | 60 ++++++++++++++++++++++++ internal/wifi/wpasupplicant/interface.go | 24 +++++----- internal/wifi/wpasupplicant/network.go | 2 +- 5 files changed, 85 insertions(+), 25 deletions(-) create mode 100644 internal/wifi/wpasupplicant/dbusutil.go diff --git a/internal/wifi/wpasupplicant/backend.go b/internal/wifi/wpasupplicant/backend.go index 1516330..7a0dec3 100644 --- a/internal/wifi/wpasupplicant/backend.go +++ b/internal/wifi/wpasupplicant/backend.go @@ -54,7 +54,7 @@ func (b *WPABackend) getInterfacePath(interfaceName string) (dbus.ObjectPath, er var interfacePath dbus.ObjectPath // Try to get existing interface - err := b.wpasupplicant.Call(Service+".GetInterface", 0, interfaceName).Store(&interfacePath) + err := callNoAutoStart(b.wpasupplicant, Service+".GetInterface", interfaceName).Store(&interfacePath) if err == nil { return interfacePath, nil } @@ -64,7 +64,7 @@ func (b *WPABackend) getInterfacePath(interfaceName string) (dbus.ObjectPath, er "Ifname": dbus.MakeVariant(interfaceName), } - err = b.wpasupplicant.Call(Service+".CreateInterface", 0, args).Store(&interfacePath) + err = callNoAutoStart(b.wpasupplicant, Service+".CreateInterface", args).Store(&interfacePath) if err != nil { return "", fmt.Errorf("failed to create interface: %v", err) } diff --git a/internal/wifi/wpasupplicant/bss.go b/internal/wifi/wpasupplicant/bss.go index ab1b5b7..e161eea 100644 --- a/internal/wifi/wpasupplicant/bss.go +++ b/internal/wifi/wpasupplicant/bss.go @@ -38,49 +38,49 @@ func (b *BSS) GetProperties() (*BSSProperties, error) { props := &BSSProperties{} // Get SSID - if ssidProp, err := b.obj.GetProperty(BSSInterface + ".SSID"); err == nil { + if ssidProp, err := getPropNoAutoStart(b.obj, BSSInterface+".SSID"); err == nil { if ssid, ok := ssidProp.Value().([]byte); ok { props.SSID = ssid } } // Get BSSID - if bssidProp, err := b.obj.GetProperty(BSSInterface + ".BSSID"); err == nil { + if bssidProp, err := getPropNoAutoStart(b.obj, BSSInterface+".BSSID"); err == nil { if bssid, ok := bssidProp.Value().([]byte); ok { props.BSSID = bssid } } // Get Signal - if signalProp, err := b.obj.GetProperty(BSSInterface + ".Signal"); err == nil { + if signalProp, err := getPropNoAutoStart(b.obj, BSSInterface+".Signal"); err == nil { if signal, ok := signalProp.Value().(int16); ok { props.Signal = signal } } // Get Frequency - if freqProp, err := b.obj.GetProperty(BSSInterface + ".Frequency"); err == nil { + if freqProp, err := getPropNoAutoStart(b.obj, BSSInterface+".Frequency"); err == nil { if freq, ok := freqProp.Value().(uint16); ok { props.Frequency = uint32(freq) } } // Get Privacy - if privacyProp, err := b.obj.GetProperty(BSSInterface + ".Privacy"); err == nil { + if privacyProp, err := getPropNoAutoStart(b.obj, BSSInterface+".Privacy"); err == nil { if privacy, ok := privacyProp.Value().(bool); ok { props.Privacy = privacy } } // Get RSN (WPA2) information - if rsnProp, err := b.obj.GetProperty(BSSInterface + ".RSN"); err == nil { + if rsnProp, err := getPropNoAutoStart(b.obj, BSSInterface+".RSN"); err == nil { if rsn, ok := rsnProp.Value().(map[string]dbus.Variant); ok { props.RSN = rsn } } // Get WPA information - if wpaProp, err := b.obj.GetProperty(BSSInterface + ".WPA"); err == nil { + if wpaProp, err := getPropNoAutoStart(b.obj, BSSInterface+".WPA"); err == nil { if wpa, ok := wpaProp.Value().(map[string]dbus.Variant); ok { props.WPA = wpa } @@ -91,7 +91,7 @@ func (b *BSS) GetProperties() (*BSSProperties, error) { // GetSSIDString returns the SSID as a string func (b *BSS) GetSSIDString() (string, error) { - prop, err := b.obj.GetProperty(BSSInterface + ".SSID") + prop, err := getPropNoAutoStart(b.obj, BSSInterface+".SSID") if err != nil { return "", fmt.Errorf("failed to get SSID property: %v", err) } @@ -106,7 +106,7 @@ func (b *BSS) GetSSIDString() (string, error) { // GetBSSIDString returns the BSSID as a formatted MAC address string func (b *BSS) GetBSSIDString() (string, error) { - prop, err := b.obj.GetProperty(BSSInterface + ".BSSID") + prop, err := getPropNoAutoStart(b.obj, BSSInterface+".BSSID") if err != nil { return "", fmt.Errorf("failed to get BSSID property: %v", err) } @@ -122,7 +122,7 @@ func (b *BSS) GetBSSIDString() (string, error) { // GetSignal returns the signal strength in dBm func (b *BSS) GetSignal() (int16, error) { - prop, err := b.obj.GetProperty(BSSInterface + ".Signal") + prop, err := getPropNoAutoStart(b.obj, BSSInterface+".Signal") if err != nil { return 0, fmt.Errorf("failed to get Signal property: %v", err) } diff --git a/internal/wifi/wpasupplicant/dbusutil.go b/internal/wifi/wpasupplicant/dbusutil.go new file mode 100644 index 0000000..217089f --- /dev/null +++ b/internal/wifi/wpasupplicant/dbusutil.go @@ -0,0 +1,60 @@ +package wpasupplicant + +import ( + "fmt" + "strings" + + "github.com/godbus/dbus/v5" +) + +// D-Bus interactions with the fi.w1.wpa_supplicant1 name must never trigger +// service activation. repeater is the sole launcher of the daemon (via +// `service wpa_supplicant start` in the app layer); if a call reached the bus +// name while the daemon was down — a startup race, or a stray call in +// Ethernet-uplink mode — the bus would auto-activate it from its .service +// file, spawning `wpa_supplicant -u` and bypassing repeater's own launch. +// +// godbus only suppresses activation when FlagNoAutoStart is set on the message +// (msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected)); the default +// flag value of 0 leaves activation enabled. The helpers below set the flag on +// every method call and property access. A leaked call then fails cleanly with +// a ServiceUnknown error instead of resurrecting the daemon. + +// callNoAutoStart performs a method call with D-Bus auto-activation disabled. +func callNoAutoStart(obj dbus.BusObject, method string, args ...interface{}) *dbus.Call { + return obj.Call(method, dbus.FlagNoAutoStart, args...) +} + +// getPropNoAutoStart reads a property (interface.member notation) with +// auto-activation disabled. It mirrors dbus.Object.GetProperty, which +// otherwise issues Properties.Get with the activation-enabling flag 0. +func getPropNoAutoStart(obj dbus.BusObject, p string) (dbus.Variant, error) { + var result dbus.Variant + iface, prop, err := splitProperty(p) + if err != nil { + return result, err + } + err = obj.Call("org.freedesktop.DBus.Properties.Get", dbus.FlagNoAutoStart, iface, prop).Store(&result) + return result, err +} + +// setPropNoAutoStart writes a property with auto-activation disabled. The value +// is passed through as-is, so callers wrap it in a dbus.Variant just as they +// would for dbus.Object.SetProperty. +func setPropNoAutoStart(obj dbus.BusObject, p string, v interface{}) error { + iface, prop, err := splitProperty(p) + if err != nil { + return err + } + return obj.Call("org.freedesktop.DBus.Properties.Set", dbus.FlagNoAutoStart, iface, prop, v).Err +} + +// splitProperty splits an "interface.member" property name into its interface +// and member parts, matching godbus's own parsing. +func splitProperty(p string) (iface, prop string, err error) { + idx := strings.LastIndex(p, ".") + if idx == -1 || idx+1 == len(p) { + return "", "", fmt.Errorf("dbus: invalid property %s", p) + } + return p[:idx], p[idx+1:], nil +} diff --git a/internal/wifi/wpasupplicant/interface.go b/internal/wifi/wpasupplicant/interface.go index e27d1a0..4484605 100644 --- a/internal/wifi/wpasupplicant/interface.go +++ b/internal/wifi/wpasupplicant/interface.go @@ -28,7 +28,7 @@ func (i *WPAInterface) Scan(scanType string) error { "Type": scanType, // "active" or "passive" } - err := i.obj.Call(InterfaceInterface+".Scan", 0, args).Err + err := callNoAutoStart(i.obj, InterfaceInterface+".Scan", args).Err if err != nil { return fmt.Errorf("scan failed: %v", err) } @@ -37,7 +37,7 @@ func (i *WPAInterface) Scan(scanType string) error { // GetBSSs returns a list of BSS (Basic Service Set) object paths func (i *WPAInterface) GetBSSs() ([]dbus.ObjectPath, error) { - prop, err := i.obj.GetProperty(InterfaceInterface + ".BSSs") + prop, err := getPropNoAutoStart(i.obj, InterfaceInterface+".BSSs") if err != nil { return nil, fmt.Errorf("failed to get BSSs property: %v", err) } @@ -52,7 +52,7 @@ func (i *WPAInterface) GetBSSs() ([]dbus.ObjectPath, error) { // GetState returns the current connection state func (i *WPAInterface) GetState() (WPAState, error) { - prop, err := i.obj.GetProperty(InterfaceInterface + ".State") + prop, err := getPropNoAutoStart(i.obj, InterfaceInterface+".State") if err != nil { return "", fmt.Errorf("failed to get State property: %v", err) } @@ -67,7 +67,7 @@ func (i *WPAInterface) GetState() (WPAState, error) { // GetCurrentBSS returns the currently connected BSS object path func (i *WPAInterface) GetCurrentBSS() (dbus.ObjectPath, error) { - prop, err := i.obj.GetProperty(InterfaceInterface + ".CurrentBSS") + prop, err := getPropNoAutoStart(i.obj, InterfaceInterface+".CurrentBSS") if err != nil { return "", fmt.Errorf("failed to get CurrentBSS property: %v", err) } @@ -90,7 +90,7 @@ func (i *WPAInterface) AddNetwork(config map[string]interface{}) (dbus.ObjectPat dbusConfig[key] = dbus.MakeVariant(value) } - err := i.obj.Call(InterfaceInterface+".AddNetwork", 0, dbusConfig).Store(&networkPath) + err := callNoAutoStart(i.obj, InterfaceInterface+".AddNetwork", dbusConfig).Store(&networkPath) if err != nil { return "", fmt.Errorf("failed to add network: %v", err) } @@ -100,7 +100,7 @@ func (i *WPAInterface) AddNetwork(config map[string]interface{}) (dbus.ObjectPat // SelectNetwork connects to a network func (i *WPAInterface) SelectNetwork(networkPath dbus.ObjectPath) error { - err := i.obj.Call(InterfaceInterface+".SelectNetwork", 0, networkPath).Err + err := callNoAutoStart(i.obj, InterfaceInterface+".SelectNetwork", networkPath).Err if err != nil { return fmt.Errorf("failed to select network: %v", err) } @@ -110,7 +110,7 @@ func (i *WPAInterface) SelectNetwork(networkPath dbus.ObjectPath) error { // EnableNetwork marks a network configuration as enabled (eligible for auto-connect) func (i *WPAInterface) EnableNetwork(networkPath dbus.ObjectPath) error { netObj := i.conn.Object(Service, networkPath) - err := netObj.SetProperty(NetworkInterface+".Enabled", dbus.MakeVariant(true)) + err := setPropNoAutoStart(netObj, NetworkInterface+".Enabled", dbus.MakeVariant(true)) if err != nil { return fmt.Errorf("failed to enable network: %v", err) } @@ -119,7 +119,7 @@ func (i *WPAInterface) EnableNetwork(networkPath dbus.ObjectPath) error { // RemoveNetwork removes a network configuration func (i *WPAInterface) RemoveNetwork(networkPath dbus.ObjectPath) error { - err := i.obj.Call(InterfaceInterface+".RemoveNetwork", 0, networkPath).Err + err := callNoAutoStart(i.obj, InterfaceInterface+".RemoveNetwork", networkPath).Err if err != nil { return fmt.Errorf("failed to remove network: %v", err) } @@ -128,7 +128,7 @@ func (i *WPAInterface) RemoveNetwork(networkPath dbus.ObjectPath) error { // GetNetworks returns the object paths of all configured networks func (i *WPAInterface) GetNetworks() ([]dbus.ObjectPath, error) { - prop, err := i.obj.GetProperty(InterfaceInterface + ".Networks") + prop, err := getPropNoAutoStart(i.obj, InterfaceInterface+".Networks") if err != nil { return nil, fmt.Errorf("failed to get Networks property: %v", err) } @@ -143,7 +143,7 @@ func (i *WPAInterface) GetNetworks() ([]dbus.ObjectPath, error) { // Disconnect disconnects from the current network func (i *WPAInterface) Disconnect() error { - err := i.obj.Call(InterfaceInterface+".Disconnect", 0).Err + err := callNoAutoStart(i.obj, InterfaceInterface+".Disconnect").Err if err != nil { return fmt.Errorf("disconnect failed: %v", err) } @@ -152,7 +152,7 @@ func (i *WPAInterface) Disconnect() error { // SaveConfig saves the current configuration to the wpa_supplicant config file func (i *WPAInterface) SaveConfig() error { - err := i.obj.Call(InterfaceInterface+".SaveConfig", 0).Err + err := callNoAutoStart(i.obj, InterfaceInterface+".SaveConfig").Err if err != nil { return fmt.Errorf("save config failed: %v", err) } @@ -166,7 +166,7 @@ func (i *WPAInterface) GetPath() dbus.ObjectPath { // GetScanning returns whether a scan is currently in progress func (i *WPAInterface) GetScanning() (bool, error) { - prop, err := i.obj.GetProperty(InterfaceInterface + ".Scanning") + prop, err := getPropNoAutoStart(i.obj, InterfaceInterface+".Scanning") if err != nil { return false, fmt.Errorf("failed to get Scanning property: %v", err) } diff --git a/internal/wifi/wpasupplicant/network.go b/internal/wifi/wpasupplicant/network.go index c018e3f..39ce080 100644 --- a/internal/wifi/wpasupplicant/network.go +++ b/internal/wifi/wpasupplicant/network.go @@ -29,7 +29,7 @@ func (n *Network) GetPath() dbus.ObjectPath { // GetProperties returns properties of the network configuration func (n *Network) GetProperties() (map[string]dbus.Variant, error) { - prop, err := n.obj.GetProperty(NetworkInterface + ".Properties") + prop, err := getPropNoAutoStart(n.obj, NetworkInterface+".Properties") if err != nil { return nil, err }