Merge pull request #2 from rfmyrick/fix/v1.8.1-key-capture
fix(capture): restore key overlays after hardening
This commit is contained in:
+29
-36
@@ -14,18 +14,28 @@
|
|||||||
|
|
||||||
local function shell_quote(s) return "'" .. s:gsub("'", "'\\''") .. "'" end
|
local function shell_quote(s) return "'" .. s:gsub("'", "'\\''") .. "'" end
|
||||||
|
|
||||||
|
local function effective_uid()
|
||||||
|
local f = io.open("/proc/self/status", "r")
|
||||||
|
if not f then return nil end
|
||||||
|
for line in f:lines() do
|
||||||
|
local uid = line:match("^Uid:%s+%d+%s+(%d+)")
|
||||||
|
if uid then
|
||||||
|
f:close()
|
||||||
|
return uid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
f:close()
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
local function is_runtime_secure(r)
|
local function is_runtime_secure(r)
|
||||||
if not r or r == "" then return false end
|
if not r or r == "" then return false end
|
||||||
if r == "/tmp" then return false end
|
if r == "/tmp" then return false end
|
||||||
if r:sub(1, 1) ~= "/" then return false end
|
if r:sub(1, 1) ~= "/" then return false end
|
||||||
local q = shell_quote(r)
|
-- /run/user is root-controlled, so accepting only this process's systemd
|
||||||
-- POSIX-only checks (test -d, test -w): no GNU `test -O`, no `stat`,
|
-- runtime path avoids shell probes that fail in Hyprland's Lua runtime.
|
||||||
-- no command substitution. They behave identically inside Hyprland's
|
local uid = effective_uid()
|
||||||
-- os.execute and in any POSIX shell. XDG_RUNTIME_DIR is always a 0700
|
return uid ~= nil and r == "/run/user/" .. uid
|
||||||
-- dir owned by the user, so "exists + writable by us + absolute + not
|
|
||||||
-- /tmp" is sufficient; the predictable /tmp fallback is rejected above.
|
|
||||||
local res = os.execute("test -d " .. q .. " && test -w " .. q)
|
|
||||||
return res == 0 or res == true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local runtime = os.getenv("XDG_RUNTIME_DIR")
|
local runtime = os.getenv("XDG_RUNTIME_DIR")
|
||||||
@@ -38,38 +48,17 @@ if not is_runtime_secure(runtime) then
|
|||||||
runtime = nil
|
runtime = nil
|
||||||
end
|
end
|
||||||
local STATE_FILE = runtime and (runtime .. "/omarchy-key-visualizer.json") or nil
|
local STATE_FILE = runtime and (runtime .. "/omarchy-key-visualizer.json") or nil
|
||||||
|
local SUPER_FLAG = runtime and (runtime .. "/omarchy-key-visualizer-super") or nil
|
||||||
|
|
||||||
local function secure_write(path, content)
|
local function secure_write(path, content)
|
||||||
if not path then return false end
|
if not runtime or (path ~= STATE_FILE and path ~= SUPER_FLAG) then return false end
|
||||||
-- Reject the exact predictable fallback that the security review flagged.
|
-- FileView watches the existing inode. These are fixed paths inside the
|
||||||
-- A valid XDG_RUNTIME_DIR under /tmp with a random suffix and 0700 is
|
-- current user's private runtime directory, so update them in place.
|
||||||
-- allowed because it passed is_runtime_secure().
|
local f = io.open(path, "w")
|
||||||
if path == "/tmp/omarchy-key-visualizer.json" or path == "/tmp/omarchy-key-visualizer-super" then return false end
|
|
||||||
local q = shell_quote(path)
|
|
||||||
-- Refuse to follow a symlink at the destination (O_NOFOLLOW mitigation).
|
|
||||||
-- POSIX `test ! -h` succeeds when the file does not exist or is not a
|
|
||||||
-- symlink (the `-h` test is POSIX; `-L` is the GNU alias and is not
|
|
||||||
-- reliable inside Hyprland's os.execute). It fails only when the
|
|
||||||
-- destination is a symlink, which we must not follow.
|
|
||||||
local not_symlink = os.execute("test ! -h " .. q)
|
|
||||||
if not (not_symlink == 0 or not_symlink == true) then
|
|
||||||
print("[key-visualizer] refusing to write symlink: " .. path)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
local tmp = path .. ".tmp"
|
|
||||||
local f = io.open(tmp, "w")
|
|
||||||
if not f then return false end
|
if not f then return false end
|
||||||
f:write(content)
|
f:write(content)
|
||||||
f:close()
|
f:close()
|
||||||
os.execute("chmod 600 " .. shell_quote(tmp) .. " 2>/dev/null")
|
os.execute("chmod 600 " .. shell_quote(path) .. " 2>/dev/null")
|
||||||
-- Atomic replace; avoids truncating a file that may have been swapped
|
|
||||||
-- between the symlink check and the open (TOCTOU mitigation).
|
|
||||||
local ok = os.rename(tmp, path)
|
|
||||||
if not ok then
|
|
||||||
os.execute("rm -f " .. shell_quote(tmp) .. " 2>/dev/null")
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
os.execute("chmod 600 " .. q .. " 2>/dev/null")
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -202,7 +191,6 @@ end
|
|||||||
-- Super-held flag: the panel/display watches this to know when to capture the
|
-- Super-held flag: the panel/display watches this to know when to capture the
|
||||||
-- SUPER+drag on the overlay (instead of a window underneath). Written only on
|
-- SUPER+drag on the overlay (instead of a window underneath). Written only on
|
||||||
-- transitions so it does not spam the filesystem on every key.
|
-- transitions so it does not spam the filesystem on every key.
|
||||||
local SUPER_FLAG = runtime and (runtime .. "/omarchy-key-visualizer-super") or nil
|
|
||||||
local last_super = nil
|
local last_super = nil
|
||||||
local function super_down()
|
local function super_down()
|
||||||
return pressed[133] or pressed[134]
|
return pressed[133] or pressed[134]
|
||||||
@@ -215,6 +203,11 @@ local function emit_super()
|
|||||||
secure_write(SUPER_FLAG, down and "1" or "0")
|
secure_write(SUPER_FLAG, down and "1" or "0")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Create stable files before Quickshell starts watching them and clear stale
|
||||||
|
-- state left by a compositor restart.
|
||||||
|
emit()
|
||||||
|
emit_super()
|
||||||
|
|
||||||
-- Combos: a combination of keys is treated as a unit. The display only
|
-- Combos: a combination of keys is treated as a unit. The display only
|
||||||
-- updates on key-down (the combo grows as you press) and when the last key
|
-- updates on key-down (the combo grows as you press) and when the last key
|
||||||
-- is released (the empty payload starts the panel's linger with the last
|
-- is released (the empty payload starts the panel's linger with the last
|
||||||
|
|||||||
Reference in New Issue
Block a user