3d3dc8
From 471eda89a25a3ceac91a2d05e39a54aae78038ed Mon Sep 17 00:00:00 2001
3d3dc8
From: Daan De Meyer <daan.j.demeyer@gmail.com>
3d3dc8
Date: Tue, 24 Aug 2021 16:46:47 +0100
3d3dc8
Subject: [PATCH] core: Check unit start rate limiting earlier
3d3dc8
3d3dc8
Fixes #17433. Currently, if any of the validations we do before we
3d3dc8
check start rate limiting fail, we can still enter a busy loop as
3d3dc8
no rate limiting gets applied. A common occurence of this scenario
3d3dc8
is path units triggering a service that fails a condition check.
3d3dc8
3d3dc8
To fix the issue, we simply move up start rate limiting checks to
3d3dc8
be the first thing we do when starting a unit. To achieve this,
3d3dc8
we add a new method to the unit vtable and implement it for the
3d3dc8
relevant unit types so that we can do the start rate limit checks
3d3dc8
earlier on.
3d3dc8
3d3dc8
(cherry picked from commit 9727f2427ff6b2e1f4ab927cc57ad8e888f04e95)
3d3dc8
3d3dc8
Related: #2036608
3d3dc8
3d3dc8
[msekleta: I've deleted part of the original commit that adds test for
3d3dc8
issue #17433. This was necessary because upstream commit assumes newer
3d3dc8
organization of the test code which we don't have in RHEL-8 tree. As
3d3dc8
a consequence we must add explicit test for this in the internal
3d3dc8
test-suite.]
3d3dc8
---
3d3dc8
 src/core/automount.c | 23 +++++++++++++++++------
3d3dc8
 src/core/mount.c     | 23 +++++++++++++++++------
3d3dc8
 src/core/path.c      | 23 +++++++++++++++++------
3d3dc8
 src/core/service.c   | 25 ++++++++++++++++++-------
3d3dc8
 src/core/socket.c    | 23 +++++++++++++++++------
3d3dc8
 src/core/swap.c      | 23 +++++++++++++++++------
3d3dc8
 src/core/timer.c     | 22 ++++++++++++++++------
3d3dc8
 src/core/unit.c      | 14 ++++++++++----
3d3dc8
 src/core/unit.h      |  4 ++++
3d3dc8
 9 files changed, 133 insertions(+), 47 deletions(-)
3d3dc8
3d3dc8
diff --git a/src/core/automount.c b/src/core/automount.c
3d3dc8
index 2bc160cb07..5e16adabb5 100644
3d3dc8
--- a/src/core/automount.c
3d3dc8
+++ b/src/core/automount.c
3d3dc8
@@ -808,12 +808,6 @@ static int automount_start(Unit *u) {
3d3dc8
                 return -ENOENT;
3d3dc8
         }
3d3dc8
 
3d3dc8
-        r = unit_test_start_limit(u);
3d3dc8
-        if (r < 0) {
3d3dc8
-                automount_enter_dead(a, AUTOMOUNT_FAILURE_START_LIMIT_HIT);
3d3dc8
-                return r;
3d3dc8
-        }
3d3dc8
-
3d3dc8
         r = unit_acquire_invocation_id(u);
3d3dc8
         if (r < 0)
3d3dc8
                 return r;
3d3dc8
@@ -1077,6 +1071,21 @@ static bool automount_supported(void) {
3d3dc8
         return supported;
3d3dc8
 }
3d3dc8
 
3d3dc8
+static int automount_test_start_limit(Unit *u) {
3d3dc8
+        Automount *a = AUTOMOUNT(u);
3d3dc8
+        int r;
3d3dc8
+
3d3dc8
+        assert(a);
3d3dc8
+
3d3dc8
+        r = unit_test_start_limit(u);
3d3dc8
+        if (r < 0) {
3d3dc8
+                automount_enter_dead(a, AUTOMOUNT_FAILURE_START_LIMIT_HIT);
3d3dc8
+                return r;
3d3dc8
+        }
3d3dc8
+
3d3dc8
+        return 0;
3d3dc8
+}
3d3dc8
+
3d3dc8
 static const char* const automount_result_table[_AUTOMOUNT_RESULT_MAX] = {
3d3dc8
         [AUTOMOUNT_SUCCESS] = "success",
3d3dc8
         [AUTOMOUNT_FAILURE_RESOURCES] = "resources",
3d3dc8
@@ -1135,4 +1144,6 @@ const UnitVTable automount_vtable = {
3d3dc8
                         [JOB_FAILED]     = "Failed to unset automount %s.",
3d3dc8
                 },
3d3dc8
         },
3d3dc8
+
3d3dc8
+        .test_start_limit = automount_test_start_limit,
3d3dc8
 };
3d3dc8
diff --git a/src/core/mount.c b/src/core/mount.c
3d3dc8
index aa586d88cb..22848847e5 100644
3d3dc8
--- a/src/core/mount.c
3d3dc8
+++ b/src/core/mount.c
3d3dc8
@@ -1065,12 +1065,6 @@ static int mount_start(Unit *u) {
3d3dc8
 
3d3dc8
         assert(IN_SET(m->state, MOUNT_DEAD, MOUNT_FAILED));
3d3dc8
 
3d3dc8
-        r = unit_test_start_limit(u);
3d3dc8
-        if (r < 0) {
3d3dc8
-                mount_enter_dead(m, MOUNT_FAILURE_START_LIMIT_HIT);
3d3dc8
-                return r;
3d3dc8
-        }
3d3dc8
-
3d3dc8
         r = unit_acquire_invocation_id(u);
3d3dc8
         if (r < 0)
3d3dc8
                 return r;
3d3dc8
@@ -1957,6 +1951,21 @@ static int mount_control_pid(Unit *u) {
3d3dc8
         return m->control_pid;
3d3dc8
 }
3d3dc8
 
3d3dc8
+static int mount_test_start_limit(Unit *u) {
3d3dc8
+        Mount *m = MOUNT(u);
3d3dc8
+        int r;
3d3dc8
+
3d3dc8
+        assert(m);
3d3dc8
+
3d3dc8
+        r = unit_test_start_limit(u);
3d3dc8
+        if (r < 0) {
3d3dc8
+                mount_enter_dead(m, MOUNT_FAILURE_START_LIMIT_HIT);
3d3dc8
+                return r;
3d3dc8
+        }
3d3dc8
+
3d3dc8
+        return 0;
3d3dc8
+}
3d3dc8
+
3d3dc8
 static const char* const mount_exec_command_table[_MOUNT_EXEC_COMMAND_MAX] = {
3d3dc8
         [MOUNT_EXEC_MOUNT] = "ExecMount",
3d3dc8
         [MOUNT_EXEC_UNMOUNT] = "ExecUnmount",
3d3dc8
@@ -2048,4 +2057,6 @@ const UnitVTable mount_vtable = {
3d3dc8
                         [JOB_TIMEOUT]    = "Timed out unmounting %s.",
3d3dc8
                 },
3d3dc8
         },
3d3dc8
+
3d3dc8
+        .test_start_limit = mount_test_start_limit,
3d3dc8
 };
3d3dc8
diff --git a/src/core/path.c b/src/core/path.c
3d3dc8
index 4bccc0396b..1e69a1f05f 100644
3d3dc8
--- a/src/core/path.c
3d3dc8
+++ b/src/core/path.c
3d3dc8
@@ -565,12 +565,6 @@ static int path_start(Unit *u) {
3d3dc8
                 return -ENOENT;
3d3dc8
         }
3d3dc8
 
3d3dc8
-        r = unit_test_start_limit(u);
3d3dc8
-        if (r < 0) {
3d3dc8
-                path_enter_dead(p, PATH_FAILURE_START_LIMIT_HIT);
3d3dc8
-                return r;
3d3dc8
-        }
3d3dc8
-
3d3dc8
         r = unit_acquire_invocation_id(u);
3d3dc8
         if (r < 0)
3d3dc8
                 return r;
3d3dc8
@@ -730,6 +724,21 @@ static void path_reset_failed(Unit *u) {
3d3dc8
         p->result = PATH_SUCCESS;
3d3dc8
 }
3d3dc8
 
3d3dc8
+static int path_test_start_limit(Unit *u) {
3d3dc8
+        Path *p = PATH(u);
3d3dc8
+        int r;
3d3dc8
+
3d3dc8
+        assert(p);
3d3dc8
+
3d3dc8
+        r = unit_test_start_limit(u);
3d3dc8
+        if (r < 0) {
3d3dc8
+                path_enter_dead(p, PATH_FAILURE_START_LIMIT_HIT);
3d3dc8
+                return r;
3d3dc8
+        }
3d3dc8
+
3d3dc8
+        return 0;
3d3dc8
+}
3d3dc8
+
3d3dc8
 static const char* const path_type_table[_PATH_TYPE_MAX] = {
3d3dc8
         [PATH_EXISTS] = "PathExists",
3d3dc8
         [PATH_EXISTS_GLOB] = "PathExistsGlob",
3d3dc8
@@ -782,4 +791,6 @@ const UnitVTable path_vtable = {
3d3dc8
 
3d3dc8
         .bus_vtable = bus_path_vtable,
3d3dc8
         .bus_set_property = bus_path_set_property,
3d3dc8
+
3d3dc8
+        .test_start_limit = path_test_start_limit,
3d3dc8
 };
3d3dc8
diff --git a/src/core/service.c b/src/core/service.c
3d3dc8
index 1a1de43d0d..c5f408d817 100644
3d3dc8
--- a/src/core/service.c
3d3dc8
+++ b/src/core/service.c
3d3dc8
@@ -2387,13 +2387,6 @@ static int service_start(Unit *u) {
3d3dc8
 
3d3dc8
         assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED));
3d3dc8
 
3d3dc8
-        /* Make sure we don't enter a busy loop of some kind. */
3d3dc8
-        r = unit_test_start_limit(u);
3d3dc8
-        if (r < 0) {
3d3dc8
-                service_enter_dead(s, SERVICE_FAILURE_START_LIMIT_HIT, false);
3d3dc8
-                return r;
3d3dc8
-        }
3d3dc8
-
3d3dc8
         r = unit_acquire_invocation_id(u);
3d3dc8
         if (r < 0)
3d3dc8
                 return r;
3d3dc8
@@ -4081,6 +4074,22 @@ static bool service_needs_console(Unit *u) {
3d3dc8
                       SERVICE_FINAL_SIGKILL);
3d3dc8
 }
3d3dc8
 
3d3dc8
+static int service_test_start_limit(Unit *u) {
3d3dc8
+        Service *s = SERVICE(u);
3d3dc8
+        int r;
3d3dc8
+
3d3dc8
+        assert(s);
3d3dc8
+
3d3dc8
+        /* Make sure we don't enter a busy loop of some kind. */
3d3dc8
+        r = unit_test_start_limit(u);
3d3dc8
+        if (r < 0) {
3d3dc8
+                service_enter_dead(s, SERVICE_FAILURE_START_LIMIT_HIT, false);
3d3dc8
+                return r;
3d3dc8
+        }
3d3dc8
+
3d3dc8
+        return 0;
3d3dc8
+}
3d3dc8
+
3d3dc8
 static const char* const service_restart_table[_SERVICE_RESTART_MAX] = {
3d3dc8
         [SERVICE_RESTART_NO] = "no",
3d3dc8
         [SERVICE_RESTART_ON_SUCCESS] = "on-success",
3d3dc8
@@ -4222,4 +4231,6 @@ const UnitVTable service_vtable = {
3d3dc8
                         [JOB_FAILED]     = "Stopped (with error) %s.",
3d3dc8
                 },
3d3dc8
         },
3d3dc8
+
3d3dc8
+        .test_start_limit = service_test_start_limit,
3d3dc8
 };
3d3dc8
diff --git a/src/core/socket.c b/src/core/socket.c
3d3dc8
index 09491c6677..36d2e4f823 100644
3d3dc8
--- a/src/core/socket.c
3d3dc8
+++ b/src/core/socket.c
3d3dc8
@@ -2469,12 +2469,6 @@ static int socket_start(Unit *u) {
3d3dc8
 
3d3dc8
         assert(IN_SET(s->state, SOCKET_DEAD, SOCKET_FAILED));
3d3dc8
 
3d3dc8
-        r = unit_test_start_limit(u);
3d3dc8
-        if (r < 0) {
3d3dc8
-                socket_enter_dead(s, SOCKET_FAILURE_START_LIMIT_HIT);
3d3dc8
-                return r;
3d3dc8
-        }
3d3dc8
-
3d3dc8
         r = unit_acquire_invocation_id(u);
3d3dc8
         if (r < 0)
3d3dc8
                 return r;
3d3dc8
@@ -3267,6 +3261,21 @@ static int socket_control_pid(Unit *u) {
3d3dc8
         return s->control_pid;
3d3dc8
 }
3d3dc8
 
3d3dc8
+static int socket_test_start_limit(Unit *u) {
3d3dc8
+        Socket *s = SOCKET(u);
3d3dc8
+        int r;
3d3dc8
+
3d3dc8
+        assert(s);
3d3dc8
+
3d3dc8
+        r = unit_test_start_limit(u);
3d3dc8
+        if (r < 0) {
3d3dc8
+                socket_enter_dead(s, SOCKET_FAILURE_START_LIMIT_HIT);
3d3dc8
+                return r;
3d3dc8
+        }
3d3dc8
+
3d3dc8
+        return 0;
3d3dc8
+}
3d3dc8
+
3d3dc8
 static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = {
3d3dc8
         [SOCKET_EXEC_START_PRE] = "ExecStartPre",
3d3dc8
         [SOCKET_EXEC_START_CHOWN] = "ExecStartChown",
3d3dc8
@@ -3359,4 +3368,6 @@ const UnitVTable socket_vtable = {
3d3dc8
                         [JOB_TIMEOUT]    = "Timed out stopping %s.",
3d3dc8
                 },
3d3dc8
         },
3d3dc8
+
3d3dc8
+        .test_start_limit = socket_test_start_limit,
3d3dc8
 };
3d3dc8
diff --git a/src/core/swap.c b/src/core/swap.c
3d3dc8
index 823699699e..90fcd69300 100644
3d3dc8
--- a/src/core/swap.c
3d3dc8
+++ b/src/core/swap.c
3d3dc8
@@ -851,12 +851,6 @@ static int swap_start(Unit *u) {
3d3dc8
                 if (UNIT(other)->job && UNIT(other)->job->state == JOB_RUNNING)
3d3dc8
                         return -EAGAIN;
3d3dc8
 
3d3dc8
-        r = unit_test_start_limit(u);
3d3dc8
-        if (r < 0) {
3d3dc8
-                swap_enter_dead(s, SWAP_FAILURE_START_LIMIT_HIT);
3d3dc8
-                return r;
3d3dc8
-        }
3d3dc8
-
3d3dc8
         r = unit_acquire_invocation_id(u);
3d3dc8
         if (r < 0)
3d3dc8
                 return r;
3d3dc8
@@ -1458,6 +1452,21 @@ static int swap_control_pid(Unit *u) {
3d3dc8
         return s->control_pid;
3d3dc8
 }
3d3dc8
 
3d3dc8
+static int swap_test_start_limit(Unit *u) {
3d3dc8
+        Swap *s = SWAP(u);
3d3dc8
+        int r;
3d3dc8
+
3d3dc8
+        assert(s);
3d3dc8
+
3d3dc8
+        r = unit_test_start_limit(u);
3d3dc8
+        if (r < 0) {
3d3dc8
+                swap_enter_dead(s, SWAP_FAILURE_START_LIMIT_HIT);
3d3dc8
+                return r;
3d3dc8
+        }
3d3dc8
+
3d3dc8
+        return 0;
3d3dc8
+}
3d3dc8
+
3d3dc8
 static const char* const swap_exec_command_table[_SWAP_EXEC_COMMAND_MAX] = {
3d3dc8
         [SWAP_EXEC_ACTIVATE] = "ExecActivate",
3d3dc8
         [SWAP_EXEC_DEACTIVATE] = "ExecDeactivate",
3d3dc8
@@ -1547,4 +1556,6 @@ const UnitVTable swap_vtable = {
3d3dc8
                         [JOB_TIMEOUT]    = "Timed out deactivating swap %s.",
3d3dc8
                 },
3d3dc8
         },
3d3dc8
+
3d3dc8
+        .test_start_limit = swap_test_start_limit,
3d3dc8
 };
3d3dc8
diff --git a/src/core/timer.c b/src/core/timer.c
3d3dc8
index be16321296..fb9ae17990 100644
3d3dc8
--- a/src/core/timer.c
3d3dc8
+++ b/src/core/timer.c
3d3dc8
@@ -599,12 +599,6 @@ static int timer_start(Unit *u) {
3d3dc8
                 return -ENOENT;
3d3dc8
         }
3d3dc8
 
3d3dc8
-        r = unit_test_start_limit(u);
3d3dc8
-        if (r < 0) {
3d3dc8
-                timer_enter_dead(t, TIMER_FAILURE_START_LIMIT_HIT);
3d3dc8
-                return r;
3d3dc8
-        }
3d3dc8
-
3d3dc8
         r = unit_acquire_invocation_id(u);
3d3dc8
         if (r < 0)
3d3dc8
                 return r;
3d3dc8
@@ -829,6 +823,21 @@ static void timer_timezone_change(Unit *u) {
3d3dc8
         timer_enter_waiting(t, false);
3d3dc8
 }
3d3dc8
 
3d3dc8
+static int timer_test_start_limit(Unit *u) {
3d3dc8
+        Timer *t = TIMER(u);
3d3dc8
+        int r;
3d3dc8
+
3d3dc8
+        assert(t);
3d3dc8
+
3d3dc8
+        r = unit_test_start_limit(u);
3d3dc8
+        if (r < 0) {
3d3dc8
+                timer_enter_dead(t, TIMER_FAILURE_START_LIMIT_HIT);
3d3dc8
+                return r;
3d3dc8
+        }
3d3dc8
+
3d3dc8
+        return 0;
3d3dc8
+}
3d3dc8
+
3d3dc8
 static const char* const timer_base_table[_TIMER_BASE_MAX] = {
3d3dc8
         [TIMER_ACTIVE] = "OnActiveSec",
3d3dc8
         [TIMER_BOOT] = "OnBootSec",
3d3dc8
@@ -884,4 +893,5 @@ const UnitVTable timer_vtable = {
3d3dc8
         .bus_set_property = bus_timer_set_property,
3d3dc8
 
3d3dc8
         .can_transient = true,
3d3dc8
+        .test_start_limit = timer_test_start_limit,
3d3dc8
 };
3d3dc8
diff --git a/src/core/unit.c b/src/core/unit.c
3d3dc8
index 9013186d8a..f0df7452fa 100644
3d3dc8
--- a/src/core/unit.c
3d3dc8
+++ b/src/core/unit.c
3d3dc8
@@ -1728,10 +1728,16 @@ int unit_start(Unit *u) {
3d3dc8
 
3d3dc8
         assert(u);
3d3dc8
 
3d3dc8
-        /* If this is already started, then this will succeed. Note
3d3dc8
-         * that this will even succeed if this unit is not startable
3d3dc8
-         * by the user. This is relied on to detect when we need to
3d3dc8
-         * wait for units and when waiting is finished. */
3d3dc8
+        /* Check start rate limiting early so that failure conditions don't cause us to enter a busy loop. */
3d3dc8
+        if (UNIT_VTABLE(u)->test_start_limit) {
3d3dc8
+                int r = UNIT_VTABLE(u)->test_start_limit(u);
3d3dc8
+                if (r < 0)
3d3dc8
+                        return r;
3d3dc8
+        }
3d3dc8
+
3d3dc8
+        /* If this is already started, then this will succeed. Note that this will even succeed if this unit
3d3dc8
+         * is not startable by the user. This is relied on to detect when we need to wait for units and when
3d3dc8
+         * waiting is finished. */
3d3dc8
         state = unit_active_state(u);
3d3dc8
         if (UNIT_IS_ACTIVE_OR_RELOADING(state))
3d3dc8
                 return -EALREADY;
3d3dc8
diff --git a/src/core/unit.h b/src/core/unit.h
3d3dc8
index a8bc350b66..9e6f1bcf81 100644
3d3dc8
--- a/src/core/unit.h
3d3dc8
+++ b/src/core/unit.h
3d3dc8
@@ -567,6 +567,10 @@ typedef struct UnitVTable {
3d3dc8
         /* The bus vtable */
3d3dc8
         const sd_bus_vtable *bus_vtable;
3d3dc8
 
3d3dc8
+        /* If this function is set, it's invoked first as part of starting a unit to allow start rate
3d3dc8
+         * limiting checks to occur before we do anything else. */
3d3dc8
+        int (*test_start_limit)(Unit *u);
3d3dc8
+
3d3dc8
         /* The strings to print in status messages */
3d3dc8
         UnitStatusMessageFormats status_message_formats;
3d3dc8