[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Bug#789162: aptdaemon: CVE-2015-1323: information disclosure via simulate dbus method



Hi,
On Thu, Jun 18, 2015 at 02:33:43PM +0200, Salvatore Bonaccorso wrote:
> Source: aptdaemon
> Version: 1.1.1-4
> Severity: grave
> Tags: security upstream
> 
> Hi,
> 
> the following vulnerability was published for aptdaemon, which AFICS
> as well affects Debian.
> 
> CVE-2015-1323[0]:
> information disclosure via simulate dbus method
> 
> If you fix the vulnerability please also make sure to include the
> CVE (Common Vulnerabilities & Exposures) id in your changelog entry.

Please find patches for wheezy-security and jessie-security attached.

The tracker has:

  "For jessie-security compat layer for PackageKit needs to be dropped"

The current version in sid also has "Drop PackageKit compat layer" which
seems to confirm that but there's no explanation why needs to go.

I'm happy about any review or a thumgs up to upload to security-master.

Cheers,
 -- Guido
diff --git a/debian/changelog b/debian/changelog
index eb3eb13..3366b5d 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+aptdaemon (0.45-2+deb7u1) wheezy-security; urgency=high
+
+  * Non maintainer upload
+  * Add CVE-2015-1323.patch to address CVE-2015-1323 - taken from
+    0.43+bzr805-0ubuntu10 (Closes: #789162)
+
+ -- Guido Günther <agx@sigxcpu.org>  Mon, 29 Feb 2016 08:33:47 +0100
+
 aptdaemon (0.45-2) unstable; urgency=medium
 
   * Check downloaded key id; merged from Ubuntu (CVE-2012-0962)
diff --git a/debian/patches/CVE-2015-1323.patch b/debian/patches/CVE-2015-1323.patch
new file mode 100644
index 0000000..09bcfb6
--- /dev/null
+++ b/debian/patches/CVE-2015-1323.patch
@@ -0,0 +1,373 @@
+From: =?utf-8?q?Guido_G=C3=BCnther?= <agx@sigxcpu.org>
+Date: Sun, 28 Feb 2016 19:55:02 +0100
+Subject: CVE-2015-1323
+
+---
+ aptdaemon/core.py             | 10 ++++++----
+ aptdaemon/pkcompat.py         | 11 +++++++----
+ aptdaemon/policykit1.py       |  9 ++++++---
+ aptdaemon/worker.py           | 43 ++++++++++++++++++++++++++++++++++++++-----
+ tests/test_dbus_type.py       |  6 ++++--
+ tests/test_unicodedecoding.py |  3 ++-
+ tests/test_worker.py          | 31 ++++++++++++++++---------------
+ 7 files changed, 79 insertions(+), 34 deletions(-)
+
+diff --git a/aptdaemon/core.py b/aptdaemon/core.py
+index b69923d..6d841e3 100644
+--- a/aptdaemon/core.py
++++ b/aptdaemon/core.py
+@@ -330,7 +330,7 @@ class Transaction(DBusObject):
+                            "DebconfSocket", "MetaData", "Locale",
+                            "RemoveObsoleteDepends")
+ 
+-    def __init__(self, tid, role, queue, pid, uid, cmdline, sender,
++    def __init__(self, tid, role, queue, pid, uid, gid, cmdline, sender,
+                  connect=True, bus=None, packages=None, kwargs=None):
+         """Initialize a new Transaction instance.
+ 
+@@ -365,6 +365,7 @@ class Transaction(DBusObject):
+             kwargs = {}
+         self.queue = queue
+         self.uid = uid
++        self.gid = gid
+         self.locale = dbus.String("")
+         self.allow_unauthenticated = dbus.Boolean(False)
+         self.remove_obsoleted_depends = dbus.Boolean(False)
+@@ -1469,11 +1470,12 @@ class AptDaemon(DBusObject):
+     @inline_callbacks
+     def _create_trans(self, role, sender, packages=None, kwargs=None):
+         """Helper method which returns the tid of a new transaction."""
+-        pid, uid, cmdline = \
++        pid, uid, gid, cmdline = \
+                 yield policykit1.get_proc_info_from_dbus_name(sender, self.bus)
+         tid = uuid.uuid4().get_hex()
+-        trans = Transaction(tid, role, self.queue, pid, uid, cmdline, sender,
+-                            packages=packages, kwargs=kwargs, bus=self.bus)
++        trans = Transaction(
++            tid, role, self.queue, pid, uid, gid, cmdline, sender,
++            packages=packages, kwargs=kwargs, bus=self.bus)
+         self.queue.limbo[trans.tid] = trans
+         return_value(trans.tid)
+ 
+diff --git a/aptdaemon/pkcompat.py b/aptdaemon/pkcompat.py
+index 0806201..845c72e 100644
+--- a/aptdaemon/pkcompat.py
++++ b/aptdaemon/pkcompat.py
+@@ -408,9 +408,10 @@ class PackageKit(aptdaemon.core.DBusObject):
+ 
+     @inline_callbacks
+     def _get_tid(self, sender):
+-        pid, uid, cmdline = \
++        pid, uid, gid, cmdline = \
+                 yield policykit1.get_proc_info_from_dbus_name(sender, self.bus)
+-        pktrans = PackageKitTransaction(pid, uid, cmdline, self.queue, sender)
++        pktrans = PackageKitTransaction(
++            pid, uid, gid, cmdline, self.queue, sender)
+         return_value(pktrans.tid)
+ 
+     # pylint: disable-msg=C0103,C0322
+@@ -531,7 +532,8 @@ class MergedTransaction(aptdaemon.core.Transaction):
+     def __init__(self, pktrans, role, queue, connect=True,
+                  bus=None, packages=None, kwargs=None):
+         aptdaemon.core.Transaction.__init__(self, pktrans.tid[1:], role, queue,
+-                                            pktrans.pid, pktrans.uid,
++                                            pktrans.pid,
++                                            pktrans.uid, pktrans.gid,
+                                             pktrans.cmdline, pktrans.sender,
+                                             connect, bus, packages, kwargs)
+         self.pktrans = pktrans
+@@ -617,7 +619,7 @@ class PackageKitTransaction(aptdaemon.core.DBusObject):
+ 
+     """Provides a PackageKit transaction object."""
+ 
+-    def __init__(self, pid, uid, cmdline, queue, sender,
++    def __init__(self, pid, uid, gid, cmdline, queue, sender,
+                  connect=True, bus=None):
+         pklog.info("Initializing PackageKit transaction")
+         bus_name = None
+@@ -643,6 +645,7 @@ class PackageKitTransaction(aptdaemon.core.DBusObject):
+         self._status = pk_enums.STATUS_SETUP
+         self._last_package = ""
+         self.uid = uid
++        self.gid = gid
+         self.pid = pid
+         self.cmdline = cmdline
+         self.role = pk_enums.ROLE_UNKNOWN
+diff --git a/aptdaemon/policykit1.py b/aptdaemon/policykit1.py
+index 3ecef85..ed45a41 100644
+--- a/aptdaemon/policykit1.py
++++ b/aptdaemon/policykit1.py
+@@ -151,11 +151,14 @@ def get_proc_info_from_dbus_name(dbus_name, bus=None):
+         bus = dbus.SystemBus()
+     pid = yield get_pid_from_dbus_name(dbus_name, bus)
+     with open("/proc/%s/status" % pid) as proc:
+-        values = [v for v in proc.readlines() if v.startswith("Uid:")]
++        lines = proc.readlines()
++        uid_values = [v for v in lines if v.startswith("Uid:")]
++        gid_values = [v for v in lines if v.startswith("Gid:")]
+     with open("/proc/%s/cmdline" % pid) as cmdline_file:
+         cmdline = cmdline_file.read()
+-    uid = int(values[0].split()[1])
+-    return_value((pid, uid, cmdline))
++    uid = int(uid_values[0].split()[1])
++    gid = int(gid_values[0].split()[1])
++    return_value((pid, uid, gid, cmdline))
+ 
+ 
+ # vim:ts=4:sw=4:et
+diff --git a/aptdaemon/worker.py b/aptdaemon/worker.py
+index c0f88f2..d41fc17 100644
+--- a/aptdaemon/worker.py
++++ b/aptdaemon/worker.py
+@@ -63,6 +63,25 @@ log = logging.getLogger("AptDaemon.Worker")
+ _ = lambda s: s
+ 
+ 
++@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)
++
++
+ class AptWorker(GObject.GObject):
+ 
+     """Worker which processes transactions from the queue."""
+@@ -588,7 +607,7 @@ password %s\n\n""" % (netloc_public + res.path, res.username, res.password))
+         log.info("Installing local package file: %s", path)
+         # Check if the dpkg can be installed at all
+         trans.status = STATUS_RESOLVING_DEP
+-        deb = self._check_deb_file(path, force, trans.uid)
++        deb = self._check_deb_file(path, force, trans.uid, trans.gid)
+         # Check for required changes and apply them before
+         (install, remove, unauth) = deb.required_changes
+         self._call_plugins("modify_cache_after")
+@@ -1173,7 +1192,7 @@ password %s\n\n""" % (netloc_public + res.path, res.username, res.password))
+ 
+         return depends, required_download, required_space, unauthenticated
+ 
+-    def _check_deb_file(self, path, force, uid):
++    def _check_deb_file(self, path, force, uid, gid):
+         """Perform some basic checks for the Debian package.
+ 
+         :param trans: The transaction instance.
+@@ -1182,8 +1201,15 @@ password %s\n\n""" % (netloc_public + res.path, res.username, res.password))
+         """
+         #FIXME: Unblock lintian call
+         path = path.encode("UTF-8")
+-        if not os.path.isfile(path):
+-            raise TransactionFailed(ERROR_UNREADABLE_PACKAGE_FILE, path)
++        # This code runs as root for simulate and simulate requires no
++        # authentication - so we need to ensure we do not leak information
++        # about files here (LP: #1449587, CVE-2015-1323)
++        #
++        # Note that the actual lintian run is also droping privs (real,
++        # not just seteuid)
++        with set_euid_egid(uid, gid):
++            if not os.path.isfile(path):
++                raise TransactionFailed(ERROR_UNREADABLE_PACKAGE_FILE, path)
+         if not force and os.path.isfile("/usr/bin/lintian"):
+             tags_dir = os.path.join(apt_pkg.config.find_dir("Dir"),
+                                     "usr", "share", "aptdaemon")
+@@ -1210,10 +1236,17 @@ password %s\n\n""" % (netloc_public + res.path, res.username, res.password))
+             fatal_args = ["/usr/bin/lintian", "--tags-from-file",
+                           tags_fatal_file, "--no-override", path]
+             for lintian_args in (nonfatal_args, fatal_args):
++                def _perm_drop_privs():
++                    try:
++                        os.setgroups([gid])
++                    except OSError:
++                        pass
++                    os.setgid(gid)
++                    os.setuid(uid)
+                 proc = subprocess.Popen(lintian_args,
+                                         stderr=subprocess.STDOUT,
+                                         stdout=subprocess.PIPE, close_fds=True,
+-                                        preexec_fn=lambda: os.setuid(uid))
++                                        preexec_fn=_perm_drop_privs)
+                 while proc.poll() is None:
+                     self._iterate_mainloop()
+                     time.sleep(0.05)
+diff --git a/tests/test_dbus_type.py b/tests/test_dbus_type.py
+index 17f1349..25038c6 100644
+--- a/tests/test_dbus_type.py
++++ b/tests/test_dbus_type.py
+@@ -137,7 +137,8 @@ class DBusTypeTest(test.AptDaemonTestCase):
+     def test_transaction_properties(self):
+         """Test object properties."""
+         trans = core.Transaction(None, enums.ROLE_REMOVE_PACKAGES, None,
+-                                 os.getpid(), os.getuid(), sys.argv[0],
++                                 os.getpid(), os.getuid(), os.getgid(),
++                                 sys.argv[0],
+                                  "org.debian.apt.test", bus=self.dbus)
+         proxy = self.dbus.get_object(core.APTDAEMON_DBUS_INTERFACE,
+                                      trans.tid)
+@@ -152,7 +153,8 @@ class DBusTypeTest(test.AptDaemonTestCase):
+     def test_transaction_signals(self):
+         """Test signal emittion."""
+         trans = core.Transaction(None, enums.ROLE_COMMIT_PACKAGES, None,
+-                                 os.getpid(), os.getuid(), sys.argv[0],
++                                 os.getpid(), os.getuid(), os.getgid(),
++                                 sys.argv[0],
+                                  "org.debian.apt.test", bus=self.dbus,
+                                  packages=[["silly-base"],[],[],[],[],[]])
+         proxy = self.dbus.get_object("org.debian.apt", trans.tid)
+diff --git a/tests/test_unicodedecoding.py b/tests/test_unicodedecoding.py
+index a1735cb..c64107b 100644
+--- a/tests/test_unicodedecoding.py
++++ b/tests/test_unicodedecoding.py
+@@ -49,7 +49,8 @@ class TestUnicodeDecoding(AptDaemonTestCase):
+         self.start_dbus_daemon()
+         self.dbus = dbus.bus.BusConnection(self.dbus_address)
+         self.trans = Transaction(None, "role-test", None,
+-                                 os.getpid(), os.getuid(), sys.argv[0],
++                                 os.getpid(), os.getuid(), os.getgid(),
++                                 sys.argv[0],
+                                  "org.debian.apt.test", bus=self.dbus)
+ 
+     def test(self):
+diff --git a/tests/test_worker.py b/tests/test_worker.py
+index 46b2df8..2ac1f59 100644
+--- a/tests/test_worker.py
++++ b/tests/test_worker.py
+@@ -78,7 +78,8 @@ class WorkerTestCase(aptdaemon.test.AptDaemonTestCase):
+         self.chroot.add_repository("/does/not/exist", copy_list=False)
+         # Only update the repository from the working snippet
+         trans = Transaction(None, enums.ROLE_UPDATE_CACHE,
+-                            self.queue, os.getpid(), os.getuid(), sys.argv[0],
++                            self.queue, os.getpid(), os.getuid(),
++                            os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", bus=self.dbus,
+                             kwargs={"sources_list": "test.list"})
+         self.worker.simulate(trans)
+@@ -100,8 +101,8 @@ class WorkerTestCase(aptdaemon.test.AptDaemonTestCase):
+                                                  "silly-base_0.1-0_all.deb"))
+         # Install the package
+         trans = Transaction(None, enums.ROLE_UPGRADE_SYSTEM,
+-                            self.queue, os.getpid(),
+-                            os.getuid(), sys.argv[0],
++                            self.queue, os.getpid(), os.getgid(),
++                            os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", bus=self.dbus,
+                             kwargs={"safe_mode": False})
+         self.worker.simulate(trans)
+@@ -131,7 +132,7 @@ class WorkerTestCase(aptdaemon.test.AptDaemonTestCase):
+         self.chroot.add_test_repository(copy_sig=False)
+         # Install the package
+         trans = Transaction(None, enums.ROLE_INSTALL_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", bus=self.dbus,
+                             packages=[["silly-base"],[],[],[],[], []])
+         self.worker.simulate(trans)
+@@ -145,7 +146,7 @@ class WorkerTestCase(aptdaemon.test.AptDaemonTestCase):
+ 
+         # Allow installation of unauthenticated packages
+         trans = Transaction(None, enums.ROLE_INSTALL_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", bus=self.dbus,
+                             packages=[["silly-base"],[],[],[],[], []])
+         trans.allow_unauthenticated = True
+@@ -165,7 +166,7 @@ class WorkerTestCase(aptdaemon.test.AptDaemonTestCase):
+         self.chroot.add_test_repository()
+         # Install the package
+         trans = Transaction(None, enums.ROLE_INSTALL_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", bus=self.dbus,
+                             packages=[["silly-depend-base"],[],[],[],[], []])
+         self.worker.simulate(trans)
+@@ -193,7 +194,7 @@ class WorkerTestCase(aptdaemon.test.AptDaemonTestCase):
+ Architecture: all
+ Auto-Installed: 1""")
+         trans = Transaction(None, enums.ROLE_REMOVE_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", bus=self.dbus,
+                             packages=[[],[],["silly-depend-base"],[],[],[]])
+         trans.remove_obsoleted_depends = True
+@@ -217,7 +218,7 @@ Auto-Installed: 1""")
+                     "silly-depend-base_0.1-0_all.deb"]:
+             self.chroot.install_debfile(os.path.join(REPO_PATH, pkg))
+         trans = Transaction(None, enums.ROLE_REMOVE_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", bus=self.dbus,
+                             packages=[[],[],["silly-base"],[],[],[]])
+         self.worker.simulate(trans)
+@@ -238,7 +239,7 @@ Auto-Installed: 1""")
+             pass
+         # Don't allow to remove essential packages
+         trans = Transaction(None, enums.ROLE_REMOVE_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", bus=self.dbus,
+                             packages=[[],[],["silly-essential"],[],[],[]])
+         self.worker.run(trans)
+@@ -255,7 +256,7 @@ Auto-Installed: 1""")
+         pkg = os.path.join(REPO_PATH, "silly-base_0.1-0update1_all.deb")
+         self.chroot.install_debfile(pkg)
+         trans = Transaction(None, enums.ROLE_COMMIT_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", bus=self.dbus,
+                             packages=[[],[],[],[],[],["silly-base=0.1-0"]])
+         self.worker.run(trans)
+@@ -272,7 +273,7 @@ Auto-Installed: 1""")
+         for pkg in ["silly-base_0.1-0_all.deb", "silly-config_0.1-0_all.deb"]:
+             self.chroot.install_debfile(os.path.join(REPO_PATH, pkg))
+         trans = Transaction(None, enums.ROLE_REMOVE_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", bus=self.dbus,
+                             packages=[[],[],[],["silly-config"],[],[]])
+         self.worker.run(trans)
+@@ -297,7 +298,7 @@ Auto-Installed: 1""")
+         pkg = os.path.join(
+             REPO_PATH, "silly-depend-base-lintian-broken_0.1-0_all.deb")
+         trans = Transaction(None, enums.ROLE_INSTALL_FILE, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", bus=self.dbus,
+                             kwargs={"path": os.path.join(REPO_PATH, pkg),
+                                     "force": False})
+@@ -328,7 +329,7 @@ Auto-Installed: 1""")
+         """
+         pkg = os.path.join(REPO_PATH, "silly-base_0.1-0_all.deb")
+         trans = Transaction(None, enums.ROLE_INSTALL_FILE, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", bus=self.dbus,
+                             kwargs={"path": os.path.join(REPO_PATH, pkg),
+                                     "force": True})
+@@ -349,7 +350,7 @@ Auto-Installed: 1""")
+         for pkg in ["silly-base_0.1-0_all.deb", "silly-broken_0.1-0_all.deb"]:
+             self.chroot.install_debfile(os.path.join(REPO_PATH, pkg), True)
+         trans = Transaction(None, enums.ROLE_FIX_BROKEN_DEPENDS, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", bus=self.dbus)
+         self.worker.simulate(trans)
+         self.loop.run()
+@@ -390,7 +391,7 @@ Auto-Installed: 1""")
+             return license_key, license_path
+         self.chroot.add_test_repository()
+         trans = Transaction(None, enums.ROLE_ADD_LICENSE_KEY, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test",
+                             kwargs={"pkg_name": "silly-license",
+                                     "json_token": "lalelu",
diff --git a/debian/patches/series b/debian/patches/series
index b359dca..a081711 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +1,2 @@
 aptd-import-from-keyserver.patch
+CVE-2015-1323.patch
diff --git a/debian/changelog b/debian/changelog
index 38e82be..b1f6453 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+aptdaemon (1.1.1-4+deb8u1) jessie-security; urgency=medium
+
+  * Non maintainer upload
+  * Add CVE-2015-1323.patch to address CVE-2015-1323 - taken from
+    1.1.1-1ubuntu5.2 (Closes: #789162)
+
+ -- Guido Günther <agx@sigxcpu.org>  Mon, 29 Feb 2016 21:13:01 +0100
+
 aptdaemon (1.1.1-4) unstable; urgency=medium
 
   * Merge all changes from Ubuntu trusty.
diff --git a/debian/patches/CVE-2015-1323.patch b/debian/patches/CVE-2015-1323.patch
new file mode 100644
index 0000000..bc6a963
--- /dev/null
+++ b/debian/patches/CVE-2015-1323.patch
@@ -0,0 +1,377 @@
+From: =?utf-8?q?Guido_G=C3=BCnther?= <agx@sigxcpu.org>
+Date: Sun, 28 Feb 2016 19:55:02 +0100
+Subject: CVE-2015-1323
+
+---
+ aptdaemon/core.py                             | 10 +++++---
+ aptdaemon/pkcompat.py                         | 10 +++++---
+ aptdaemon/policykit1.py                       |  9 ++++---
+ aptdaemon/progress.py                         |  5 ++++
+ aptdaemon/worker.py                           | 31 ++++++++++++++++++++++--
+ tests/_test_py2_string_handling.py            |  3 ++-
+ tests/test_high_trust_repository_whitelist.py |  4 +--
+ tests/test_worker.py                          | 35 ++++++++++++++-------------
+ 8 files changed, 74 insertions(+), 33 deletions(-)
+
+diff --git a/aptdaemon/core.py b/aptdaemon/core.py
+index 9e1e9d3..35f40d7 100644
+--- a/aptdaemon/core.py
++++ b/aptdaemon/core.py
+@@ -342,7 +342,7 @@ class Transaction(DBusObject):
+                            "DebconfSocket", "MetaData", "Locale",
+                            "RemoveObsoleteDepends")
+ 
+-    def __init__(self, tid, role, queue, pid, uid, cmdline, sender,
++    def __init__(self, tid, role, queue, pid, uid, gid, cmdline, sender,
+                  connect=True, bus=None, packages=None, kwargs=None):
+         """Initialize a new Transaction instance.
+ 
+@@ -378,6 +378,7 @@ class Transaction(DBusObject):
+             kwargs = {}
+         self.queue = queue
+         self.uid = uid
++        self.gid = gid
+         self.locale = dbus.String("")
+         self.allow_unauthenticated = dbus.Boolean(False)
+         self.remove_obsoleted_depends = dbus.Boolean(False)
+@@ -1538,11 +1539,12 @@ class AptDaemon(DBusObject):
+     @inline_callbacks
+     def _create_trans(self, role, sender, packages=None, kwargs=None):
+         """Helper method which returns the tid of a new transaction."""
+-        pid, uid, cmdline = (
++        pid, uid, gid, cmdline = (
+             yield policykit1.get_proc_info_from_dbus_name(sender, self.bus))
+         tid = uuid.uuid4().hex
+-        trans = Transaction(tid, role, self.queue, pid, uid, cmdline, sender,
+-                            packages=packages, kwargs=kwargs, bus=self.bus)
++        trans = Transaction(
++            tid, role, self.queue, pid, uid, gid, cmdline, sender,
++            packages=packages, kwargs=kwargs, bus=self.bus)
+         self.queue.limbo[trans.tid] = trans
+         return_value(trans.tid)
+ 
+diff --git a/aptdaemon/pkcompat.py b/aptdaemon/pkcompat.py
+index cc05415..da99a32 100644
+--- a/aptdaemon/pkcompat.py
++++ b/aptdaemon/pkcompat.py
+@@ -469,9 +469,10 @@ class PackageKit(core.DBusObject):
+ 
+     @inline_callbacks
+     def _create_transaction(self, sender):
+-        pid, uid, cmdline = yield policykit1.get_proc_info_from_dbus_name(
++        pid, uid, gid, cmdline = yield policykit1.get_proc_info_from_dbus_name(
+             sender, self.bus)
+-        pktrans = PackageKitTransaction(pid, uid, cmdline, self.queue, sender)
++        pktrans = PackageKitTransaction(
++            pid, uid, gid, cmdline, self.queue, sender)
+         return_value(pktrans.tid)
+ 
+     # pylint: disable-msg=C0103,C0322
+@@ -602,7 +603,7 @@ class MergedTransaction(core.Transaction):
+     def __init__(self, pktrans, role, queue, connect=True,
+                  bus=None, packages=None, kwargs=None):
+         core.Transaction.__init__(self, pktrans.tid[1:], role, queue,
+-                                  pktrans.pid, pktrans.uid,
++                                  pktrans.pid, pktrans.uid, pktrans.gid,
+                                   pktrans.cmdline, pktrans.sender,
+                                   connect, bus, packages, kwargs)
+         self.pktrans = pktrans
+@@ -758,7 +759,7 @@ class PackageKitTransaction(core.DBusObject):
+ 
+     """Provides a PackageKit transaction object."""
+ 
+-    def __init__(self, pid, uid, cmdline, queue, sender,
++    def __init__(self, pid, uid, gid, cmdline, queue, sender,
+                  connect=True, bus=None):
+         pklog.info("Initializing PackageKit transaction")
+         bus_name = None
+@@ -784,6 +785,7 @@ class PackageKitTransaction(core.DBusObject):
+         self._status = pk.StatusEnum.SETUP
+         self._last_package = ""
+         self.uid = dbus.UInt32(uid)
++        self.gid = dbus.UInt32(gid)
+         self.pid = pid
+         self.cmdline = cmdline
+         self.role = pk.RoleEnum.UNKNOWN
+diff --git a/aptdaemon/policykit1.py b/aptdaemon/policykit1.py
+index 6a21875..9a15513 100644
+--- a/aptdaemon/policykit1.py
++++ b/aptdaemon/policykit1.py
+@@ -161,12 +161,15 @@ def get_proc_info_from_dbus_name(dbus_name, bus=None):
+         bus = dbus.SystemBus()
+     pid = yield get_pid_from_dbus_name(dbus_name, bus)
+     with open("/proc/%s/status" % pid) as proc:
+-        values = [v for v in proc.readlines() if v.startswith("Uid:")]
++        lines = proc.readlines()
++        uid_values = [v for v in lines if v.startswith("Uid:")]
++        gid_values = [v for v in lines if v.startswith("Gid:")]
+     # instead of ", encoding='utf8'" we use the "rb"/decode() here for
+     # py2 compatibility
+     with open("/proc/%s/cmdline" % pid, "rb") as cmdline_file:
+         cmdline = cmdline_file.read().decode("utf-8")
+-    uid = int(values[0].split()[1])
+-    return_value((pid, uid, cmdline))
++    uid = int(uid_values[0].split()[1])
++    gid = int(gid_values[0].split()[1])
++    return_value((pid, uid, gid, cmdline))
+ 
+ # vim:ts=4:sw=4:et
+diff --git a/aptdaemon/progress.py b/aptdaemon/progress.py
+index 73cb411..f15b7d6 100644
+--- a/aptdaemon/progress.py
++++ b/aptdaemon/progress.py
+@@ -628,6 +628,11 @@ class DaemonLintianProgress(DaemonForkProgress):
+ 
+     def _child(self, path):
+         # Avoid running lintian as root
++        try:
++            os.setgroups([self.transaction.gid])
++        except OSError:
++            pass
++        os.setgid(self.transaction.gid)
+         os.setuid(self.transaction.uid)
+ 
+         if platform.dist()[1] == "debian":
+diff --git a/aptdaemon/worker.py b/aptdaemon/worker.py
+index 85e73cc..e9272d3 100644
+--- a/aptdaemon/worker.py
++++ b/aptdaemon/worker.py
+@@ -76,6 +76,25 @@ log = logging.getLogger("AptDaemon.Worker")
+ _ = lambda s: s
+ 
+ 
++@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
+@@ -1329,8 +1348,16 @@ class AptWorker(GObject.GObject):
+ 
+         :returns: An apt.debfile.Debfile instance.
+         """
+-        if not os.path.isfile(path):
+-            raise TransactionFailed(ERROR_UNREADABLE_PACKAGE_FILE, path)
++        # This code runs as root for simulate and simulate requires no
++        # authentication - so we need to ensure we do not leak information
++        # about files here (LP: #1449587, CVE-2015-1323)
++        #
++        # Note that the actual lintian run is also droping privs (real,
++        # not just seteuid)
++        with set_euid_egid(trans.uid, trans.gid):
++            if not os.path.isfile(path):
++                raise TransactionFailed(ERROR_UNREADABLE_PACKAGE_FILE, path)
++
+         if not force and os.path.isfile("/usr/bin/lintian"):
+             with DaemonLintianProgress(trans) as progress:
+                 progress.run(path)
+diff --git a/tests/_test_py2_string_handling.py b/tests/_test_py2_string_handling.py
+index 1a56b9b..86709fb 100644
+--- a/tests/_test_py2_string_handling.py
++++ b/tests/_test_py2_string_handling.py
+@@ -49,7 +49,8 @@ class TestUnicodeDecoding(AptDaemonTestCase):
+         self.start_dbus_daemon()
+         self.dbus = dbus.bus.BusConnection(self.dbus_address)
+         self.trans = Transaction(None, "role-test", None,
+-                                 os.getpid(), os.getuid(), sys.argv[0],
++                                 os.getpid(), os.getuid(), os.getgid(),
++                                 sys.argv[0],
+                                  "org.debian.apt.test", bus=self.dbus)
+ 
+     def test(self):
+diff --git a/tests/test_high_trust_repository_whitelist.py b/tests/test_high_trust_repository_whitelist.py
+index ca2ebd4..fac471f 100644
+--- a/tests/test_high_trust_repository_whitelist.py
++++ b/tests/test_high_trust_repository_whitelist.py
+@@ -116,7 +116,7 @@ class HighTrustRepositoryTestCase(BaseHighTrustTestCase):
+             ("Ubuntu", "", "silly.*"))
+         # a high-trust whitelisted pkg and a non-whitelisted one
+         trans = Transaction(None, enums.ROLE_INSTALL_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             packages=[["silly-base", "other-pkg"], [], [], [],
+                                       [], []])
+@@ -128,7 +128,7 @@ class HighTrustRepositoryTestCase(BaseHighTrustTestCase):
+                 trans, self.worker._high_trust_repositories))
+         # whitelisted only
+         trans = Transaction(None, enums.ROLE_INSTALL_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             packages=[["silly-base"], [], [], [], [], []])
+         self.worker.simulate(trans)
+diff --git a/tests/test_worker.py b/tests/test_worker.py
+index 41a8caa..6cf7717 100644
+--- a/tests/test_worker.py
++++ b/tests/test_worker.py
+@@ -77,7 +77,8 @@ class WorkerTestCase(aptdaemon.test.AptDaemonTestCase):
+         self.chroot.add_repository("/does/not/exist", copy_list=False)
+         # Only update the repository from the working snippet
+         trans = Transaction(None, enums.ROLE_UPDATE_CACHE,
+-                            self.queue, os.getpid(), os.getuid(), sys.argv[0],
++                            self.queue, os.getpid(), os.getuid(),
++                            os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             kwargs={"sources_list": "test.list"})
+         self.worker.simulate(trans)
+@@ -99,7 +100,7 @@ class WorkerTestCase(aptdaemon.test.AptDaemonTestCase):
+                                                  "silly-base_0.1-0_all.deb"))
+         # Install the package
+         trans = Transaction(None, enums.ROLE_UPGRADE_SYSTEM,
+-                            self.queue, os.getpid(),
++                            self.queue, os.getpid(), os.getgid(),
+                             os.getuid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             kwargs={"safe_mode": False})
+@@ -130,7 +131,7 @@ class WorkerTestCase(aptdaemon.test.AptDaemonTestCase):
+         self.chroot.add_test_repository(copy_sig=False)
+         # Install the package
+         trans = Transaction(None, enums.ROLE_INSTALL_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             packages=[["silly-base"], [], [], [], [], []])
+         self.worker.simulate(trans)
+@@ -144,7 +145,7 @@ class WorkerTestCase(aptdaemon.test.AptDaemonTestCase):
+ 
+         # Allow installation of unauthenticated packages
+         trans = Transaction(None, enums.ROLE_INSTALL_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             packages=[["silly-base"], [], [], [], [], []])
+         trans.allow_unauthenticated = True
+@@ -164,7 +165,7 @@ class WorkerTestCase(aptdaemon.test.AptDaemonTestCase):
+         self.chroot.add_test_repository()
+         # Install the package
+         trans = Transaction(None, enums.ROLE_INSTALL_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             packages=[["silly-depend-base"], [], [], [],
+                                       [], []])
+@@ -193,7 +194,7 @@ class WorkerTestCase(aptdaemon.test.AptDaemonTestCase):
+ Architecture: all
+ Auto-Installed: 1""")
+         trans = Transaction(None, enums.ROLE_REMOVE_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             packages=[[], [], ["silly-depend-base"], [],
+                                       [], []])
+@@ -219,7 +220,7 @@ Auto-Installed: 1""")
+                     "silly-depend-base_0.1-0_all.deb"]:
+             self.chroot.install_debfile(os.path.join(REPO_PATH, pkg))
+         trans = Transaction(None, enums.ROLE_REMOVE_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             packages=[[], [], ["silly-base"], [], [], []])
+         self.worker.simulate(trans)
+@@ -240,7 +241,7 @@ Auto-Installed: 1""")
+             pass
+         # Don't allow to remove essential packages
+         trans = Transaction(None, enums.ROLE_REMOVE_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             packages=[[], [], ["silly-essential"], [], [], []])
+         self.worker.run(trans)
+@@ -263,7 +264,7 @@ Auto-Installed: 1""")
+ Architecture: all
+ Auto-Installed: 1""")
+         trans = Transaction(None, enums.ROLE_COMMIT_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             packages=[[], [], [], [],
+                                       ["silly-base=0.1-0update1"], []])
+@@ -283,7 +284,7 @@ Auto-Installed: 1""")
+         pkg = os.path.join(REPO_PATH, "silly-base_0.1-0update1_all.deb")
+         self.chroot.install_debfile(pkg)
+         trans = Transaction(None, enums.ROLE_COMMIT_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             packages=[[], [], [], [], [],
+                                       ["silly-base=0.1-0"]])
+@@ -301,7 +302,7 @@ Auto-Installed: 1""")
+         for pkg in ["silly-base_0.1-0_all.deb", "silly-config_0.1-0_all.deb"]:
+             self.chroot.install_debfile(os.path.join(REPO_PATH, pkg))
+         trans = Transaction(None, enums.ROLE_REMOVE_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             packages=[[], [], [], ["silly-config"], [], []])
+         self.worker.run(trans)
+@@ -324,7 +325,7 @@ Auto-Installed: 1""")
+         pkg = os.path.join(REPO_PATH,
+                            "silly-depend-base-lintian-broken_0.1-0_all.deb")
+         trans = Transaction(None, enums.ROLE_INSTALL_FILE, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             kwargs={"path": os.path.join(REPO_PATH, pkg),
+                                     "force": False})
+@@ -359,7 +360,7 @@ Auto-Installed: 1""")
+         self.chroot.install_debfile(os.path.join(REPO_PATH, pkg_base))
+         pkg = os.path.join(REPO_PATH, "silly-bully_0.1-0_all.deb")
+         trans = Transaction(None, enums.ROLE_INSTALL_FILE, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             kwargs={"path": os.path.join(REPO_PATH, pkg),
+                                     "force": True})
+@@ -379,7 +380,7 @@ Auto-Installed: 1""")
+         """
+         pkg = os.path.join(REPO_PATH, "silly-base_0.1-0_all.deb")
+         trans = Transaction(None, enums.ROLE_INSTALL_FILE, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False,
+                             kwargs={"path": os.path.join(REPO_PATH, pkg),
+                                     "force": True})
+@@ -400,7 +401,7 @@ Auto-Installed: 1""")
+         for pkg in ["silly-base_0.1-0_all.deb", "silly-broken_0.1-0_all.deb"]:
+             self.chroot.install_debfile(os.path.join(REPO_PATH, pkg), True)
+         trans = Transaction(None, enums.ROLE_FIX_BROKEN_DEPENDS, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test", connect=False)
+         self.worker.simulate(trans)
+         self.loop.run()
+@@ -420,7 +421,7 @@ Auto-Installed: 1""")
+         """
+         self.chroot.add_test_repository()
+         trans = Transaction(None, enums.ROLE_COMMIT_PACKAGES, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test",
+                             packages=[["silly-broken"], [], [], [], [], []],
+                             connect=False)
+@@ -459,7 +460,7 @@ Auto-Installed: 1""")
+ 
+         self.chroot.add_test_repository()
+         trans = Transaction(None, enums.ROLE_ADD_LICENSE_KEY, self.queue,
+-                            os.getpid(), os.getuid(), sys.argv[0],
++                            os.getpid(), os.getuid(), os.getgid(), sys.argv[0],
+                             "org.debian.apt.test",
+                             kwargs={"pkg_name": "silly-license",
+                                     "json_token": "lalelu",
diff --git a/debian/patches/series b/debian/patches/series
index b1b680e..9eb67c8 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -9,3 +9,4 @@ lp1266844.patch
 fix-configparser.patch
 py3_inheritable.patch
 upstream-include-pkg-version.patch
+CVE-2015-1323.patch

Reply to: