Freezer: handle DBus SocketOverflow 75/259075/6 accepted/tizen/unified/20210611.145105 submit/tizen/20210610.022051
authorMichal Bloch <m.bloch@partner.samsung.com>
Fri, 28 May 2021 20:16:07 +0000 (22:16 +0200)
committerMichal Bloch <m.bloch@partner.samsung.com>
Wed, 9 Jun 2021 18:28:27 +0000 (20:28 +0200)
Public part, has no freezer module changes

Change-Id: I95928115d1a33d5ccd8940a8375145ade1869c70
Signed-off-by: Michal Bloch <m.bloch@partner.samsung.com>
packaging/resourced.spec
src/CMakeLists.txt
src/common/dbus-handler.c
src/common/dbus-handler.h
src/common/proc-common.h
src/freezer/freezer.c

index b60cf5f..3f5a159 100644 (file)
@@ -30,6 +30,7 @@ Source0:    %{name}-%{version}.tar.gz
 %define plugindir %{_libdir}/resourced/plugins
 
 BuildRequires:  cmake
+BuildRequires:  pkgconfig(dbus-1)
 BuildRequires:  pkgconfig(glib-2.0)
 BuildRequires:  pkgconfig(dlog)
 BuildRequires:  pkgconfig(sqlite3)
index f8cf79b..8e19001 100644 (file)
@@ -86,6 +86,7 @@ SET(REQUIRES_LIST ${REQUIRES_LIST}
        libtzplatform-config
        capi-system-device
        libudev
+       dbus-1
   )
 
 INCLUDE(FindPkgConfig)
index 94cf16c..97f43b7 100644 (file)
@@ -39,7 +39,7 @@ static guint owner_id;
 static GList *dbus_signal_list;
 static GList *dbus_method_handle_list;
 
-static GDBusConnection *d_bus_get_connection(void)
+GDBusConnection *d_bus_get_connection(void)
 {
        static GDBusConnection *g_dbus_conn;
 
index df66ffd..d98c6ae 100644 (file)
@@ -88,6 +88,8 @@ struct d_bus_signal {
                                        "%s failed", g_dbus_method_invocation_get_method_name(ivc));    \
 }
 
+GDBusConnection *d_bus_get_connection(void);
+
 int d_bus_call_method_sync_gvariant(const char *dest, const char *path,
                const char *interface, const char *method,
                GVariant *gv);
index 0aab2d9..efaea5e 100644 (file)
@@ -177,6 +177,9 @@ struct proc_app_info {
        struct proc_program_info *program;
        struct proc_memory_state memory;
        unsigned long starttime;
+
+       unsigned long last_thaw_time; // in seconds since boot
+       bool dont_freeze;
 };
 
 int proc_get_freezer_status(void);
index f55b96e..cd9e592 100644 (file)
@@ -28,6 +28,7 @@
 #include <dlfcn.h>
 #include <sys/stat.h>
 #include <gio/gio.h>
+#include <dbus/dbus.h>
 
 #include "macro.h"
 #include "util.h"
 #include "proc-common.h"
 #include "proc-main.h"
 #include "freezer.h"
+#include "cgroup.h"
 
 #define FREEZER_MODULE_PATH FREEZER_DIR"/libsystem-freezer.so.0"
 
+static const int SOCKET_OVERFLOW_MIN_KILL_AGE_SECONDS = 5; // don't kill processes younger than this.
+static const int SOCKET_OVERFLOW_MIN_KILL_THAW_SECONDS = 5; // don't kill processes which were thawed this recently.
+
+/* SOCKET_OVERFLOW_SHOULD_REFREEZE:
+ *
+ * if FALSE: a process that gets thawed by SocketOverflow is PERMANENTLY
+ *           prevented from freezing ever again.
+ *
+ * if TRUE:  a process that gets thawed by SocketOverflow is TEMPORARILY
+ *           prevented from freezing for specified time, then it is frozen again. */
+static const bool SOCKET_OVERFLOW_SHOULD_REFREEZE = false;
+static const int SOCKET_OVERFLOW_REFREEZE_MILLISECONDS = 5000;
+
 static int freezer_init_check;
 
 /******************************************** Freezer symbols *************************************************/
@@ -111,6 +126,171 @@ static struct d_bus_method dbus_methods[] = {
        /* Add methods here */
 };
 
+static gboolean freezer_refreeze_cb(gpointer *data)
+{
+       const int pid = GPOINTER_TO_INT(data);
+
+       /* NB: this pointer cannot be cached and needs to be
+        * looked up every time. This is because the app can
+        * die before it is handled and so the pointer can
+        * become invalidated. */
+       struct proc_app_info *const pai = find_app_info(pid);
+       if (!pai)
+               return FALSE;
+
+       pai->dont_freeze = false;
+       resourced_freezer_app_suspend(& (struct proc_status) { .pid = pid, .pai = pai });
+
+       return FALSE;
+}
+
+static void cleanup_pids_array(GArray **pids)
+{
+       assert(*pids);
+       g_array_free(*pids, TRUE);
+}
+
+static int is_frozen(int frozenstein)
+{
+       __attribute__((cleanup(cleanup_pids_array))) GArray *refrigerati = NULL;
+
+       int ret = cgroup_get_pids("/sys/fs/cgroup/freezer/frozen", &refrigerati);
+       if (ret < 0) {
+               _E("Failed to get list of frozen pids: %s", strerror(-ret));
+               return ret;
+       }
+
+       for (int i = 0; i < refrigerati->len; ++i)
+               if (frozenstein == g_array_index(refrigerati, pid_t, i))
+                       return 1;
+
+       return 0;
+}
+
+static int get_pid_from_connection_name(const char *name)
+{
+       assert(name);
+
+       GError *err = NULL;
+       GVariant *reply = g_dbus_connection_call_sync(d_bus_get_connection(),
+               "org.freedesktop.DBus",
+               "/org/freedesktop/DBus",
+               "org.freedesktop.DBus",
+               "GetConnectionUnixProcessID",
+               g_variant_new("(s)", name),
+               G_VARIANT_TYPE("(u)"),
+               G_DBUS_CALL_FLAGS_NONE,
+               -1,
+               NULL,
+               &err);
+
+       if (err) {
+               g_error_free(err);
+               return -1;
+       } else if (!reply)
+               return -1;
+
+       unsigned int pid = 0;
+       g_variant_get(reply, "(u)", &pid);
+       g_variant_unref(reply);
+
+       if (!pid)
+               return -1;
+
+       return (int) pid;
+}
+
+static bool is_process_too_young_to_die(const struct proc_app_info *pai)
+{
+       unsigned long current_time;
+       if (proc_get_uptime(&current_time) != RESOURCED_ERROR_NONE)
+               return false;
+
+       return pai->starttime + SOCKET_OVERFLOW_MIN_KILL_AGE_SECONDS > current_time;
+}
+
+static void maybe_kill(int pid, const struct proc_app_info *pai)
+{
+       /* Don't kill processes created recently. If a process
+        * was created recently, it probably means that it has
+        * actually replaced an earlier process which had the
+        * overflow and just received the same PID. */
+       if (is_process_too_young_to_die(pai))
+               return;
+
+       kill(pid, SIGKILL); // ðŸš®
+}
+
+static bool was_thawed_recently(const struct proc_app_info *pai)
+{
+       unsigned long current_time;
+       if (proc_get_uptime(&current_time) != RESOURCED_ERROR_NONE)
+               return false;
+
+       /* `last_thaw_time` may look like it never gets updated here,
+        * this is because it happens inside `resourced_freezer_wakeup`
+        * inside the `libsystem-freezer.so` library. */
+       return pai->last_thaw_time + SOCKET_OVERFLOW_MIN_KILL_THAW_SECONDS > current_time;
+}
+
+static void freezer_socket_overflow_handler(GVariant *params)
+{
+       /* This signal means that the DBus daemon is unable to send data
+        * to given connection. The assumption here is that the process
+        * behind the connection is most likely frozen (since otherwise
+        * it would just wake up to fetch the message). */
+
+       const char *connection_name = NULL;
+       g_variant_get(params, "(&s)", &connection_name);
+       if (!connection_name)
+               return;
+
+       int frozenstein = get_pid_from_connection_name(connection_name);
+       if (frozenstein < 0)
+               return;
+
+       struct proc_app_info *const pai = find_app_info(frozenstein);
+       if (!pai) {
+               /* Don't kill services/daemons, this is
+                * a requirement (they aren't supposed
+                * to be frozen, anyway). However, it's
+                * still a suspicious situation so log it. */
+               _E("Non-app process %d has a D-Bus socket overflow!", frozenstein);
+               return;
+       }
+
+       if (was_thawed_recently(pai)) {
+               /* The process was recently thawed. It shouldn't be
+                * killed, and there is no need to thaw it again.
+                * But, prevent it from freezing to make sure it can
+                * handle the socket overflow. */
+               pai->dont_freeze = true;
+               return;
+       }
+
+       if (!is_frozen(frozenstein)) {
+               /* If it has a socket overflow but is NOT frozen,
+                * it seems it's broken enough to be killed. */
+               maybe_kill(frozenstein, pai);
+               return;
+       }
+
+       pai->dont_freeze = true;
+       resourced_freezer_wakeup(& (struct proc_status) { .pid = frozenstein, .pai = pai });
+
+       if (SOCKET_OVERFLOW_SHOULD_REFREEZE) {
+               /* The delay is arbitrary but should be long enough
+                * to let the process handle its DBus message queue. */
+               guint timeout_source = g_timeout_add(SOCKET_OVERFLOW_REFREEZE_MILLISECONDS,
+                       G_SOURCE_FUNC(freezer_refreeze_cb),
+                       GINT_TO_POINTER(frozenstein));
+
+               /* I don't think we can make use of this.
+                * Cleanup should be handled automatically anyway. */
+               (void) timeout_source;
+       }
+}
+
 static void freezer_dbus_init(bool is_suspend)
 {
        resourced_ret_c ret;
@@ -125,6 +305,11 @@ static void freezer_dbus_init(bool is_suspend)
                                      SIGNAL_FREEZER_SERVICE,
                                      (void *)resourced_freezer_dbus_service_signal_handler,
                                      NULL);
+       d_bus_register_signal(DBUS_PATH_DBUS,
+                                     DBUS_INTERFACE_TIZEN,
+                                     DBUS_TIZEN_CONNECTION_OVERFLOW_SIGNAL,
+                                     (void *)freezer_socket_overflow_handler,
+                                     NULL);
 
        ret = d_bus_register_methods(RESOURCED_PATH_FREEZER, dbus_methods_xml,
                                dbus_methods, ARRAY_SIZE(dbus_methods));