Blame SOURCES/0313-bus-message-introduce-two-kinds-of-references-to-bus.patch

ddca0b
From bc2d7df4fc21e9e54413169d5aad21616314d65e Mon Sep 17 00:00:00 2001
a3e2b5
From: Lennart Poettering <lennart@poettering.net>
a3e2b5
Date: Thu, 17 Jan 2019 18:18:54 +0100
a3e2b5
Subject: [PATCH] bus-message: introduce two kinds of references to bus
a3e2b5
 messages
a3e2b5
a3e2b5
Before this commit bus messages had a single reference count: when it
a3e2b5
reached zero the message would be freed. This simple approach meant a
a3e2b5
cyclic dependency was typically seen: a message that was enqueued in a
a3e2b5
bus connection object would reference the bus connection object but also
a3e2b5
itself be referenced by the bus connection object. So far out strategy
a3e2b5
to avoid cases like this was: make sure to process the bus connection
a3e2b5
regularly so that messages don#t stay queued, and at exit flush/close
a3e2b5
the connection so that the message queued would be emptied, and thus the
a3e2b5
cyclic dependencies resolved. Im many cases this isn't done properly
a3e2b5
however.
a3e2b5
a3e2b5
With this change, let's address the issue more systematically: let's
a3e2b5
break the reference cycle. Specifically, there are now two types of
a3e2b5
references to a bus message:
a3e2b5
a3e2b5
1. A regular one, which keeps both the message and the bus object it is
a3e2b5
   associated with pinned.
a3e2b5
a3e2b5
2. A "queue" reference, which is weaker: it pins the message, but not
a3e2b5
   the bus object it is associated with.
a3e2b5
a3e2b5
The idea is then that regular user handling uses regular references, but
a3e2b5
when a message is enqueued on its connection, then this takes a "queue"
a3e2b5
reference instead. This then means that a queued message doesn't imply
a3e2b5
the connection itself remains pinned, only regular references to the
a3e2b5
connection or a message associated with it do. Thus, if we end up in the
a3e2b5
situation where a user allocates a bus and a message and enqueues the
a3e2b5
latter in the former and drops all refs to both, then this will detect
a3e2b5
this case and free both.
a3e2b5
a3e2b5
Note that this scheme isn't perfect, it only covers references between
a3e2b5
messages and the busses they are associated with. If OTOH a bus message
a3e2b5
is enqueued on a different bus than it is associated with cyclic deps
a3e2b5
cannot be recognized with this simple algorithm, and thus if you enqueue
a3e2b5
a message associated with a bus A on a bus B, and another message
a3e2b5
associated with bus B on a bus A, a cyclic ref will be in effect and not
a3e2b5
be discovered. However, given that this is an exotic case (though one
a3e2b5
that happens, consider systemd-bus-stdio-bridge), it should be OK not to
a3e2b5
cover with this, and people have to explicit flush all queues on exit in
a3e2b5
that case.
a3e2b5
a3e2b5
Note that this commit only establishes the separate reference counters
a3e2b5
per message. A follow-up commit will start making use of this from the
a3e2b5
bus connection object.
a3e2b5
a3e2b5
(cherry picked from commit 1b3f9dd759ca0ea215e7b89f8ce66d1b724497b9)
a3e2b5
Related: CVE-2020-1712
a3e2b5
---
a3e2b5
 src/libsystemd/sd-bus/bus-message.c | 60 ++++++++++++++++++++++++++---
a3e2b5
 src/libsystemd/sd-bus/bus-message.h | 14 ++++++-
a3e2b5
 2 files changed, 68 insertions(+), 6 deletions(-)
a3e2b5
a3e2b5
diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c
ddca0b
index 306b6d6816..7fe8929f82 100644
a3e2b5
--- a/src/libsystemd/sd-bus/bus-message.c
a3e2b5
+++ b/src/libsystemd/sd-bus/bus-message.c
a3e2b5
@@ -120,7 +120,8 @@ static sd_bus_message* message_free(sd_bus_message *m) {
a3e2b5
 
a3e2b5
         message_reset_parts(m);
a3e2b5
 
a3e2b5
-        sd_bus_unref(m->bus);
a3e2b5
+        /* Note that we don't unref m->bus here. That's already done by sd_bus_message_unref() as each user
a3e2b5
+         * reference to the bus message also is considered a reference to the bus connection itself. */
a3e2b5
 
a3e2b5
         if (m->free_fds) {
a3e2b5
                 close_many(m->fds, m->n_fds);
a3e2b5
@@ -893,27 +894,76 @@ int bus_message_new_synthetic_error(
a3e2b5
 }
a3e2b5
 
a3e2b5
 _public_ sd_bus_message* sd_bus_message_ref(sd_bus_message *m) {
a3e2b5
-
a3e2b5
         if (!m)
a3e2b5
                 return NULL;
a3e2b5
 
a3e2b5
-        assert(m->n_ref > 0);
a3e2b5
+        /* We are fine if this message so far was either explicitly reffed or not reffed but queued into at
a3e2b5
+         * least one bus connection object. */
a3e2b5
+        assert(m->n_ref > 0 || m->n_queued > 0);
a3e2b5
+
a3e2b5
         m->n_ref++;
a3e2b5
 
a3e2b5
+        /* Each user reference to a bus message shall also be considered a ref on the bus */
a3e2b5
+        sd_bus_ref(m->bus);
a3e2b5
         return m;
a3e2b5
 }
a3e2b5
 
a3e2b5
 _public_ sd_bus_message* sd_bus_message_unref(sd_bus_message *m) {
a3e2b5
-
a3e2b5
         if (!m)
a3e2b5
                 return NULL;
a3e2b5
 
a3e2b5
         assert(m->n_ref > 0);
a3e2b5
+
a3e2b5
+        sd_bus_unref(m->bus); /* Each regular ref is also a ref on the bus connection. Let's hence drop it
a3e2b5
+                               * here. Note we have to do this before decrementing our own n_ref here, since
a3e2b5
+                               * otherwise, if this message is currently queued sd_bus_unref() might call
a3e2b5
+                               * bus_message_unref_queued() for this which might then destroy the message
a3e2b5
+                               * while we are still processing it. */
a3e2b5
         m->n_ref--;
a3e2b5
 
a3e2b5
-        if (m->n_ref > 0)
a3e2b5
+        if (m->n_ref > 0 || m->n_queued > 0)
a3e2b5
                 return NULL;
a3e2b5
 
a3e2b5
+        /* Unset the bus field if neither the user has a reference nor this message is queued. We are careful
a3e2b5
+         * to reset the field only after the last reference to the bus is dropped, after all we might keep
a3e2b5
+         * multiple references to the bus, once for each reference kept on outselves. */
a3e2b5
+        m->bus = NULL;
a3e2b5
+
a3e2b5
+        return message_free(m);
a3e2b5
+}
a3e2b5
+
a3e2b5
+sd_bus_message* bus_message_ref_queued(sd_bus_message *m, sd_bus *bus) {
a3e2b5
+        if (!m)
a3e2b5
+                return NULL;
a3e2b5
+
a3e2b5
+        /* If this is a different bus than the message is associated with, then implicitly turn this into a
a3e2b5
+         * regular reference. This means that you can create a memory leak by enqueuing a message generated
a3e2b5
+         * on one bus onto another at the same time as enqueueing a message from the second one on the first,
a3e2b5
+         * as we'll not detect the cyclic references there. */
a3e2b5
+        if (bus != m->bus)
a3e2b5
+                return sd_bus_message_ref(m);
a3e2b5
+
a3e2b5
+        assert(m->n_ref > 0 || m->n_queued > 0);
a3e2b5
+        m->n_queued++;
a3e2b5
+
a3e2b5
+        return m;
a3e2b5
+}
a3e2b5
+
a3e2b5
+sd_bus_message* bus_message_unref_queued(sd_bus_message *m, sd_bus *bus) {
a3e2b5
+        if (!m)
a3e2b5
+                return NULL;
a3e2b5
+
a3e2b5
+        if (bus != m->bus)
a3e2b5
+                return sd_bus_message_unref(m);
a3e2b5
+
a3e2b5
+        assert(m->n_queued > 0);
a3e2b5
+        m->n_queued--;
a3e2b5
+
a3e2b5
+        if (m->n_ref > 0 || m->n_queued > 0)
a3e2b5
+                return NULL;
a3e2b5
+
a3e2b5
+        m->bus = NULL;
a3e2b5
+
a3e2b5
         return message_free(m);
a3e2b5
 }
a3e2b5
 
a3e2b5
diff --git a/src/libsystemd/sd-bus/bus-message.h b/src/libsystemd/sd-bus/bus-message.h
a3e2b5
index 97f6060e30..ded88005e2 100644
a3e2b5
--- a/src/libsystemd/sd-bus/bus-message.h
a3e2b5
+++ b/src/libsystemd/sd-bus/bus-message.h
a3e2b5
@@ -51,7 +51,16 @@ struct bus_body_part {
a3e2b5
 };
a3e2b5
 
a3e2b5
 struct sd_bus_message {
a3e2b5
-        unsigned n_ref;
a3e2b5
+        /* Caveat: a message can be referenced in two different ways: the main (user-facing) way will also
a3e2b5
+         * pin the bus connection object the message is associated with. The secondary way ("queued") is used
a3e2b5
+         * when a message is in the read or write queues of the bus connection object, which will not pin the
a3e2b5
+         * bus connection object. This is necessary so that we don't have to have a pair of cyclic references
a3e2b5
+         * between a message that is queued and its connection: as soon as a message is only referenced by
a3e2b5
+         * the connection (by means of being queued) and the connection itself has no other references it
a3e2b5
+         * will be freed. */
a3e2b5
+
a3e2b5
+        unsigned n_ref;     /* Counter of references that pin the connection */
a3e2b5
+        unsigned n_queued;  /* Counter of references that do not pin the connection */
a3e2b5
 
a3e2b5
         sd_bus *bus;
a3e2b5
 
a3e2b5
@@ -216,3 +225,6 @@ int bus_message_append_sender(sd_bus_message *m, const char *sender);
a3e2b5
 
a3e2b5
 void bus_message_set_sender_driver(sd_bus *bus, sd_bus_message *m);
a3e2b5
 void bus_message_set_sender_local(sd_bus *bus, sd_bus_message *m);
a3e2b5
+
a3e2b5
+sd_bus_message* bus_message_ref_queued(sd_bus_message *m, sd_bus *bus);
a3e2b5
+sd_bus_message* bus_message_unref_queued(sd_bus_message *m, sd_bus *bus);