4ff8bf
From 94bacc6955e563a7e698e53151a75323279a9f45 Mon Sep 17 00:00:00 2001
4ff8bf
From: Simon McVittie <smcv@collabora.com>
4ff8bf
Date: Mon, 11 Mar 2019 09:03:39 +0000
4ff8bf
Subject: [PATCH] bus: Try to raise soft fd limit to match hard limit
4ff8bf
4ff8bf
Linux systems have traditionally set the soft limit to 1024 and the hard
4ff8bf
limit to 4096. Recent versions of systemd keep the soft fd limit at
4ff8bf
1024 to avoid breaking programs that still use select(), but raise the
4ff8bf
hard limit to 512*1024, while in recent Debian versions a complicated
4ff8bf
interaction between components gives a soft limit of 1024 and a hard
4ff8bf
limit of 1024*1024. If we can, we might as well elevate our soft limit
4ff8bf
to match the hard limit, minimizing the chance that we will run out of
4ff8bf
file descriptor slots.
4ff8bf
4ff8bf
Unlike the previous code to raise the hard and soft limits to at least
4ff8bf
65536, we do this even if we don't have privileges: privileges are
4ff8bf
unnecessary to raise the soft limit up to the hard limit.
4ff8bf
4ff8bf
If we *do* have privileges, we also continue to raise the hard and soft
4ff8bf
limits to at least 65536 if they weren't already that high, making
4ff8bf
it harder to carry out a denial of service attack on the system bus on
4ff8bf
systems that use the traditional limit (CVE-2014-7824).
4ff8bf
4ff8bf
As was previously the case on the system bus, we'll drop the limits back
4ff8bf
to our initial limits before we execute a subprocess for traditional
4ff8bf
(non-systemd) activation, if enabled.
4ff8bf
4ff8bf
systemd activation doesn't involve us starting subprocesses at all,
4ff8bf
so in both cases activated services will still inherit the same limits
4ff8bf
they did previously.
4ff8bf
4ff8bf
This change also fixes a bug when the hard limit is very large but
4ff8bf
the soft limit is not, for example seen as a regression when upgrading
4ff8bf
to systemd >= 240 (Debian #928877). In such environments, dbus-daemon
4ff8bf
would previously have changed its fd limit to 64K soft/64K hard. Because
4ff8bf
this hard limit is less than its original hard limit, it was unable to
4ff8bf
restore its original hard limit as intended when carrying out traditional
4ff8bf
activation, leaving activated subprocesses with unintended limits (while
4ff8bf
logging a warning).
4ff8bf
4ff8bf
Reviewed-by: Lennart Poettering <lennart@poettering.net>
4ff8bf
[smcv: Correct a comment based on Lennart's review, reword commit message]
4ff8bf
Signed-off-by: Simon McVittie <smcv@collabora.com>
4ff8bf
(cherry picked from commit 7eacbfece70f16bb54d0f3ac51f87ae398759ef5)
4ff8bf
[smcv: Mention that this also fixes Debian #928877]
4ff8bf
---
4ff8bf
 bus/bus.c                     |  8 ++---
4ff8bf
 dbus/dbus-sysdeps-util-unix.c | 64 +++++++++++++++++++++--------------
4ff8bf
 dbus/dbus-sysdeps-util-win.c  |  3 +-
4ff8bf
 dbus/dbus-sysdeps.h           |  3 +-
4ff8bf
 4 files changed, 44 insertions(+), 34 deletions(-)
4ff8bf
4ff8bf
diff --git a/bus/bus.c b/bus/bus.c
4ff8bf
index 30ce4e10..2ad8e789 100644
4ff8bf
--- a/bus/bus.c
4ff8bf
+++ b/bus/bus.c
4ff8bf
@@ -693,11 +693,11 @@ raise_file_descriptor_limit (BusContext      *context)
4ff8bf
   /* We used to compute a suitable rlimit based on the configured number
4ff8bf
    * of connections, but that breaks down as soon as we allow fd-passing,
4ff8bf
    * because each connection is allowed to pass 64 fds to us, and if
4ff8bf
-   * they all did, we'd hit kernel limits. We now hard-code 64k as a
4ff8bf
-   * good limit, like systemd does: that's enough to avoid DoS from
4ff8bf
-   * anything short of multiple uids conspiring against us.
4ff8bf
+   * they all did, we'd hit kernel limits. We now hard-code a good
4ff8bf
+   * limit that is enough to avoid DoS from anything short of multiple
4ff8bf
+   * uids conspiring against us, much like systemd does.
4ff8bf
    */
4ff8bf
-  if (!_dbus_rlimit_raise_fd_limit_if_privileged (65536, &error))
4ff8bf
+  if (!_dbus_rlimit_raise_fd_limit (&error))
4ff8bf
     {
4ff8bf
       bus_context_log (context, DBUS_SYSTEM_LOG_WARNING,
4ff8bf
                        "%s: %s", error.name, error.message);
4ff8bf
diff --git a/dbus/dbus-sysdeps-util-unix.c b/dbus/dbus-sysdeps-util-unix.c
4ff8bf
index 2be5b779..7c4c3604 100644
4ff8bf
--- a/dbus/dbus-sysdeps-util-unix.c
4ff8bf
+++ b/dbus/dbus-sysdeps-util-unix.c
4ff8bf
@@ -406,23 +406,15 @@ _dbus_rlimit_save_fd_limit (DBusError *error)
4ff8bf
   return self;
4ff8bf
 }
4ff8bf
 
4ff8bf
+/* Enough fds that we shouldn't run out, even if several uids work
4ff8bf
+ * together to carry out a denial-of-service attack. This happens to be
4ff8bf
+ * the same number that systemd < 234 would normally use. */
4ff8bf
+#define ENOUGH_FDS 65536
4ff8bf
+
4ff8bf
 dbus_bool_t
4ff8bf
-_dbus_rlimit_raise_fd_limit_if_privileged (unsigned int  desired,
4ff8bf
-                                           DBusError    *error)
4ff8bf
+_dbus_rlimit_raise_fd_limit (DBusError *error)
4ff8bf
 {
4ff8bf
-  struct rlimit lim;
4ff8bf
-
4ff8bf
-  /* No point to doing this practically speaking
4ff8bf
-   * if we're not uid 0.  We expect the system
4ff8bf
-   * bus to use this before we change UID, and
4ff8bf
-   * the session bus takes the Linux default,
4ff8bf
-   * currently 1024 for cur and 4096 for max.
4ff8bf
-   */
4ff8bf
-  if (getuid () != 0)
4ff8bf
-    {
4ff8bf
-      /* not an error, we're probably the session bus */
4ff8bf
-      return TRUE;
4ff8bf
-    }
4ff8bf
+  struct rlimit old, lim;
4ff8bf
 
4ff8bf
   if (getrlimit (RLIMIT_NOFILE, &lim) < 0)
4ff8bf
     {
4ff8bf
@@ -431,22 +423,43 @@ _dbus_rlimit_raise_fd_limit_if_privileged (unsigned int  desired,
4ff8bf
       return FALSE;
4ff8bf
     }
4ff8bf
 
4ff8bf
-  if (lim.rlim_cur == RLIM_INFINITY || lim.rlim_cur >= desired)
4ff8bf
+  old = lim;
4ff8bf
+
4ff8bf
+  if (getuid () == 0)
4ff8bf
     {
4ff8bf
-      /* not an error, everything is fine */
4ff8bf
-      return TRUE;
4ff8bf
+      /* We are privileged, so raise the soft limit to at least
4ff8bf
+       * ENOUGH_FDS, and the hard limit to at least the desired soft
4ff8bf
+       * limit. This assumes we can exercise CAP_SYS_RESOURCE on Linux,
4ff8bf
+       * or other OSs' equivalents. */
4ff8bf
+      if (lim.rlim_cur != RLIM_INFINITY &&
4ff8bf
+          lim.rlim_cur < ENOUGH_FDS)
4ff8bf
+        lim.rlim_cur = ENOUGH_FDS;
4ff8bf
+
4ff8bf
+      if (lim.rlim_max != RLIM_INFINITY &&
4ff8bf
+          lim.rlim_max < lim.rlim_cur)
4ff8bf
+        lim.rlim_max = lim.rlim_cur;
4ff8bf
     }
4ff8bf
 
4ff8bf
-  /* Ignore "maximum limit", assume we have the "superuser"
4ff8bf
-   * privileges.  On Linux this is CAP_SYS_RESOURCE.
4ff8bf
-   */
4ff8bf
-  lim.rlim_cur = lim.rlim_max = desired;
4ff8bf
+  /* Raise the soft limit to match the hard limit, which we can do even
4ff8bf
+   * if we are unprivileged. In particular, systemd >= 240 will normally
4ff8bf
+   * set rlim_cur to 1024 and rlim_max to 512*1024, recent Debian
4ff8bf
+   * versions end up setting rlim_cur to 1024 and rlim_max to 1024*1024,
4ff8bf
+   * and older and non-systemd Linux systems would typically set rlim_cur
4ff8bf
+   * to 1024 and rlim_max to 4096. */
4ff8bf
+  if (lim.rlim_max == RLIM_INFINITY || lim.rlim_cur < lim.rlim_max)
4ff8bf
+    lim.rlim_cur = lim.rlim_max;
4ff8bf
+
4ff8bf
+  /* Early-return if there is nothing to do. */
4ff8bf
+  if (lim.rlim_max == old.rlim_max &&
4ff8bf
+      lim.rlim_cur == old.rlim_cur)
4ff8bf
+    return TRUE;
4ff8bf
 
4ff8bf
   if (setrlimit (RLIMIT_NOFILE, &lim) < 0)
4ff8bf
     {
4ff8bf
       dbus_set_error (error, _dbus_error_from_errno (errno),
4ff8bf
-                      "Failed to set fd limit to %u: %s",
4ff8bf
-                      desired, _dbus_strerror (errno));
4ff8bf
+                      "Failed to set fd limit to %lu: %s",
4ff8bf
+                      (unsigned long) lim.rlim_cur,
4ff8bf
+                      _dbus_strerror (errno));
4ff8bf
       return FALSE;
4ff8bf
     }
4ff8bf
 
4ff8bf
@@ -485,8 +498,7 @@ _dbus_rlimit_save_fd_limit (DBusError *error)
4ff8bf
 }
4ff8bf
 
4ff8bf
 dbus_bool_t
4ff8bf
-_dbus_rlimit_raise_fd_limit_if_privileged (unsigned int  desired,
4ff8bf
-                                           DBusError    *error)
4ff8bf
+_dbus_rlimit_raise_fd_limit (DBusError *error)
4ff8bf
 {
4ff8bf
   fd_limit_not_supported (error);
4ff8bf
   return FALSE;
4ff8bf
diff --git a/dbus/dbus-sysdeps-util-win.c b/dbus/dbus-sysdeps-util-win.c
4ff8bf
index 1ef4ae6c..1c1d9f7d 100644
4ff8bf
--- a/dbus/dbus-sysdeps-util-win.c
4ff8bf
+++ b/dbus/dbus-sysdeps-util-win.c
4ff8bf
@@ -273,8 +273,7 @@ _dbus_rlimit_save_fd_limit (DBusError *error)
4ff8bf
 }
4ff8bf
 
4ff8bf
 dbus_bool_t
4ff8bf
-_dbus_rlimit_raise_fd_limit_if_privileged (unsigned int  desired,
4ff8bf
-                                           DBusError    *error)
4ff8bf
+_dbus_rlimit_raise_fd_limit (DBusError *error)
4ff8bf
 {
4ff8bf
   fd_limit_not_supported (error);
4ff8bf
   return FALSE;
4ff8bf
diff --git a/dbus/dbus-sysdeps.h b/dbus/dbus-sysdeps.h
4ff8bf
index ef786ecc..0b9d7696 100644
4ff8bf
--- a/dbus/dbus-sysdeps.h
4ff8bf
+++ b/dbus/dbus-sysdeps.h
4ff8bf
@@ -698,8 +698,7 @@ dbus_bool_t _dbus_replace_install_prefix (DBusString *path);
4ff8bf
 typedef struct DBusRLimit DBusRLimit;
4ff8bf
 
4ff8bf
 DBusRLimit     *_dbus_rlimit_save_fd_limit                 (DBusError    *error);
4ff8bf
-dbus_bool_t     _dbus_rlimit_raise_fd_limit_if_privileged  (unsigned int  desired,
4ff8bf
-                                                            DBusError    *error);
4ff8bf
+dbus_bool_t     _dbus_rlimit_raise_fd_limit                (DBusError    *error);
4ff8bf
 dbus_bool_t     _dbus_rlimit_restore_fd_limit              (DBusRLimit   *saved,
4ff8bf
                                                             DBusError    *error);
4ff8bf
 void            _dbus_rlimit_free                          (DBusRLimit   *lim);
4ff8bf
-- 
4ff8bf
GitLab
4ff8bf