From a0cb3d075efdae10cc94fcc99c0e5f9234af61bc Mon Sep 17 00:00:00 2001 From: Richard Weinberger Date: Mon, 23 Feb 2026 21:19:21 +0100 Subject: [PATCH] usermod: Add option to automatically find subordinate IDs Tools such as useradd(8) automatically select subordinate UID and GID ranges based on settings in login.defs. But when one wants to add subordinate IDs to an existing user, these ranges have to be specified manually using the -w and -v options of usermod(8). Add a new -S / --add-subids option to usermod(8) which will, just like useradd(8), find a range based on the settings in login.defs. Signed-off-by: Richard Weinberger --- lib/find_new_sub_gids.c | 6 ++--- lib/find_new_sub_uids.c | 6 ++--- lib/prototypes.h | 4 ++-- man/usermod.8.xml | 13 +++++++++++ src/usermod.c | 52 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 72 insertions(+), 9 deletions(-) diff --git a/lib/find_new_sub_gids.c b/lib/find_new_sub_gids.c index ee85c4f354..dda80b0972 100644 --- a/lib/find_new_sub_gids.c +++ b/lib/find_new_sub_gids.c @@ -25,11 +25,11 @@ * * Return 0 on success, -1 if no unused GIDs are available. */ -int find_new_sub_gids (gid_t *range_start, unsigned long *range_count) +int find_new_sub_gids (id_t *range_start, unsigned long *range_count) { unsigned long min, max; unsigned long count; - gid_t start; + id_t start; assert (range_start != NULL); assert (range_count != NULL); @@ -47,7 +47,7 @@ int find_new_sub_gids (gid_t *range_start, unsigned long *range_count) } start = sub_gid_find_free_range(min, max, count); - if (start == (gid_t)-1) { + if (start == (id_t)-1) { fprintf (log_get_logfd(), _("%s: Can't get unique subordinate GID range\n"), log_get_progname()); diff --git a/lib/find_new_sub_uids.c b/lib/find_new_sub_uids.c index 52dbee3831..07a60e3cf9 100644 --- a/lib/find_new_sub_uids.c +++ b/lib/find_new_sub_uids.c @@ -25,11 +25,11 @@ * * Return 0 on success, -1 if no unused UIDs are available. */ -int find_new_sub_uids (uid_t *range_start, unsigned long *range_count) +int find_new_sub_uids (id_t *range_start, unsigned long *range_count) { unsigned long min, max; unsigned long count; - uid_t start; + id_t start; assert (range_start != NULL); assert (range_count != NULL); @@ -47,7 +47,7 @@ int find_new_sub_uids (uid_t *range_start, unsigned long *range_count) } start = sub_uid_find_free_range(min, max, count); - if (start == (uid_t)-1) { + if (start == (id_t)-1) { fprintf (log_get_logfd(), _("%s: Can't get unique subordinate UID range\n"), log_get_progname()); diff --git a/lib/prototypes.h b/lib/prototypes.h index 162804da13..411f8d559c 100644 --- a/lib/prototypes.h +++ b/lib/prototypes.h @@ -133,10 +133,10 @@ extern int find_new_uid (bool sys_user, #ifdef ENABLE_SUBIDS /* find_new_sub_gids.c */ -extern int find_new_sub_gids (gid_t *range_start, unsigned long *range_count); +extern int find_new_sub_gids (id_t *range_start, unsigned long *range_count); /* find_new_sub_uids.c */ -extern int find_new_sub_uids (uid_t *range_start, unsigned long *range_count); +extern int find_new_sub_uids (id_t *range_start, unsigned long *range_count); #endif /* ENABLE_SUBIDS */ /* getgr_nam_gid.c */ diff --git a/man/usermod.8.xml b/man/usermod.8.xml index 2c704ab5b2..c84d10f247 100644 --- a/man/usermod.8.xml +++ b/man/usermod.8.xml @@ -501,6 +501,19 @@ + + + , + + + + Add subordinate uids and gids to the user's account. + + + An appropriate uid and gid range is automatically selected from /etc/login.defs defaults. + + + ,  SEUSER diff --git a/src/usermod.c b/src/usermod.c index 13c9e1b414..19770d6315 100644 --- a/src/usermod.c +++ b/src/usermod.c @@ -161,6 +161,7 @@ static bool Vflg = false, /* delete subordinate uids */ wflg = false, /* add subordinate gids */ Wflg = false, /* delete subordinate gids */ + Sflg = false, /* auto add subordinate ids */ #endif /* ENABLE_SUBIDS */ uflg = false, /* specify new user ID */ Uflg = false; /* unlock the password */ @@ -375,6 +376,32 @@ prepend_range(const char *str, struct id_range_list_entry **head) *head = entry; return 1; } + +static int +find_range(struct id_range_list_entry **head, + int (*find_fn)(id_t *range_start, unsigned long *range_count)) +{ + struct id_range_list_entry *entry; + struct id_range range; + unsigned long count; + + if (find_fn(&range.first, &count) != 0) + return 0; + + range.last = range.first + count; + + entry = malloc_T(1, struct id_range_list_entry); + if (entry == NULL) { + fprintf(stderr, "%s: malloc: %s\n", Prog, strerrno()); + return 0; + } + + entry->next = *head; + entry->range = range; + *head = entry; + + return 1; +} #endif /* ENABLE_SUBIDS */ /* @@ -421,6 +448,7 @@ usage (int status) (void) fputs (_(" -V, --del-subuids FIRST-LAST remove range of subordinate uids\n"), usageout); (void) fputs (_(" -w, --add-subgids FIRST-LAST add range of subordinate gids\n"), usageout); (void) fputs (_(" -W, --del-subgids FIRST-LAST remove range of subordinate gids\n"), usageout); + (void) fputs (_(" -S, --add-subids add entries to sub[ud]id based on system defaults\n"), usageout); #endif /* ENABLE_SUBIDS */ #ifdef WITH_SELINUX (void) fputs (_(" -Z, --selinux-user SEUSER new SELinux user mapping for the user account\n"), usageout); @@ -1038,6 +1066,7 @@ process_flags(int argc, char **argv, struct option_flags *flags) {"del-subuids", required_argument, NULL, 'V'}, {"add-subgids", required_argument, NULL, 'w'}, {"del-subgids", required_argument, NULL, 'W'}, + {"add-subids", no_argument, NULL, 'S'}, #endif /* ENABLE_SUBIDS */ #ifdef WITH_SELINUX {"selinux-user", required_argument, NULL, 'Z'}, @@ -1048,7 +1077,7 @@ process_flags(int argc, char **argv, struct option_flags *flags) while ((c = getopt_long (argc, argv, "abc:d:e:f:g:G:hl:Lmop:rR:s:u:UP:" #ifdef ENABLE_SUBIDS - "v:w:V:W:" + "v:w:V:W:S" #endif /* ENABLE_SUBIDS */ #ifdef WITH_SELINUX "Z:" @@ -1245,6 +1274,12 @@ process_flags(int argc, char **argv, struct option_flags *flags) } Wflg = true; break; + case 'S': + Sflg = true; + /* -S implies vflg and wflg */ + vflg = true; + wflg = true; + break; #endif /* ENABLE_SUBIDS */ #ifdef WITH_SELINUX case 'Z': @@ -2234,6 +2269,21 @@ int main (int argc, char **argv) grp_update (process_selinux); } #ifdef ENABLE_SUBIDS + if (Sflg) { + if (find_range (&add_sub_uids, find_new_sub_uids) == 0) { + fprintf (stderr, + _("%s: unable to find new subordinate uid range\n"), + Prog); + fail_exit (E_SUB_UID_UPDATE, process_selinux); + } + if (find_range (&add_sub_gids, find_new_sub_gids) == 0) { + fprintf (stderr, + _("%s: unable to find new subordinate gid range\n"), + Prog); + fail_exit (E_SUB_GID_UPDATE, process_selinux); + } + } + if (Vflg) { struct id_range_list_entry *ptr;