Package: libcgroup3
Version: 3.1.0-2+b2
Severity: normal
Tags: patch upstream
Now at trixie, I am using systemd and CGROUPv2 and also using
cgrulesengd quite successfully. While pondering how to better avoid
conflicts with systemd, noticed that cgrules.conf allows the "ignore"
option (though undocumented, documented only since upstream V3.2).
Trying the "ignore" option in cgrules.conf, I found that those rule
lines were mis-handled (simply ignored, skipped) by cgrulesengd. The
cause seems wrong logic in libcgroup3 code, and the patch below seems
to fix the issue.
The issue affects upstream code, including their V3.2 version. I have
not yet reported upstream since I have not fully tested that version.
Cheers, Paul
Paul Szabo [email protected] www.maths.usyd.edu.au/u/psz
School of Mathematics and Statistics University of Sydney Australia
-- System Information:
Debian Release: 13.0
APT prefers stable-security
APT policy: (500, 'stable-security'), (500, 'stable')
Architecture: amd64 (x86_64)
Kernel: Linux 6.12+pk13.01 (SMP w/12 CPU threads; PREEMPT)
Locale: LANG=C.UTF-8, LC_CTYPE=C.UTF-8 (charmap=UTF-8), LANGUAGE not set
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
Versions of packages libcgroup3 depends on:
ii libc6 2.41-12
ii libsystemd0 257.7-1
libcgroup3 recommends no packages.
libcgroup3 suggests no packages.
-- no debconf information
diff -r -u -p a-3.1.0-2/src/api.c b-3.1.0-2/src/api.c
--- a-3.1.0-2/src/api.c 2023-07-29 06:05:21.000000000 +1000
+++ b-3.1.0-2/src/api.c 2025-09-04 10:17:20.637089684 +1000
@@ -3982,148 +3982,6 @@ static int cgroup_find_matching_controll
}
/**
- * Evaluates if rule is an ignore rule and the pid/procname match this rule.
- * If rule is an ignore rule and the pid/procname match this rule, then this
- * function returns true. Otherwise it returns false.
- *
- * @param rule The rule being evaluated
- * @param pid PID of the process being compared
- * @param procname Process name of the process being compared
- * @return True if the rule is an ignore rule and this pid/procname
- * match the rule. False otherwise
- */
-STATIC bool cgroup_compare_ignore_rule(const struct cgroup_rule * const rule,
pid_t pid,
- const char * const procname)
-{
- char *controller_list[MAX_MNT_ELEMENTS] = { '\0' };
- char *cgroup_list[MAX_MNT_ELEMENTS] = { '\0' };
- int rule_matching_controller_idx;
- int cgroup_list_matching_idx;
- bool found_match = false;
- char *token, *saveptr;
- int ret, i;
-
- if (!rule->is_ignore)
- /* Immediately return if the 'ignore' option is not set */
- return false;
-
- ret = cg_get_cgroups_from_proc_cgroups(pid, cgroup_list,
controller_list,
- MAX_MNT_ELEMENTS);
- if (ret < 0)
- goto out;
-
- ret = cgroup_find_matching_destination(cgroup_list, rule->destination,
- &cgroup_list_matching_idx);
- if (ret < 0)
- /* No cgroups matched */
- goto out;
-
- token = strtok_r(controller_list[cgroup_list_matching_idx], ",",
&saveptr);
- while (token != NULL) {
-
- ret = cgroup_find_matching_controller(rule->controllers, token,
-
&rule_matching_controller_idx);
- if (ret == 0)
- /* We found a matching controller */
- break;
-
- token = strtok_r(NULL, ",", &saveptr);
- }
-
- if (!rule->procname) {
- /*
- * The rule procname is empty, thus it's a wildcard and
- * all processes match.
- */
- found_match = true;
- goto out;
- }
-
- if (!strcmp(rule->procname, procname)) {
- found_match = true;
- goto out;
- }
-
- if (cgroup_compare_wildcard_procname(rule->procname, procname))
- found_match = true;
-
-out:
- for (i = 0; i < MAX_MNT_ELEMENTS; i++) {
- if (controller_list[i])
- free(controller_list[i]);
- if (cgroup_list[i])
- free(cgroup_list[i]);
- }
-
- return found_match;
-}
-
-static struct cgroup_rule *cgroup_find_matching_rule_uid_gid(uid_t uid, gid_t
gid,
- struct cgroup_rule
*rule)
-{
- /* Temporary user data */
- struct passwd *usr = NULL;
-
- /* Temporary group data */
- struct group *grp = NULL;
-
- /* Temporary string pointer */
- char *sp = NULL;
-
- /* Loop variable */
- int i = 0;
-
- while (rule) {
- /* Skip "%" which indicates continuation of previous rule. */
- if (rule->username[0] == '%') {
- rule = rule->next;
- continue;
- }
- /* The wildcard rule always matches. */
- if ((rule->uid == CGRULE_WILD) && (rule->gid == CGRULE_WILD))
- return rule;
-
- /* This is the simple case of the UID matching. */
- if (rule->uid == uid)
- return rule;
-
- /* This is the simple case of the GID matching. */
- if (rule->gid == gid)
- return rule;
-
- /* If this is a group rule, the UID might be a member. */
- if (rule->username[0] == '@') {
- /* Get the group data. */
- sp = &(rule->username[1]);
- grp = getgrnam(sp);
- if (!grp) {
- rule = rule->next;
- continue;
- }
-
- /* Get the data for UID. */
- usr = getpwuid(uid);
- if (!usr) {
- rule = rule->next;
- continue;
- }
-
- /* If UID is a member of group, we matched. */
- for (i = 0; grp->gr_mem[i]; i++) {
- if (!(strcmp(usr->pw_name, grp->gr_mem[i])))
- return rule;
- }
- }
-
- /* If we haven't matched, try the next rule. */
- rule = rule->next;
- }
-
- /* If we get here, no rules matched. */
- return NULL;
-}
-
-/**
* Finds the first rule in the cached list that matches the given UID, GID
* or PROCESS NAME, and returns a pointer to that rule.
* This function uses rl_lock.
@@ -4138,53 +3996,127 @@ static struct cgroup_rule *cgroup_find_m
static struct cgroup_rule *cgroup_find_matching_rule(uid_t uid, gid_t gid,
pid_t pid,
const char *procname)
{
- /* Return value */
+ /* Return value (rule) */
struct cgroup_rule *ret = rl.head;
+
+ /* Temporary data */
+ struct passwd *usr = NULL;
+ struct group *grp = NULL;
+ char *sp = NULL;
+ int i = 0;
char *base = NULL;
pthread_rwlock_wrlock(&rl_lock);
while (ret) {
- ret = cgroup_find_matching_rule_uid_gid(uid, gid, ret);
- if (!ret)
- break;
- if (cgroup_compare_ignore_rule(ret, pid, procname))
- /*
- * This pid matched a rule that instructs the
- * cgrules daemon to ignore this process.
- */
- break;
- if (ret->is_ignore) {
+ /* PSz Do UID/GID matching inline, no need for separate routine
*/
+
+ /* Skip "%" which indicates continuation of previous rule. */
+ /*
+ * PSz BUG ALERT: simply skip % lines: not evaluate and
+ * "add" to previous, not handle as multi-line rule.
+ * Was like that, is like that also in upstream V3.2.
+ */
+ if (ret->username[0] == '%') {
+ goto trynext;
+ }
+
+ /* UID/GID matching - in a bogus while loop so can break */
+ while(1) {
+ /* The wildcard rule always matches. */
+ if ((ret->uid == CGRULE_WILD) && (ret->gid ==
CGRULE_WILD))
+ break;
+
+ /* This is the simple case of the UID matching. */
+ if (ret->uid == uid)
+ break;
+
+ /* This is the simple case of the GID matching. */
+ if (ret->gid == gid)
+ break;
+
+ /* If this is a group rule, the UID might be a member.
*/
/*
- * The rule currently being examined is an ignore
- * rule, but it didn't match this pid. Move on to
- * the next rule
+ * PSz DOCUMENTATION BUG ALERT - Should document that
"default"
+ * membership is checked, not the result of any
setgroups()
*/
- ret = ret->next;
- continue;
+ if (ret->username[0] == '@') {
+ /* Get the group data. */
+ sp = &(ret->username[1]);
+ grp = getgrnam(sp);
+ if (!grp)
+ goto trynext;
+
+ /* Get the data for UID. */
+ usr = getpwuid(uid);
+ if (!usr)
+ goto trynext;
+
+ /* If UID is a member of group, we matched. */
+ for (i = 0; grp->gr_mem[i]; i++) {
+ if (!(strcmp(usr->pw_name,
grp->gr_mem[i])))
+ break;
+ }
+ }
+ /* Did not match */
+ goto trynext;
}
- if (!procname)
- /* If procname is NULL, return a rule matching UID or
GID. */
- break;
- if (!ret->procname)
- /* If no process name in a rule, that means wildcard */
- break;
- if (!strcmp(ret->procname, procname))
- break;
- base = cgroup_basename(procname);
- if (!strcmp(ret->procname, base))
- /* Check a rule of basename. */
- break;
- if (cgroup_compare_wildcard_procname(ret->procname, procname))
- break;
+ /* procname matching - in a bogus while loop so can break */
+ while(1) {
+ /* If procname is NULL, we take it as matched */
+ if (!procname)
+ break;
+ /* If no process name in a rule then any will match */
+ if (!ret->procname)
+ break;
+ /* Simple match */
+ if (!strcmp(ret->procname, procname))
+ break;
+ /* Wildcard match */
+ if (cgroup_compare_wildcard_procname(ret->procname,
procname))
+ break;
+
+ /* Check basename (as rules are most commonly written)
*/
+ /* Try both "simple" and wildcard matches */
+ base = cgroup_basename(procname);
+ if (!strcmp(ret->procname, base))
+ break;
+ if (cgroup_compare_wildcard_procname(ret->procname,
base))
+ break;
+
+ /* Did not match */
+ free(base);
+ base = NULL;
+ goto trynext;
+ }
+ if (base) {
+ free(base);
+ base = NULL;
+ }
+
+ /*
+ * PSz Upstream V3.2 knows about ignore and ignore_rt options,
+ * and should check whether the process matches. Documentation
+ * is unclear about what to do if both ignore and ignore_rt are
+ * specified, or if the RT status of process does not match.
+ * What upstream V3.2 should do:
+ * if ((rule->is_ignore & CGRULE_OPT_IGNORE) &&
cgroup_is_rt_task(pid) == false)
+ * break;
+ * if ((rule->is_ignore & CGRULE_OPT_IGNORE_RT) &&
cgroup_is_rt_task(pid) == true)
+ * break;
+ *
+ * V3.1 knows about a single ignore option, to match all
processes:
+ * nothing to check here.
+ * Done all checks - we matched, found the right rule.
+ */
+ break;
+
+
+trynext:
ret = ret->next;
- free(base);
- base = NULL;
}
pthread_rwlock_unlock(&rl_lock);
- if (base)
- free(base);
return ret;
}
@@ -4682,6 +4614,8 @@ finished:
* This function may be called after creating new control groups to move
* running PIDs into the newly created control groups.
* @return 0 on success, < 0 on error
+ * PSz: Do not use if caller would not have CGFLAG_USECACHE.
+ * PSz: Used from cgrulesengd.c only, so it is OK to use CGFLAG_USECACHE below.
*/
int cgroup_change_all_cgroups(void)
{