Systray icon, started by scheduled root-mode or foreign-user backup profiles via cron, allows privilege escalation
Describe the problem, feature or ask a question:
The systray-icon process runs as root (at least when launched from cron). This allows it to lauch the BiT GUI as root without authentication. It indeed does not request authentication when one clicks the "Start Back in Time" button in the right-click menu of the systray icon. If one gains access to GUI running as root, they can read any backed-up file, and also overwrite any file by using the Restore feature.
A trivial solution to this problem would be to ask for the password (the root password, or the password of the user who owns the BiT profile that is doing the backup) when the user clicks the "Start Back in Time" button, and maybe also when the "View Last Log" button is clicked.
A more correct solution would be to run the icon process unpriviledged. If this is implemented, this would possibly restrict some of the functionality of the icon, e.g. it could not access the logs of other users' BiT profiles, and the pause/resume/stop feature would stop working altogether. To fix this, the icon process would need to use an IPC mechanism to communicate with the backup process.
Introducing IPC mechanisms for communicating between GUI elements (be they the systray icon, or the BiT GUI itself) and the backend would have implications for #694. But I think that for now, it would be more feasible to just implement a prompt asking for the password, with the systray-icon process still running as root.
Hi Samo, thanks again for reporting this.
Yes, I total re-design regarding interprocess communication (IPC) (#2260) is an often mentioned topic in several issues:
- #1264
- #1230
- #827
- #770
- #694
I agree with you. I am not deep enough into the details.
What exactly does it mean that the systrayicon is started from cron? Do you mean the icon appearing when a (cron) scheduled backup runs? Does the security issue is relevant only for root-mode backup jobs or for all (cron) scheduled backup jobs?
Regards, Christian
I can reproduce the problem. I am interested in your detailed opinion about a short solution without regarding the IPC stuff. I am not sure about how valid my solution and my ideas are.
This is how the systray icon starts the BIT Gui.
https://github.com/bit-team/backintime/blob/37d5f0468d7ee89fad3685963dcee4de8f2ef144/qt/qtsystrayicon.py#L227-L233
In my understanding this code simply should "open" the GUI the same way as a regular desktop user would do via calling /usr/bin/backintime-qt_polkit. Am I right so far?
About the other features of the systray: This is how the menu currently looks like:
I don't see a reason why a regular non-root user should be able to pause/stop or view the log of a root backup job. This menu entries simply should be disabled when a root-job is running.
Event the filepaths in the status message (2nd entry in the example) shouldn't be shown to a regular user, when this is a root job.
What do you think?
What exactly does it mean that the systrayicon is started from cron? Do you mean the icon appearing when a (cron) scheduled backup runs?
Yes.
Does the security issue is relevant only for root-mode backup jobs or for all (cron) scheduled backup jobs?
All scheduled backups. (I have learned that at least some non-root jobs are executed as their respective users. But non-root jobs of other users are still a concern.)
In my understanding this code simply should "open" the GUI the same way as a regular desktop user would do via calling /usr/bin/backintime-qt_polkit. Am I right so far?
Yes, that is what I meant. Of course, as long as backintime-qt_polkit actually verifies the password, even if it is run as root.
I don't see a reason why a regular non-root user should be able to pause/stop or view the log of a root backup job. This menu entries simply should be disabled when a root-job is running. Event the filepaths in the status message (2nd entry in the example) shouldn't be shown to a regular user, when this is a root job.
I was thinking about that. I though that this (especially the status-message part) would be rejected as too pedantic. But yes, it is right.
If the named functionality was just eliminated completely (that is, root could not re-enable these features manually), then there would be no need for IPC even for unpriviledged systray icon, as long as the icon process can learn when the backup finishes.
Also, regarding this:
I don't see a reason why a regular non-root user should be able to pause/stop or view the log of a root backup job.
I do not think this is only about root/non-root jobs, but also about jobs where the desktop user (mostly of the display ":0.0", which is quasi-hardcoded in the systray-icon code) is different from the backup-job owner.
I wrote:
I have learned that at least some non-root jobs are executed as their respective users.
This is what I mean. (Sorry for the text screenshot, but it is from a virtual machine, so there is no easy was to transfer the text to my computer.) The highlighted lines represent the backup job. Read the pstree manpage for the meaning of the -u option.
The schedule option I used is "Every 5 minutes". The profile owner is user.
Great analysis. I am scared about what you may find also in BIT. 🤣
There is a question on my side. This is not only about root-jobs but about foreign-user-jobs. How can I (in the systrayicon code) distinguish if the current running job is own by the current active user or by someone else?
Do you have a tip?
In my current understand I do propose the following modifications to BIT regarding this issue.
- Determine somehow if the running job is owned by the current user or if it is a foreign job.
- If it is foreign modify the systray tooltip and context menu:
- Remove "View Last Log"
- Remove "Stop snapshot process"
- Remove "Pause snapshot process"
- Modify the status/tooltip text to be more generic ("running job from user xyz") without containing path information.
- Consider to not remove but disable (graying out) the context menu entries and adding a tooltip to them explaining why they are disabled.
- Entry "Start Back In Time" should use pkexec (ask for a password) if it is a foreign job.
Anything else?
How can I (in the systrayicon code) distinguish if the current running job is own by the current active user or by someone else?
I am not sure. But the following two lines in qt/qtsystrayicon.py are of interest:
if not os.getenv('DISPLAY', ''):
os.putenv('DISPLAY', ':0.0')
I understand these as follows:
- If the process has inherited DISPLAY, it uses DISPLAY as the output display. This happens when the backup process has been invoked manually from BIT -- the systray icon runs as the same user as BIT, so there is no need to restrict the functionality of the systray icon.
- Otherwise, DISPLAY is set manually to a hardcoded value. This is the case when the systray icon has been launched from a scheduled backup process. To determine if there is a need to restrict the functionality, it needs to find out the owner of the running graphical session. This webpage may have useful tips.
Anything else?
No, I think that's it.
Just a note by myself:
- Does each backup job trigger its own sytrayicon instance? Add debug log incl PID.
import os
import sys
from PyQt6.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction
from PyQt6.QtGui import QIcon
# Session-Erkennung
if 'WAYLAND_DISPLAY' in os.environ:
# Wayland erkannt
os.environ.setdefault('QT_QPA_PLATFORM', 'wayland')
use_xwayland = False
if os.environ.get('XDG_CURRENT_DESKTOP', '').lower() in ['gnome', 'sway']:
# XWayland-Fallback für Systray
os.environ.setdefault('QT_QPA_PLATFORM', 'xcb')
os.environ.setdefault('DISPLAY', ':0.0')
use_xwayland = True
else:
# X11
os.environ.setdefault('DISPLAY', ':0.0')
os.environ.setdefault('QT_QPA_PLATFORM', 'xcb')
use_xwayland = False
print("QT_QPA_PLATFORM =", os.environ['QT_QPA_PLATFORM'],
"| Using XWayland:", use_xwayland)
app = QApplication(sys.argv)
tray_icon = QSystemTrayIcon(QIcon("/usr/share/icons/hicolor/48x48/apps/python3.png"), app)
menu = QMenu()
quit_action = QAction("Quit", app)
quit_action.triggered.connect(app.quit)
menu.addAction(quit_action)
tray_icon.setContextMenu(menu)
tray_icon.show()
sys.exit(app.exec())
I tried some things and also considered non-systemd distros. What do you think?
- #2259