fix(ui): Never write the sudo password to a file; Update label on the progress screen

The askpass helper reads the password from a named pipe that the app
feeds from memory each time sudo asks, instead of from a file.
This commit is contained in:
theupriser committed 2026-09-27 12:33:47 +02:00
1 parent 40a121f4d1
commit dc1abd912b
2 files changed
+35 -9

No files matched your search

+1 -1
View File
@@ -858,7 +858,7 @@ ApplicationWindow {
border.width: parent.parent.st === "wait" || parent.parent.st === "run" ? 2 : 0; border.color: parent.parent.st === "run" ? t.accent : "#343f50" border.width: parent.parent.st === "wait" || parent.parent.st === "run" ? 2 : 0; border.color: parent.parent.st === "run" ? t.accent : "#343f50"
Text { anchors.centerIn: parent; text: parent.parent.parent.st === "ok" ? "✓" : (parent.parent.parent.st === "fail" ? "!" : ""); color: parent.parent.parent.st === "ok" ? t.good : t.bad; font.pixelSize: 13; font.weight: Font.Bold } Text { anchors.centerIn: parent; text: parent.parent.parent.st === "ok" ? "✓" : (parent.parent.parent.st === "fail" ? "!" : ""); color: parent.parent.parent.st === "ok" ? t.good : t.bad; font.pixelSize: 13; font.weight: Font.Bold }
RotationAnimator on rotation { running: parent.parent.parent.st === "run"; from: 0; to: 360; duration: 1000; loops: Animation.Infinite } } RotationAnimator on rotation { running: parent.parent.parent.st === "run"; from: 0; to: 360; duration: 1000; loops: Animation.Infinite } }
Text { text: ({ on: "Turn on ", off: "Turn off ", again: "Re-apply ", desktop: "Boot into ", gaming: "Boot into ", check: "Download and check the ", flash: "Hand to fwupd: the " })[parent.parent.modelData.action] + ((texts[parent.parent.modelData.id] || {}).label || parent.parent.modelData.id) Text { text: (({ on: "Turn on ", off: "Turn off ", again: "Re-apply ", update: "Update ", desktop: "Boot into ", gaming: "Boot into ", check: "Download and check the ", flash: "Hand to fwupd: the " })[parent.parent.modelData.action] || "") + ((texts[parent.parent.modelData.id] || {}).label || parent.parent.modelData.id)
color: parent.parent.st === "wait" ? t.faint : t.textHi; font.family: t.body; font.pixelSize: 16; font.weight: Font.DemiBold; anchors.verticalCenter: parent.verticalCenter; width: 380; elide: Text.ElideRight } color: parent.parent.st === "wait" ? t.faint : t.textHi; font.family: t.body; font.pixelSize: 16; font.weight: Font.DemiBold; anchors.verticalCenter: parent.verticalCenter; width: 380; elide: Text.ElideRight }
Text { text: ({ wait: "Waiting", run: "Working…", ok: "Done", fail: "Problem" })[parent.parent.st]; color: parent.parent.st === "ok" ? t.good : (parent.parent.st === "fail" ? t.bad : "#b8c3d1"); font.family: t.body; font.pixelSize: 13; anchors.verticalCenter: parent.verticalCenter } Text { text: ({ wait: "Waiting", run: "Working…", ok: "Done", fail: "Problem" })[parent.parent.st]; color: parent.parent.st === "ok" ? t.good : (parent.parent.st === "fail" ? t.bad : "#b8c3d1"); font.family: t.body; font.pixelSize: 13; anchors.verticalCenter: parent.verticalCenter }
} }
+34 -8
View File
@@ -197,19 +197,19 @@ class Backend(QObject):
p.start() p.start()
def _make_askpass(self, password): def _make_askpass(self, password):
"""A private folder with the password, a helper that prints it for """A private folder with a helper that `sudo -A` runs for the
`sudo -A`, and a `sudo` that always uses the helper (makepkg and password, and a `sudo` that always uses it (makepkg and yay call
yay call plain sudo).""" plain sudo). The password is never written to a file: the helper
reads it from a named pipe, and the app writes it into the pipe from
memory each time sudo asks, until _drop_askpass."""
base = os.environ.get("XDG_RUNTIME_DIR") or tempfile.gettempdir() base = os.environ.get("XDG_RUNTIME_DIR") or tempfile.gettempdir()
d = tempfile.mkdtemp(prefix="steamify-", dir=base) d = tempfile.mkdtemp(prefix="steamify-", dir=base)
os.chmod(d, stat.S_IRWXU) os.chmod(d, stat.S_IRWXU)
secret = os.path.join(d, "secret") pipe = os.path.join(d, "pipe")
fd = os.open(secret, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) os.mkfifo(pipe, 0o600)
with os.fdopen(fd, "w") as f:
f.write(password + "\n")
askpass = os.path.join(d, "askpass") askpass = os.path.join(d, "askpass")
with open(askpass, "w") as f: with open(askpass, "w") as f:
f.write("#!/bin/sh\nexec cat %s\n" % json.dumps(secret)) f.write("#!/bin/sh\nexec cat %s\n" % json.dumps(pipe))
os.chmod(askpass, 0o700) os.chmod(askpass, 0o700)
bindir = os.path.join(d, "bin") bindir = os.path.join(d, "bin")
os.mkdir(bindir) os.mkdir(bindir)
@@ -217,9 +217,35 @@ class Backend(QObject):
with open(wrapper, "w") as f: with open(wrapper, "w") as f:
f.write("#!/bin/sh\nexec /usr/bin/sudo -A \"$@\"\n") f.write("#!/bin/sh\nexec /usr/bin/sudo -A \"$@\"\n")
os.chmod(wrapper, 0o700) os.chmod(wrapper, 0o700)
stop = threading.Event()
data = (password + "\n").encode()
def serve():
while not stop.is_set():
try:
fd = os.open(pipe, os.O_WRONLY) # waits for a reader
except OSError:
return
try:
if not stop.is_set():
os.write(fd, data)
except OSError:
pass
finally:
os.close(fd)
threading.Thread(target=serve, daemon=True).start()
self._askpass_stop = (stop, pipe)
return d, askpass, bindir return d, askpass, bindir
def _drop_askpass(self): def _drop_askpass(self):
if getattr(self, "_askpass_stop", None):
stop, pipe = self._askpass_stop
self._askpass_stop = None
stop.set()
try: # wakes the writer waiting for a reader, which then ends
os.close(os.open(pipe, os.O_RDONLY | os.O_NONBLOCK))
except OSError:
pass
if self._secret_dir: if self._secret_dir:
shutil.rmtree(self._secret_dir, ignore_errors=True) shutil.rmtree(self._secret_dir, ignore_errors=True)
self._secret_dir = None self._secret_dir = None