cgroup: rework how we validate/escape cgroups

Let's clean up validation/escaping of cgroup names. i.e. split out code
that tests if name needs escaping. Return proper error codes, and extend
test a bit.
This commit is contained in:
Lennart Poettering
2023-04-27 12:17:58 +02:00
parent 49c778e6bf
commit 1a56b0c05d
6 changed files with 103 additions and 75 deletions
+53 -42
View File
@@ -1554,52 +1554,64 @@ int cg_pid_get_user_slice(pid_t pid, char **slice) {
return cg_path_get_user_slice(cgroup, slice);
}
char *cg_escape(const char *p) {
bool need_prefix = false;
bool cg_needs_escape(const char *p) {
/* This implements very minimal escaping for names to be used
* as file names in the cgroup tree: any name which might
* conflict with a kernel name or is prefixed with '_' is
* prefixed with a '_'. That way, when reading cgroup names it
* is sufficient to remove a single prefixing underscore if
* there is one. */
/* Checks if the specified path is a valid cgroup name by our rules, or if it must be escaped. Note
* that we consider escaped cgroup names invalid here, as they need to be escaped a second time if
* they shall be used. Also note that various names cannot be made valid by escaping even if we
* return true here (because too long, or contain the forbidden character "/"). */
/* The return value of this function (unlike cg_unescape())
* needs free()! */
if (!filename_is_valid(p))
return true;
if (IN_SET(p[0], 0, '_', '.') ||
STR_IN_SET(p, "notify_on_release", "release_agent", "tasks") ||
startswith(p, "cgroup."))
need_prefix = true;
else {
const char *dot;
if (IN_SET(p[0], '_', '.'))
return true;
dot = strrchr(p, '.');
if (dot) {
CGroupController c;
size_t l = dot - p;
if (STR_IN_SET(p, "notify_on_release", "release_agent", "tasks"))
return true;
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
const char *n;
if (startswith(p, "cgroup."))
return true;
n = cgroup_controller_to_string(c);
for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
const char *q;
if (l != strlen(n))
continue;
q = startswith(p, cgroup_controller_to_string(c));
if (!q)
continue;
if (memcmp(p, n, l) != 0)
continue;
need_prefix = true;
break;
}
}
if (q[0] == '.')
return true;
}
if (need_prefix)
return strjoin("_", p);
return false;
}
return strdup(p);
int cg_escape(const char *p, char **ret) {
_cleanup_free_ char *n = NULL;
/* This implements very minimal escaping for names to be used as file names in the cgroup tree: any
* name which might conflict with a kernel name or is prefixed with '_' is prefixed with a '_'. That
* way, when reading cgroup names it is sufficient to remove a single prefixing underscore if there
* is one. */
/* The return value of this function (unlike cg_unescape()) needs free()! */
if (cg_needs_escape(p)) {
n = strjoin("_", p);
if (!n)
return -ENOMEM;
if (!filename_is_valid(n)) /* became invalid due to the prefixing? Or contained things like a slash that cannot be fixed by prefixing? */
return -EINVAL;
} else {
n = strdup(p);
if (!n)
return -ENOMEM;
}
*ret = TAKE_PTR(n);
return 0;
}
char *cg_unescape(const char *p) {
@@ -1698,9 +1710,9 @@ int cg_slice_to_path(const char *unit, char **ret) {
if (!unit_name_is_valid(n, UNIT_NAME_PLAIN))
return -EINVAL;
escaped = cg_escape(n);
if (!escaped)
return -ENOMEM;
r = cg_escape(n, &escaped);
if (r < 0)
return r;
if (!strextend(&s, escaped, "/"))
return -ENOMEM;
@@ -1708,15 +1720,14 @@ int cg_slice_to_path(const char *unit, char **ret) {
dash = strchr(dash+1, '-');
}
e = cg_escape(unit);
if (!e)
return -ENOMEM;
r = cg_escape(unit, &e);
if (r < 0)
return r;
if (!strextend(&s, e))
return -ENOMEM;
*ret = TAKE_PTR(s);
return 0;
}
+2 -1
View File
@@ -279,7 +279,8 @@ int cg_pid_get_user_slice(pid_t pid, char **slice);
int cg_path_decode_unit(const char *cgroup, char **unit);
char *cg_escape(const char *p);
bool cg_needs_escape(const char *p);
int cg_escape(const char *p, char **ret);
char *cg_unescape(const char *p) _pure_;
bool cg_controller_is_valid(const char *p);