From abc372fdbe3892fa9a71fc639198bf32aaec9c23 Mon Sep 17 00:00:00 2001 From: Alban Crequy Date: Wed, 16 Jul 2014 14:17:47 +0100 Subject: [PATCH 2/4] _dbus_cgroup_for_pid: new function Retrieve the cgroup of a given process by reading /proc/pid/cgroup. Malicious processes could inject data in /proc/pid/cgroup so it cannot be completely trusted but it's the best we can do without SCM_CGROUP. When SCM_CGROUP is implemented in the Linux kernel, the implementation should be revisited. https://bugs.freedesktop.org/show_bug.cgi?id=81469 --- dbus/dbus-sysdeps-util-unix.c | 165 ++++++++++++++++++++++++++++++++++++++++++ dbus/dbus-sysdeps.h | 5 ++ 2 files changed, 170 insertions(+) diff --git a/dbus/dbus-sysdeps-util-unix.c b/dbus/dbus-sysdeps-util-unix.c index d104e41..29af2af 100644 --- a/dbus/dbus-sysdeps-util-unix.c +++ b/dbus/dbus-sysdeps-util-unix.c @@ -1182,6 +1182,171 @@ fail: return FALSE; } +/** + * Get the cgroup path of a task in the first cgroup hierarchy. + * + * When systemd is used, the first cgroup hierarchy is the generic named + * hierarchy "name=systemd" with no controller attached, mounted on + * /sys/fs/cgroup/systemd/. An example of cgroup path generated by systemd is + * "/system.slice/bluetooth.service". + * + * This is similar to task_cgroup_path() in kernel. + * + * This could fail if the process is short-lived and already terminated but we + * don't have SO_PEERCGROUP / SO_PASSCGROUP yet. + * + * @param pid Process id + * @param str Append cgroup path to this string + * @param max_len Maximum length of returned cgroup path + * @param error return location for errors + * @returns #FALSE on error + */ +dbus_bool_t +_dbus_cgroup_for_pid (unsigned long pid, + DBusString *str, + int max_len, + DBusError *error) +{ + /* This is all Linux-specific for now */ + DBusString proc_path; + DBusString content; + int fd; + int bytes_read; + int pos, min_pos; + unsigned long hierarchy_id, min_hierarchy_id; + int cgroup_path_len; + dbus_bool_t ret; + + if (!_dbus_string_init (&proc_path)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + + if (!_dbus_string_init (&content)) + { + _DBUS_SET_OOM (error); + _dbus_string_free (&proc_path); + return FALSE; + } + + if (!_dbus_string_append_printf (&proc_path, "/proc/%ld/cgroup", pid)) + goto oom; + + fd = open (_dbus_string_get_const_data (&proc_path), O_RDONLY); + if (fd < 0) + { + dbus_set_error (error, + _dbus_error_from_errno (errno), + "Failed to open \"%s\": %s", + _dbus_string_get_const_data (&proc_path), + _dbus_strerror (errno)); + goto fail; + } + + /* A buffer of 4096 is large enough for normal /proc/pid/cgroup files (it is + * usually less than 150). A process can easily move itself to a sub-cgroup + * with an long name and do that recursively to get a file bigger than 4096. + * + * So max_connections_per_cgroup is not always a protection against + * malicious processes but at least protects against bogus applications. + */ + bytes_read =_dbus_read (fd, &content, 4096); + if (!bytes_read) + { + dbus_set_error (error, + _dbus_error_from_errno (errno), + "Failed to read from \"%s\": %s", + _dbus_string_get_const_data (&proc_path), + _dbus_strerror (errno)); + _dbus_close (fd, NULL); + goto fail; + } + + if (!_dbus_close (fd, error)) + goto fail; + + if (bytes_read == 4096) + { + dbus_set_error (error, + _dbus_error_from_errno (errno), + "File \"%s\" is too big to read", + _dbus_string_get_const_data (&proc_path)); + _dbus_close (fd, NULL); + goto fail; + } + + /* Extract the path from the lowest-numbered hierarchy id starting + * from 1 (usually 1) instead of returning the whole content of the file. + */ + pos = 0; + min_hierarchy_id = 0; + do + { + ret = _dbus_string_parse_uint (&content, pos, &hierarchy_id, NULL); + if (!ret) + { + min_hierarchy_id = 0; + break; + } + + if (min_hierarchy_id == 0 || hierarchy_id < min_hierarchy_id) + { + min_hierarchy_id = hierarchy_id; + + ret = _dbus_string_find (&content, pos, ":", &pos); + if (!ret) + { + min_hierarchy_id = 0; + break; + } + pos += 1; + + ret = _dbus_string_find (&content, pos, ":", &pos); + if (!ret) + { + min_hierarchy_id = 0; + break; + } + pos += 1; + + min_pos = pos; + ret = _dbus_string_find (&content, min_pos, "\n", &cgroup_path_len); + if (!ret) + { + min_hierarchy_id = 0; + break; + } + cgroup_path_len -= min_pos; + } + } + while (ret && _dbus_string_find (&content, pos, "\n", &pos) && pos++ && + pos < _dbus_string_get_length (&content)); + + if (min_hierarchy_id < 1) + { + dbus_set_error (error, + _dbus_error_from_errno (errno), + "Cannot parse file \"%s\"", + _dbus_string_get_const_data (&proc_path)); + _dbus_close (fd, NULL); + goto fail; + } + + if (!_dbus_string_copy_len (&content, min_pos, cgroup_path_len, str, 0)) + goto oom; + + _dbus_string_free (&content); + _dbus_string_free (&proc_path); + return TRUE; +oom: + _DBUS_SET_OOM (error); +fail: + _dbus_string_free (&content); + _dbus_string_free (&proc_path); + return FALSE; +} + /* * replaces the term DBUS_PREFIX in configure_time_path by the * current dbus installation directory. On unix this function is a noop diff --git a/dbus/dbus-sysdeps.h b/dbus/dbus-sysdeps.h index 21033eb..6383d89 100644 --- a/dbus/dbus-sysdeps.h +++ b/dbus/dbus-sysdeps.h @@ -440,6 +440,11 @@ dbus_bool_t _dbus_command_for_pid (unsigned long pid, int max_len, DBusError *error); +dbus_bool_t _dbus_cgroup_for_pid (unsigned long pid, + DBusString *str, + int max_len, + DBusError *error); + /** A UNIX signal handler */ typedef void (* DBusSignalHandler) (int sig); -- 1.8.5.3