Skip to content

Commit dbd76ed

Browse files
committed
Add support for run0 as an alternative to pkexec
run0 allows escalating privileges through polkit without SUID binaries, if pkexec is not available try it as a fallback method https://www.freedesktop.org/software/systemd/man/latest/run0.htm
1 parent e6fd59b commit dbd76ed

2 files changed

Lines changed: 76 additions & 3 deletions

File tree

files/usr/bin/cinnamon-settings-users

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,35 @@
44
"""
55

66
import os
7+
import shutil
8+
import sys
79

8-
os.system("pkexec /usr/share/cinnamon/cinnamon-settings-users/cinnamon-settings-users.py")
10+
TARGET = "/usr/share/cinnamon/cinnamon-settings-users/cinnamon-settings-users.py"
11+
12+
# Display-related environment variables that need to be forwarded to the
13+
# privileged process when launching via run0 (which, unlike pkexec, does not
14+
# inherit the caller's environment).
15+
RUN0_FORWARDED_ENV_VARS = (
16+
"DISPLAY",
17+
"WAYLAND_DISPLAY",
18+
"XAUTHORITY",
19+
"XDG_RUNTIME_DIR",
20+
"DBUS_SESSION_BUS_ADDRESS",
21+
)
22+
23+
# Prefer pkexec, fall back to run0.
24+
if shutil.which("pkexec"):
25+
os.execvp("pkexec", ["pkexec", TARGET])
26+
elif shutil.which("run0"):
27+
argv = ["run0"]
28+
for var in RUN0_FORWARDED_ENV_VARS:
29+
value = os.environ.get(var)
30+
if value is not None:
31+
argv.append(f"--setenv={var}={value}")
32+
argv.append("--")
33+
argv.append(TARGET)
34+
os.execvp("run0", argv)
35+
36+
print("cinnamon-settings-users: neither 'pkexec' nor 'run0' was found in PATH",
37+
file=sys.stderr)
38+
sys.exit(1)

files/usr/share/cinnamon/cinnamon-settings/bin/SettingsWidgets.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,39 @@
1919

2020
CAN_BACKEND = ["SoundFileChooser", "DateChooser", "TimeChooser", "Keybinding"]
2121

22+
# Display-related environment variables that need to be forwarded to the
23+
# privileged process when launching via run0 (which, unlike pkexec, does not
24+
# inherit the caller's environment).
25+
_RUN0_FORWARDED_ENV_VARS = (
26+
"DISPLAY",
27+
"WAYLAND_DISPLAY",
28+
"XAUTHORITY",
29+
"XDG_RUNTIME_DIR",
30+
"DBUS_SESSION_BUS_ADDRESS",
31+
)
32+
33+
# Prefer pkexec, fall back to run0.
34+
def get_privilege_escalation_command():
35+
for cmd in ("pkexec", "run0"):
36+
if GLib.find_program_in_path(cmd) is not None:
37+
return cmd
38+
return None
39+
40+
def build_privileged_argv(command_argv):
41+
escalation = get_privilege_escalation_command()
42+
if escalation is None:
43+
return None
44+
if escalation == "run0":
45+
argv = [escalation]
46+
for var in _RUN0_FORWARDED_ENV_VARS:
47+
value = GLib.getenv(var)
48+
if value is not None:
49+
argv.append(f"--setenv={var}={value}")
50+
argv.append("--")
51+
argv.extend(command_argv)
52+
return argv
53+
return [escalation, *command_argv]
54+
2255
class BinFileMonitor(GObject.GObject):
2356
__gsignals__ = {
2457
'changed': (GObject.SignalFlags.RUN_LAST, None, ()),
@@ -218,7 +251,12 @@ def build(self):
218251
self.module.loaded = True
219252

220253
if self.is_standalone:
221-
subprocess.Popen(self.exec_name.split())
254+
argv = self.exec_name.split()
255+
if argv and argv[0] == "pkexec":
256+
privileged = build_privileged_argv(argv[1:])
257+
if privileged is not None:
258+
argv = privileged
259+
subprocess.Popen(argv)
222260
return
223261

224262
# Add our own widgets
@@ -297,7 +335,12 @@ def __init__(self, label, mod_id, icon, category, keywords, content_box):
297335
self.category = category
298336

299337
def process (self):
300-
name = self.name.replace("pkexec ", "")
338+
name = self.name
339+
if name.startswith("pkexec "):
340+
# Require some privilege-escalation tool to exist (pkexec or run0).
341+
if get_privilege_escalation_command() is None:
342+
return False
343+
name = name[len("pkexec "):]
301344
name = name.split()[0]
302345

303346
return GLib.find_program_in_path(name) is not None

0 commit comments

Comments
 (0)