Description: Fix file existence disclosures (CVE-2020-16128) Move set_euid_egid into aptdaemon.utils and use it when setting Terminal and DebConfSocket properties, to ensure our checks only have access to the same objects the user has. . Rewrite the checks so that they avoid extra os.access lookups and have correct error handling for os.open() in the terminal, and os.stat() in the debconf case. Author: Julian Andres Klode Bug-Ubuntu: https://bugs.launchpad.net/bugs/1899513 --- a/aptdaemon/core.py +++ b/aptdaemon/core.py @@ -60,7 +60,7 @@ from . import enums from defer import inline_callbacks, return_value, Deferred from defer.utils import dbus_deferred_method from . import policykit1 -from .utils import split_package_id +from .utils import split_package_id, set_euid_egid from .worker import DummyWorker from .worker.aptworker import (AptWorker, trans_only_installs_pkgs_from_high_trust_repos) @@ -1107,24 +1107,28 @@ class Transaction(DBusObject): """ if self.status != enums.STATUS_SETTING_UP: raise errors.TransactionAlreadyRunning() - if not os.access(ttyname, os.W_OK): - raise errors.AptDaemonError("Pty device does not exist: " - "%s" % ttyname) - if not os.stat(ttyname)[4] == self.uid: - raise errors.AptDaemonError("Pty device '%s' has to be owned by" - "the owner of the transaction " - "(uid %s) " % (ttyname, self.uid)) - if os.path.dirname(ttyname) != "/dev/pts": - raise errors.AptDaemonError("%s isn't a tty" % ttyname) - try: - slave_fd = os.open(ttyname, os.O_RDWR | os.O_NOCTTY) - if os.isatty(slave_fd): - self.terminal = dbus.String(ttyname) - self.PropertyChanged("Terminal", self.terminal) - else: + with set_euid_egid(self.uid, self.gid): + if os.path.dirname(ttyname) != "/dev/pts": raise errors.AptDaemonError("%s isn't a tty" % ttyname) - finally: - os.close(slave_fd) + + slave_fd = None + try: + slave_fd = os.open(ttyname, os.O_RDWR | os.O_NOCTTY) + except Exception: + raise errors.AptDaemonError("Could not open %s" % ttyname) + else: + if os.fstat(slave_fd).st_uid != self.uid: + raise errors.AptDaemonError("Pty device '%s' has to be owned by" + "the owner of the transaction " + "(uid %s) " % (ttyname, self.uid)) + if os.isatty(slave_fd): + self.terminal = dbus.String(ttyname) + self.PropertyChanged("Terminal", self.terminal) + else: + raise errors.AptDaemonError("%s isn't a tty" % ttyname) + finally: + if slave_fd is not None: + os.close(slave_fd) def _set_debconf(self, debconf_socket): """Set the socket of the debconf proxy. @@ -1140,13 +1144,17 @@ class Transaction(DBusObject): """ if self.status != enums.STATUS_SETTING_UP: raise errors.TransactionAlreadyRunning() - if not os.access(debconf_socket, os.W_OK): - raise errors.AptDaemonError("socket does not exist: " - "%s" % debconf_socket) - if not os.stat(debconf_socket)[4] == self.uid: - raise errors.AptDaemonError("socket '%s' has to be owned by the " - "owner of the " - "transaction" % debconf_socket) + with set_euid_egid(self.uid, self.gid): + try: + stat = os.stat(debconf_socket) + except Exception: + raise errors.AptDaemonError("socket status could not be read: " + "%s" % debconf_socket) + else: + if stat.st_uid != self.uid: + raise errors.AptDaemonError("socket '%s' has to be owned by the " + "owner of the " + "transaction" % debconf_socket) self.debconf = dbus.String(debconf_socket) self.PropertyChanged("DebconfSocket", self.debconf) --- a/aptdaemon/worker/aptworker.py +++ b/aptdaemon/worker/aptworker.py @@ -58,6 +58,7 @@ from . import BaseWorker from ..enums import * from ..errors import * from .. import lock +from ..utils import set_euid_egid from ..progress import ( DaemonOpenProgress, DaemonInstallProgress, @@ -90,25 +91,6 @@ USE_HTTP="yes" """ -@contextlib.contextmanager -def set_euid_egid(uid, gid): - # no need to drop privs - if os.getuid() != 0 and os.getgid() != 0: - yield - return - # temporary drop privs - os.setegid(gid) - old_groups = os.getgroups() - os.setgroups([gid]) - os.seteuid(uid) - try: - yield - finally: - os.seteuid(os.getuid()) - os.setegid(os.getgid()) - os.setgroups(old_groups) - - def trans_only_installs_pkgs_from_high_trust_repos(trans, whitelist=set()): """Return True if this transaction only touches packages in the --- a/aptdaemon/utils.py +++ b/aptdaemon/utils.py @@ -24,7 +24,9 @@ __author__ = "Sebastian Heinlein