Package: libcgroup3
Version: 3.1.0-2+b2
Severity: wishlist
Please add minimum and maximum UID selectors to cgrules.conf,
so could use lines like
>=1000 cpu student/%u
to match UIDs 1000 and above, and
<=999 * * ignore
to match UIDs up to 999.
The patch below seems to do exactly this.
This patch also includes my fix for #1113911 .
Sometime soon I will generate a patch for upstream V3.2 and report there.
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: i386 (x86_64)
Kernel: Linux 6.12+pk13.01 (SMP w/4 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/doc/man/cgrules.conf.5 b-3.1.0-2/doc/man/cgrules.conf.5
--- a-3.1.0-2/doc/man/cgrules.conf.5 2025-09-05 16:11:37.300790881 +1000
+++ b-3.1.0-2/doc/man/cgrules.conf.5 2025-09-06 13:30:35.077854928 +1000
@@ -18,10 +18,10 @@ Rules have two formats:
.in +4n
.nf
-<user> <controllers> <destination>
+<user> <controllers> <destination> <options>
.fi
.nf
-<user>:<process name> <controllers> <destination>
+<user>:<process name> <controllers> <destination> <options>
.fi
.in
@@ -32,10 +32,13 @@ can be:
.nf
- a user name
- a group name with @group syntax
+ - a minimum or maximum UID with >=number or <=number syntax
- the wildcard '*', for any user or group
- '%', which is equivalent to "ditto" (useful for
multi-line rules where different cgroups need to be
specified for various hierarchies for a single user)
+ (Multi-line is probably useless for CGROUPv2, and is
+ probably mis-handled by libcgroup code: do not use.)
.fi
.I process name
@@ -43,6 +46,7 @@ is optional and it can be:
.nf
- a process name
- a full command path of a process
+ - partial process or command path name with part* syntax
.fi
.I controllers
@@ -67,6 +71,16 @@ can be:
'\\' can be used to escape '%'
.fi
+
+.I options
+is optional and it can be:
+.nf
+ - "ignore" - if a process matches a rule with the "ignore" option,
+ then it will be ignored by cgrulesengd. cgrulesengd processing
+ will stop for this process, and all rules below this rule will
+ not affect it.
+.fi
+
First rule which matches the criteria will be executed.
Any text starting with '#' is considered as a start of comment line and is
@@ -123,13 +137,37 @@ at the end of the list. It will put a ta
previous rules to default/ control group.
.nf
-@students cpu,cpuacct students/%u
+@students cpu,cpuacct students/%u
.fi
Processes in cpu and cpuacct subsystems started by anybody from students group
-belong to group students/name. Where "name" is user name of owner of the
-process.
+belong to control group students/name, where "name" is user name of owner of
+the process.
+
+.nf
+root * * ignore
+daemon * * ignore
+www-data * * ignore
+sshd * * ignore
+ ...
+@faculty cpu faculty/%u
+* cpu student/%u
+.fi
+ "System" processes are un-touched, those started by faculty members
+belong to faculty/name, those of others e.g. students to student/name.
+Need to enumerate all system users.
+.nf
+<=999 * * ignore
+@faculty cpu faculty/%u
+* cpu student/%u
+.fi
+Probably same effect as previous example, but more succint.
+.nf
+@faculty cpu faculty/%u
+>=1000 cpu student/%u
+.fi
+Same again.
.SH FILES
.LP
@@ -151,15 +189,4 @@ default libcgroup configuration files di
cgconfig.conf (5), cgclassify (1), cgred.conf (5), cgrules.d (5)
.SH BUGS
-
-
-
-
-
-
-
-
-
-
-
-
+Hopefully none... but look in code for comments to the contrary!
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-06 09:23:28.443710391 +1000
@@ -609,6 +609,8 @@ static int cgroup_parse_rules_file(char
char options[CG_OPTIONS_MAX] = { '\0' };
uid_t uid = CGRULE_INVALID;
gid_t gid = CGRULE_INVALID;
+ uid_t minuid = CGRULE_INVALID;
+ uid_t maxuid = CGRULE_INVALID;
bool has_options = false;
size_t len_username;
int len_procname;
@@ -705,6 +707,14 @@ static int cgroup_parse_rules_file(char
strncpy(user, key, len_username);
user[sizeof(user) - 1] = '\0';
+
+ /*
+ * PSz BUG ALERT:
+ * The values we use for CGRULE_INVALID and CGRULE_WILD
((uid_t) -1 and -2)
+ * are legit UID values, and may clash: a problem if there is a
process
+ * with UID -1, or if we tried to set a rule for UID -2.
+ * On small systems where uid_t is 16 bits, the usual
nobody=65534 is -2.
+ */
/*
* Next, check the user/group. If it's a % sign, then we are
* continuing another rule and UID/GID should not be reset.
@@ -727,8 +737,10 @@ static int cgroup_parse_rules_file(char
if (grp) {
uid = CGRULE_INVALID;
gid = grp->gr_gid;
+ minuid = CGRULE_INVALID;
+ maxuid = CGRULE_INVALID;
} else {
- cgroup_warn("Entry for %s not found. Skipping
rule on line %d.\n",
+ cgroup_warn("Entry for group %s not found.
Skipping rule on line %d.\n",
itr, linenum);
skipped = true;
continue;
@@ -737,12 +749,46 @@ static int cgroup_parse_rules_file(char
/* Special wildcard rule. */
uid = CGRULE_WILD;
gid = CGRULE_WILD;
+ minuid = CGRULE_INVALID;
+ maxuid = CGRULE_INVALID;
+ } else if (strncmp(user, ">=", 2) == 0) {
+ /* New minUID rule. */
+ itr = &(user[2]);
+ i = atoi(itr);
+ if (i > 0 && i < CGRULE_WILD) {
+ uid = CGRULE_INVALID;
+ gid = CGRULE_INVALID;
+ minuid = (uid_t)i;
+ maxuid = CGRULE_INVALID;
+ } else {
+ cgroup_warn("Invalid minUID in %s. Skipping
rule on line %d.\n",
+ user, linenum);
+ skipped = true;
+ continue;
+ }
+ } else if (strncmp(user, "<=", 2) == 0) {
+ /* New maxUID rule. */
+ itr = &(user[2]);
+ i = atoi(itr);
+ if (i > 0 && i < CGRULE_WILD) {
+ uid = CGRULE_INVALID;
+ gid = CGRULE_INVALID;
+ minuid = CGRULE_INVALID;
+ maxuid = (uid_t)i;
+ } else {
+ cgroup_warn("Invalid maxUID in %s. Skipping
rule on line %d.\n",
+ user, linenum);
+ skipped = true;
+ continue;
+ }
} else if (*itr != '%') {
/* New UID rule. */
pwd = getpwnam(user);
if (pwd) {
uid = pwd->pw_uid;
gid = CGRULE_INVALID;
+ minuid = CGRULE_INVALID;
+ maxuid = CGRULE_INVALID;
} else {
cgroup_warn("Entry for %s not found. Skipping
rule on line %d.\n",
user, linenum);
@@ -772,7 +818,9 @@ static int cgroup_parse_rules_file(char
}
}
- if (uid == muid || gid == mgid || uid == CGRULE_WILD)
+ if (uid == muid || gid == mgid || uid == CGRULE_WILD ||
+ (minuid != CGRULE_INVALID && muid >= minuid) ||
+ (maxuid != CGRULE_INVALID && muid <= maxuid) )
matched = true;
if (!cache) {
@@ -784,9 +832,18 @@ static int cgroup_parse_rules_file(char
* If there is a rule based on process name,
* it should be matched with mprocname.
*/
+ /*
+ * PSz BUG ALERT:
+ * procname match different from cgrulesengd:
+ * - does not accept missing (NULL) mprocname
as match
+ * - does not handle wildcard in procname
+ * (and wonder about RT status for ignore_rt in
V3.2).
+ */
if (!mprocname) {
uid = CGRULE_INVALID;
gid = CGRULE_INVALID;
+ minuid = CGRULE_INVALID;
+ maxuid = CGRULE_INVALID;
matched = false;
continue;
}
@@ -795,6 +852,8 @@ static int cgroup_parse_rules_file(char
if (strcmp(mprocname, procname) &&
strcmp(mproc_base, procname)) {
uid = CGRULE_INVALID;
gid = CGRULE_INVALID;
+ minuid = CGRULE_INVALID;
+ maxuid = CGRULE_INVALID;
matched = false;
free(mproc_base);
continue;
@@ -818,6 +877,8 @@ static int cgroup_parse_rules_file(char
newrule->uid = uid;
newrule->gid = gid;
+ newrule->minuid = minuid;
+ newrule->maxuid = maxuid;
newrule->is_ignore = false;
len_username = min(len_username, sizeof(newrule->username) - 1);
@@ -3982,148 +4043,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 +4057,137 @@ 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))
+ /* 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.
+ * I guess that the idea of % with separate controllers
+ * or destinations is invalid with CGROUPv2, anyway.
+ */
+ 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;
+
+ /* Matching minUID or maxUID */
+ if (ret->minuid != CGRULE_INVALID && uid >= ret->minuid)
+ break;
+ if (ret->maxuid != CGRULE_INVALID && uid <= ret->maxuid)
+ break;
+
+ /* If this is a group rule, the UID might be a member.
*/
/*
- * This pid matched a rule that instructs the
- * cgrules daemon to ignore this process.
+ * PSz DOCUMENTATION BUG ALERT - Should document that
"default"
+ * membership is checked, not the result of any
setgroups()
*/
+ 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])))
+ goto grpmatched;
+ }
+ }
+ /* Did not match */
+ goto trynext;
+ grpmatched:
break;
- if (ret->is_ignore) {
- /*
- * The rule currently being examined is an ignore
- * rule, but it didn't match this pid. Move on to
- * the next rule
- */
- ret = ret->next;
- continue;
}
- 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))
+ * 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 +4685,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)
{
@@ -4765,6 +4770,16 @@ void cgroup_print_rules_config(FILE *fp)
else
fprintf(fp, " GID: %d\n", itr->gid);
+ if (itr->minuid == CGRULE_INVALID)
+ fprintf(fp, " minUID: N/A\n");
+ else
+ fprintf(fp, " minUID: %d\n", itr->minuid);
+
+ if (itr->maxuid == CGRULE_INVALID)
+ fprintf(fp, " maxUID: N/A\n");
+ else
+ fprintf(fp, " maxUID: %d\n", itr->maxuid);
+
fprintf(fp, " DEST: %s\n", itr->destination);
fprintf(fp, " CONTROLLERS:\n");
diff -r -u -p a-3.1.0-2/src/libcgroup-internal.h
b-3.1.0-2/src/libcgroup-internal.h
--- a-3.1.0-2/src/libcgroup-internal.h 2023-07-29 06:05:21.000000000 +1000
+++ b-3.1.0-2/src/libcgroup-internal.h 2025-09-06 07:32:27.101136272 +1000
@@ -155,6 +155,8 @@ struct cgroup_rules_data {
struct cgroup_rule {
uid_t uid;
gid_t gid;
+ uid_t minuid;
+ uid_t maxuid;
bool is_ignore;
char *procname;
char username[LOGIN_NAME_MAX];
@@ -404,8 +406,6 @@ void cgroup_free_controller(struct cgrou
int cgroup_parse_rules_options(char *options, struct cgroup_rule * const rule);
int cg_get_cgroups_from_proc_cgroups(pid_t pid, char *cgroup_list[], char
*controller_list[],
int list_len);
-bool cgroup_compare_ignore_rule(const struct cgroup_rule * const rule, pid_t
pid,
- const char * const procname);
bool cgroup_compare_wildcard_procname(const char * const rule_procname,
const char * const procname);
int cgroup_process_v1_mnt(char *controllers[], struct mntent *ent, int
*mnt_tbl_idx);