mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-06 05:28:29 -04:00
core: fix security and concurrency issues found in a backend audit (#2805)
* 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>
This commit is contained in:
@@ -299,6 +299,9 @@ type hyprlandOverrideBind struct {
|
||||
Options map[string]any
|
||||
// Unbind: negative override (hl.unbind only, no rebind).
|
||||
Unbind bool
|
||||
// RawLuaAction: Action is a custom hl.* Lua expression round-tripped from an
|
||||
// existing Lua override; re-emit it verbatim instead of quoting it.
|
||||
RawLuaAction bool
|
||||
}
|
||||
|
||||
func (h *HyprlandProvider) ensureWritableConfig() error {
|
||||
@@ -1046,18 +1049,26 @@ func luaActionStringFromHyprlangAction(action string) string {
|
||||
if expr, ok := luaActionStringFromKnownHyprlandAction(action); ok {
|
||||
return expr
|
||||
}
|
||||
return action
|
||||
// Unrecognized dispatchers are freeform text, not Lua; forward them to
|
||||
// hyprctl quoted so a stray `"` can't produce broken Lua output.
|
||||
return luaHyprctlDispatchFunction(action)
|
||||
}
|
||||
|
||||
func luaExprToInternalAction(expr string) string {
|
||||
// luaExprToInternalAction converts a parsed Lua bind expression back into
|
||||
// "dispatcher params" text. isRawLua reports that expr matched no known hl.*
|
||||
// shape and must be re-emitted verbatim as Lua on write-back.
|
||||
func luaExprToInternalAction(expr string) (action string, isRawLua bool) {
|
||||
d, p := luaExprToDispatcherParams(expr)
|
||||
if d == expr && p == "" {
|
||||
return expr, true
|
||||
}
|
||||
if d == "exec" && p != "" && !strings.HasPrefix(p, "hyprctl dispatch lua:") {
|
||||
return "exec " + p
|
||||
return "exec " + p, false
|
||||
}
|
||||
if p != "" {
|
||||
return d + " " + p
|
||||
return d + " " + p, false
|
||||
}
|
||||
return d
|
||||
return d, false
|
||||
}
|
||||
|
||||
func luaBindOptions(bind *hyprlandOverrideBind) []string {
|
||||
@@ -1075,20 +1086,25 @@ func luaBindOptions(bind *hyprlandOverrideBind) []string {
|
||||
}
|
||||
|
||||
func writeLuaBindLine(sb *strings.Builder, bind *hyprlandOverrideBind) {
|
||||
key := formatLuaBindKey(bind.Key)
|
||||
key := strconv.Quote(formatLuaBindKey(bind.Key))
|
||||
if bind.Unbind {
|
||||
fmt.Fprintf(sb, `hl.unbind("%s")`, key)
|
||||
fmt.Fprintf(sb, `hl.unbind(%s)`, key)
|
||||
sb.WriteByte('\n')
|
||||
return
|
||||
}
|
||||
expr := luaActionStringFromHyprlangAction(bind.Action)
|
||||
var expr string
|
||||
if bind.RawLuaAction {
|
||||
expr = bind.Action
|
||||
} else {
|
||||
expr = luaActionStringFromHyprlangAction(bind.Action)
|
||||
}
|
||||
opts := luaBindOptions(bind)
|
||||
fmt.Fprintf(sb, `hl.unbind("%s")`, key)
|
||||
fmt.Fprintf(sb, `hl.unbind(%s)`, key)
|
||||
sb.WriteByte('\n')
|
||||
if len(opts) > 0 {
|
||||
fmt.Fprintf(sb, `hl.bind("%s", %s, { %s })`, key, expr, strings.Join(opts, ", "))
|
||||
fmt.Fprintf(sb, `hl.bind(%s, %s, { %s })`, key, expr, strings.Join(opts, ", "))
|
||||
} else {
|
||||
fmt.Fprintf(sb, `hl.bind("%s", %s)`, key, expr)
|
||||
fmt.Fprintf(sb, `hl.bind(%s, %s)`, key, expr)
|
||||
}
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
@@ -1104,17 +1120,18 @@ func parseLuaBindOverrideLine(line string) (*hyprlandOverrideBind, bool) {
|
||||
}
|
||||
internalKey := luaKeyComboToInternalKey(kbc)
|
||||
|
||||
action := luaExprToInternalAction(actionExpr)
|
||||
action, isRawLua := luaExprToInternalAction(actionExpr)
|
||||
flags := luaBindOptFlags(optSuffix)
|
||||
description := luaBindOptDescription(optSuffix)
|
||||
if description == "" {
|
||||
description = luaLineTrailingComment(line)
|
||||
}
|
||||
return &hyprlandOverrideBind{
|
||||
Key: internalKey,
|
||||
Action: action,
|
||||
Description: description,
|
||||
Flags: flags,
|
||||
Key: internalKey,
|
||||
Action: action,
|
||||
Description: description,
|
||||
Flags: flags,
|
||||
RawLuaAction: isRawLua,
|
||||
}, true
|
||||
}
|
||||
|
||||
|
||||
@@ -147,9 +147,10 @@ hl.bind("SUPER + N", hl.dsp.exec_cmd("dms ipc call notepad toggle"), { descripti
|
||||
func TestWriteLuaBindLineLeavesCustomLuaDispatcherRaw(t *testing.T) {
|
||||
var sb strings.Builder
|
||||
writeLuaBindLine(&sb, &hyprlandOverrideBind{
|
||||
Key: "Super+u",
|
||||
Action: "hl.dsp.no_op()",
|
||||
Description: "Custom Lua",
|
||||
Key: "Super+u",
|
||||
Action: "hl.dsp.no_op()",
|
||||
Description: "Custom Lua",
|
||||
RawLuaAction: true,
|
||||
})
|
||||
|
||||
want := `hl.unbind("SUPER + U")
|
||||
@@ -159,6 +160,24 @@ hl.bind("SUPER + U", hl.dsp.no_op(), { description = "Custom Lua" })`
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteLuaBindLineQuotesUnrecognizedActionWithoutRawLuaFlag(t *testing.T) {
|
||||
var sb strings.Builder
|
||||
writeLuaBindLine(&sb, &hyprlandOverrideBind{
|
||||
Key: "Super+u",
|
||||
Action: `customdispatcher "),os.execute("id")--`,
|
||||
})
|
||||
|
||||
got := sb.String()
|
||||
if !strings.Contains(got, "hl.exec_cmd(") {
|
||||
t.Fatalf("expected unrecognized action to go through the hyprctl-dispatch wrapper, got %q", got)
|
||||
}
|
||||
// an unpaired bare quote means the action broke out of its string literal
|
||||
withoutEscapedQuotes := strings.ReplaceAll(got, `\"`, "")
|
||||
if n := strings.Count(withoutEscapedQuotes, `"`); n%2 != 0 {
|
||||
t.Fatalf("action broke out of its string literal (%d unpaired quotes): %q", n, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLuaActionStringFromHyprlangActionUsesNativeDispatchers(t *testing.T) {
|
||||
tests := []struct {
|
||||
action string
|
||||
@@ -226,15 +245,12 @@ func TestParseLuaBindLineHandlesFunctionDispatcherFallback(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLuaActionStringLeavesCustomLuaDispatcherRaw(t *testing.T) {
|
||||
func TestLuaActionStringFromHyprlangActionAlwaysQuotesUnrecognizedText(t *testing.T) {
|
||||
got := luaActionStringFromHyprlangAction("hl.dsp.no_op()")
|
||||
want := `hl.dsp.no_op()`
|
||||
want := `function() hl.exec_cmd("hyprctl dispatch hl.dsp.no_op()") end`
|
||||
if got != want {
|
||||
t.Fatalf("luaActionStringFromHyprlangAction() = %q, want %q", got, want)
|
||||
}
|
||||
if strings.Contains(got, "hl.dispatch") || strings.Contains(got, "hyprctl dispatch") {
|
||||
t.Fatalf("expected custom Lua dispatcher expression to stay raw, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadLuaOverrideMigratesTrailingCommentToDescription(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user