mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-01 19:18:28 -04:00
d96559f7df
* core: fix security and concurrency issues found in backend audit
Security:
- privesc: pipe the sudo password via stdin (sudo -S) instead of
embedding it in the command string, so it no longer appears in argv
(readable by any local user via /proc/<pid>/cmdline or ps)
- greeter: tokenize a session .desktop Exec= line into argv and execve
directly instead of running it through /bin/sh -c, closing a command-
injection path via user-writable ~/.local/share/wayland-sessions
- plugins: reject path-separator/.. in plugin id/name before joining
into a filesystem path, closing an arbitrary-directory-delete in the
uninstall/update fallback
- keybinds/hyprland: always quote unrecognized bind actions/keys when
writing generated Lua; only re-emit genuine round-tripped custom Lua
verbatim (tracked via an explicit flag), closing a Lua-injection path
- desktop/mimeapps: reject newline/bracket in mime/desktop-id fields so
they can't inject fake sections into the shared mimeapps.list
Robustness / concurrency:
- server: recover panics in the request-dispatch path so one bad
handler can't crash the daemon and drop every client
- go-wayland: recover panics in the shared dispatch choke point so a
malformed compositor event can't crash CLI tools / the daemon
- server: per-connection D-Bus client ID instead of a shared constant,
fixing cross-client signal delivery and subscription teardown
- network: guard the NetworkManager device maps with a mutex (a
concurrent map read/write here is an unrecoverable fatal error)
- cups: close the event channel on Stop() so Unsubscribe() of the last
subscriber no longer deadlocks; allocate the fresh channel in Start()
- freedesktop: reuse the shared session conn for the settings watcher
and tear it down in Close(), fixing a per-Manager conn+goroutine leak
- clipboard: mutex-guard lazy dbusConn creation
- geolocation: use WithMatchMember for the GeoClue2 LocationUpdated
signal (was WithMatchSender with an interface.member string, so the
match never fired and live location updates never arrived)
- screenshot: set failed=true on buffer/pool creation errors so the
dispatch loop doesn't wait forever for a ready/failed that never comes
* apply code review comments
---------
Co-authored-by: bbedward <bbedward@gmail.com>
(cherry picked from commit ca89e12963)
242 lines
5.7 KiB
Go
242 lines
5.7 KiB
Go
package dbus
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
|
|
)
|
|
|
|
type objectParams struct {
|
|
bus string
|
|
dest string
|
|
path string
|
|
iface string
|
|
}
|
|
|
|
func extractObjectParams(p map[string]any, requirePath bool) (objectParams, error) {
|
|
bus, err := params.String(p, "bus")
|
|
if err != nil {
|
|
return objectParams{}, err
|
|
}
|
|
dest, err := params.String(p, "dest")
|
|
if err != nil {
|
|
return objectParams{}, err
|
|
}
|
|
|
|
var path string
|
|
if requirePath {
|
|
path, err = params.String(p, "path")
|
|
if err != nil {
|
|
return objectParams{}, err
|
|
}
|
|
} else {
|
|
path = params.StringOpt(p, "path", "/")
|
|
}
|
|
|
|
iface, err := params.String(p, "interface")
|
|
if err != nil {
|
|
return objectParams{}, err
|
|
}
|
|
|
|
return objectParams{bus: bus, dest: dest, path: path, iface: iface}, nil
|
|
}
|
|
|
|
func HandleRequest(conn net.Conn, req models.Request, m *Manager, clientID string) {
|
|
switch req.Method {
|
|
case "dbus.call":
|
|
handleCall(conn, req, m)
|
|
case "dbus.getProperty":
|
|
handleGetProperty(conn, req, m)
|
|
case "dbus.setProperty":
|
|
handleSetProperty(conn, req, m)
|
|
case "dbus.getAllProperties":
|
|
handleGetAllProperties(conn, req, m)
|
|
case "dbus.introspect":
|
|
handleIntrospect(conn, req, m)
|
|
case "dbus.listNames":
|
|
handleListNames(conn, req, m)
|
|
case "dbus.subscribe":
|
|
handleSubscribe(conn, req, m, clientID)
|
|
case "dbus.unsubscribe":
|
|
handleUnsubscribe(conn, req, m)
|
|
default:
|
|
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
|
|
}
|
|
}
|
|
|
|
func handleCall(conn net.Conn, req models.Request, m *Manager) {
|
|
op, err := extractObjectParams(req.Params, true)
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
method, err := params.String(req.Params, "method")
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
var args []any
|
|
if argsRaw, ok := params.Any(req.Params, "args"); ok {
|
|
if argsSlice, ok := argsRaw.([]any); ok {
|
|
args = argsSlice
|
|
}
|
|
}
|
|
|
|
result, err := m.Call(op.bus, op.dest, op.path, op.iface, method, args)
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
models.Respond(conn, req.ID, result)
|
|
}
|
|
|
|
func handleGetProperty(conn net.Conn, req models.Request, m *Manager) {
|
|
op, err := extractObjectParams(req.Params, true)
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
property, err := params.String(req.Params, "property")
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
result, err := m.GetProperty(op.bus, op.dest, op.path, op.iface, property)
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
models.Respond(conn, req.ID, result)
|
|
}
|
|
|
|
func handleSetProperty(conn net.Conn, req models.Request, m *Manager) {
|
|
op, err := extractObjectParams(req.Params, true)
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
property, err := params.String(req.Params, "property")
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
value, ok := params.Any(req.Params, "value")
|
|
if !ok {
|
|
models.RespondError(conn, req.ID, "missing 'value' parameter")
|
|
return
|
|
}
|
|
|
|
if err := m.SetProperty(op.bus, op.dest, op.path, op.iface, property, value); err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true})
|
|
}
|
|
|
|
func handleGetAllProperties(conn net.Conn, req models.Request, m *Manager) {
|
|
op, err := extractObjectParams(req.Params, true)
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
result, err := m.GetAllProperties(op.bus, op.dest, op.path, op.iface)
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
models.Respond(conn, req.ID, result)
|
|
}
|
|
|
|
func handleIntrospect(conn net.Conn, req models.Request, m *Manager) {
|
|
bus, err := params.String(req.Params, "bus")
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
dest, err := params.String(req.Params, "dest")
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
path := params.StringOpt(req.Params, "path", "/")
|
|
|
|
result, err := m.Introspect(bus, dest, path)
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
models.Respond(conn, req.ID, result)
|
|
}
|
|
|
|
func handleListNames(conn net.Conn, req models.Request, m *Manager) {
|
|
bus, err := params.String(req.Params, "bus")
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
result, err := m.ListNames(bus)
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
models.Respond(conn, req.ID, result)
|
|
}
|
|
|
|
func handleSubscribe(conn net.Conn, req models.Request, m *Manager, clientID string) {
|
|
if id := params.StringOpt(req.Params, "clientId", ""); id != "" {
|
|
clientID = id
|
|
}
|
|
|
|
bus, err := params.String(req.Params, "bus")
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
sender := params.StringOpt(req.Params, "sender", "")
|
|
path := params.StringOpt(req.Params, "path", "")
|
|
iface := params.StringOpt(req.Params, "interface", "")
|
|
member := params.StringOpt(req.Params, "member", "")
|
|
|
|
result, err := m.Subscribe(clientID, bus, sender, path, iface, member)
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
models.Respond(conn, req.ID, result)
|
|
}
|
|
|
|
func handleUnsubscribe(conn net.Conn, req models.Request, m *Manager) {
|
|
subID, err := params.String(req.Params, "subscriptionId")
|
|
if err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
if err := m.Unsubscribe(subID); err != nil {
|
|
models.RespondError(conn, req.ID, err.Error())
|
|
return
|
|
}
|
|
|
|
models.Respond(conn, req.ID, models.SuccessResult{Success: true})
|
|
}
|