From f98304fff1e2c5660e3ba88f59f08fb3a0eaba09 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 01:21:49 +0800 Subject: [PATCH 01/20] lib/nss.c: seperate nss lib open and check logic Move the nss module open and check logic to a seperate static function. Signed-off-by: Yi Kuo --- lib/nss.c | 84 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/lib/nss.c b/lib/nss.c index 5957390741..84a4499e74 100644 --- a/lib/nss.c +++ b/lib/nss.c @@ -48,6 +48,55 @@ static void nss_exit(void) { } } +static struct subid_nss_ops * +open_and_check_nss_module(const char *libname) { + void *h; + struct subid_nss_ops *nss_ops; + + h = dlopen(libname, RTLD_LAZY); + if (!h) { + fprintf(log_get_logfd(), "Error opening %s: %s\n", libname, dlerror()); + fprintf(log_get_logfd(), "Using files\n"); + return NULL; + } + + nss_ops = malloc_T(1, struct subid_nss_ops); + if (!nss_ops) { + fprintf(log_get_logfd(), "Failed to allocate memory for subid NSS module %s\n", libname); + dlclose(h); + return NULL; + } + + nss_ops->has_range = dlsym(h, "shadow_subid_has_range"); + if (!nss_ops->has_range) { + fprintf(log_get_logfd(), "%s did not provide @has_range@\n", libname); + goto close_lib; + } + nss_ops->list_owner_ranges = dlsym(h, "shadow_subid_list_owner_ranges"); + if (!nss_ops->list_owner_ranges) { + fprintf(log_get_logfd(), "%s did not provide @list_owner_ranges@\n", libname); + goto close_lib; + } + nss_ops->find_subid_owners = dlsym(h, "shadow_subid_find_subid_owners"); + if (!nss_ops->find_subid_owners) { + fprintf(log_get_logfd(), "%s did not provide @find_subid_owners@\n", libname); + goto close_lib; + } + nss_ops->free = dlsym(h, "shadow_subid_free"); + if (!nss_ops->free) { + fprintf(log_get_logfd(), "%s did not provide @free@\n", libname); + goto close_lib; + } + + nss_ops->handle = h; + return nss_ops; + +close_lib: + dlclose(h); + free(nss_ops); + return NULL; +} + // nsswitch_path is an argument only to support testing. void nss_init(const char *nsswitch_path) { @@ -108,42 +157,13 @@ nss_init(const char *nsswitch_path) { goto null_subid; } stprintf_a(libname, "libsubid_%s.so", p); - h = dlopen(libname, RTLD_LAZY); - if (!h) { - fprintf(log_get_logfd(), "Error opening %s: %s\n", libname, dlerror()); - fprintf(log_get_logfd(), "Using files\n"); - goto null_subid; - } - subid_nss = malloc_T(1, struct subid_nss_ops); + subid_nss = open_and_check_nss_module(libname); if (!subid_nss) { - goto close_lib; - } - subid_nss->has_range = dlsym(h, "shadow_subid_has_range"); - if (!subid_nss->has_range) { - fprintf(log_get_logfd(), "%s did not provide @has_range@\n", libname); - goto close_lib; - } - subid_nss->list_owner_ranges = dlsym(h, "shadow_subid_list_owner_ranges"); - if (!subid_nss->list_owner_ranges) { - fprintf(log_get_logfd(), "%s did not provide @list_owner_ranges@\n", libname); - goto close_lib; - } - subid_nss->find_subid_owners = dlsym(h, "shadow_subid_find_subid_owners"); - if (!subid_nss->find_subid_owners) { - fprintf(log_get_logfd(), "%s did not provide @find_subid_owners@\n", libname); - goto close_lib; - } - subid_nss->free = dlsym(h, "shadow_subid_free"); - if (!subid_nss->free) { - fprintf(log_get_logfd(), "%s did not provide @subid_free@\n", libname); - goto close_lib; + fprintf(log_get_logfd(), "Failed to initialize subid NSS module %s\n", libname); + goto null_subid; } - subid_nss->handle = h; goto done; -close_lib: - dlclose(h); - free(subid_nss); null_subid: subid_nss = NULL; From 3b65abbe885a4fd0240e92a5a5f181101ca3ccb4 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 01:27:46 +0800 Subject: [PATCH 02/20] lib/nss.c, lib/prototypes.h: linked list for nss_ops Define subid_nss_db, which is a linked list wrapper for subid_nss_ops. Signed-off-by: Yi Kuo --- lib/nss.c | 8 ++++++++ lib/prototypes.h | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/lib/nss.c b/lib/nss.c index 84a4499e74..748121f5f1 100644 --- a/lib/nss.c +++ b/lib/nss.c @@ -35,6 +35,7 @@ static atomic_flag nss_init_started; static atomic_bool nss_init_completed; static struct subid_nss_ops *subid_nss; +static struct subid_nss_db *subid_nss_db_head; bool nss_is_initialized() { return atomic_load(&nss_init_completed); @@ -180,3 +181,10 @@ struct subid_nss_ops *get_subid_nss_handle() { nss_init(NULL); return subid_nss; } + +struct subid_nss_db * +get_subid_nss_db(void) { + nss_init(NULL); + return subid_nss_db_head; +} + diff --git a/lib/prototypes.h b/lib/prototypes.h index 411f8d559c..5f1981b2d9 100644 --- a/lib/prototypes.h +++ b/lib/prototypes.h @@ -228,6 +228,11 @@ extern /*@null@*//*@only@*/struct passwd *get_my_pwent (void); extern void nss_init(const char *nsswitch_path); extern bool nss_is_initialized(void); +struct subid_nss_db { + struct subid_nss_ops *ops; + struct subid_nss_db *next; +}; + struct subid_nss_ops { /* * nss_has_range: does a user own a given subid range @@ -290,6 +295,7 @@ struct subid_nss_ops { }; extern struct subid_nss_ops *get_subid_nss_handle(void); +extern struct subid_nss_db *get_subid_nss_db(void); /* pam_pass_non_interactive.c */ From 25ebd6933084dbc5b12c34eb47a64136a2c4c1f2 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 01:40:21 +0800 Subject: [PATCH 03/20] lib/nss.c: parse multiple subid databases from nsswitch.conf Update nss_init() to parse and build a linked list of all databases specified in the "subid:" line of /etc/nsswitch.conf. Signed-off-by: Yi Kuo --- lib/nss.c | 76 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/lib/nss.c b/lib/nss.c index 748121f5f1..35fe76e96b 100644 --- a/lib/nss.c +++ b/lib/nss.c @@ -101,10 +101,14 @@ open_and_check_nss_module(const char *libname) { // nsswitch_path is an argument only to support testing. void nss_init(const char *nsswitch_path) { + struct subid_nss_db **tail = &subid_nss_db_head; + struct subid_nss_ops *ops; + struct subid_nss_db *new_db; + const char *delimiters = " \t\n"; + char *token; char *line = NULL, *p; char libname[64]; FILE *nssfp = NULL; - void *h; size_t len = 0; if (atomic_flag_test_and_set(&nss_init_started)) { @@ -136,37 +140,61 @@ nss_init(const char *nsswitch_path) { if (!strcaseprefix(line, "subid:")) continue; p = &line[6]; - p = stpspn(p, " \t\n"); + p = stpspn(p, delimiters); if (!streq(p, "")) break; p = NULL; } + if (p == NULL) { - goto null_subid; + // Use NULL to indicate the built-in "files" database + subid_nss_db_head = NULL; + goto done; + } + + while (NULL != (token = strsep(&p, delimiters))) { + if (*token == '\0') { + continue; + } + + if (streq(token, "files")) { + // Use NULL to indicate the built-in "files" database + ops = NULL; + } else { + if (stprintf_a(libname, "libsubid_%s.so", token) == -1) { + fprintf(log_get_logfd(), "Subid NSS module name too long: %s\n", token); + continue; + } + + ops = open_and_check_nss_module(libname); + if (!ops) { + continue; + } + } + + new_db = malloc_T(1, struct subid_nss_db); + if (!new_db) { + if (ops) { + dlclose(ops->handle); + free(ops); + ops = NULL; + } + + fprintf(log_get_logfd(), "Failed to allocate memory for subid NSS module %s, skipping\n", token); + continue; + } + + new_db->ops = ops; + new_db->next = NULL; + *tail = new_db; + tail = &new_db->next; } - if (stpsep(p, " \t\n") == NULL) { + + if (subid_nss_db_head == NULL) { + // No vaild NSS database loaded, using "files" only. + // NULL indicates the built-in "files" database, so we can continue, but log a warning. fprintf(log_get_logfd(), "No usable subid NSS module found, using files\n"); - // subid_nss has to be null here, but to ease reviews: - goto null_subid; - } - if (streq(p, "files")) { - goto null_subid; } - if (strlen(p) > 50) { - fprintf(log_get_logfd(), "Subid NSS module name too long (longer than 50 characters): %s\n", p); - fprintf(log_get_logfd(), "Using files\n"); - goto null_subid; - } - stprintf_a(libname, "libsubid_%s.so", p); - subid_nss = open_and_check_nss_module(libname); - if (!subid_nss) { - fprintf(log_get_logfd(), "Failed to initialize subid NSS module %s\n", libname); - goto null_subid; - } - goto done; - -null_subid: - subid_nss = NULL; done: atomic_store(&nss_init_completed, true); From 4376b039ee6f2874336d7ad563ee8381c5d00077 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 01:46:55 +0800 Subject: [PATCH 04/20] lib/nss.c: free subid NSS database list on exit Update nss_exit() to traverse and release the subid_nss_db linked list. Signed-off-by: Yi Kuo --- lib/nss.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/nss.c b/lib/nss.c index 35fe76e96b..dff70bff42 100644 --- a/lib/nss.c +++ b/lib/nss.c @@ -41,11 +41,24 @@ bool nss_is_initialized() { return atomic_load(&nss_init_completed); } -static void nss_exit(void) { - if (nss_is_initialized() && subid_nss) { - dlclose(subid_nss->handle); - free(subid_nss); - subid_nss = NULL; +static void +nss_exit(void) { + struct subid_nss_db *current; + struct subid_nss_db *next; + + if (nss_is_initialized() && subid_nss_db_head) { + current = subid_nss_db_head; + while (current) { + next = current->next; + if (current->ops) { + dlclose(current->ops->handle); + free(current->ops); + } + free(current); + current = next; + } + + subid_nss_db_head = NULL; } } From 5c96c85226d00baa55ef500d137313b1a594c4fd Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 01:49:23 +0800 Subject: [PATCH 05/20] lib/nss.c, lib/prototypes.h: remove old NSS handle defs With the transition to a linked-list-based NSS database system, the singular 'subid_nss' handle and its accessor 'get_subid_nss_handle()' are now obsolete. Signed-off-by: Yi Kuo --- lib/nss.c | 6 ------ lib/prototypes.h | 1 - 2 files changed, 7 deletions(-) diff --git a/lib/nss.c b/lib/nss.c index dff70bff42..ee4c191fc4 100644 --- a/lib/nss.c +++ b/lib/nss.c @@ -34,7 +34,6 @@ static atomic_flag nss_init_started; static atomic_bool nss_init_completed; -static struct subid_nss_ops *subid_nss; static struct subid_nss_db *subid_nss_db_head; bool nss_is_initialized() { @@ -218,11 +217,6 @@ nss_init(const char *nsswitch_path) { } } -struct subid_nss_ops *get_subid_nss_handle() { - nss_init(NULL); - return subid_nss; -} - struct subid_nss_db * get_subid_nss_db(void) { nss_init(NULL); diff --git a/lib/prototypes.h b/lib/prototypes.h index 5f1981b2d9..17841483ca 100644 --- a/lib/prototypes.h +++ b/lib/prototypes.h @@ -294,7 +294,6 @@ struct subid_nss_ops { void *handle; }; -extern struct subid_nss_ops *get_subid_nss_handle(void); extern struct subid_nss_db *get_subid_nss_db(void); From 1b8aeaf040037e6829afb08dcf795e0aac316189 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 01:52:26 +0800 Subject: [PATCH 06/20] lib/nss.c: update comments and formatting Update the comments in nss.c to describe the new multi-database support. Reformat and reorder local variables in nss_init(). Reformat function definition of nss_is_initialized(). Signed-off-by: Yi Kuo --- lib/nss.c | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/lib/nss.c b/lib/nss.c index ee4c191fc4..8e71879ed9 100644 --- a/lib/nss.c +++ b/lib/nss.c @@ -25,18 +25,27 @@ // NSS plugin handling for subids // If nsswitch has a line like -// subid: sssd -// then sssd will be consulted for subids. Unlike normal NSS dbs, -// only one db is supported at a time. That's open to debate, but -// the subids are a pretty limited resource, and local files seem -// bound to step on any other allocations leading to insecure -// conditions. +// subid: sss +// then the sss module (libsubid_sss.so) will be consulted for subids. +// If nsswitch has a line specifying multiple databases, like: +// subid: sss files +// then databases will be consulted in the specified order. The search +// stops as soon as the user is found in a database, even if no subids +// are defined there. For example, if 'sss' knows the user but provides +// no subids, 'files' will not be consulted. +// +// While multiple databases are now supported, the subids are a pretty +// limited resource. Mixing local files with network allocations +// (like sssd) requires careful management. Misconfigurations would +// lead to overlapping ID mappings. Use with caution. + static atomic_flag nss_init_started; static atomic_bool nss_init_completed; static struct subid_nss_db *subid_nss_db_head; -bool nss_is_initialized() { +bool +nss_is_initialized(void) { return atomic_load(&nss_init_completed); } @@ -113,15 +122,16 @@ open_and_check_nss_module(const char *libname) { // nsswitch_path is an argument only to support testing. void nss_init(const char *nsswitch_path) { - struct subid_nss_db **tail = &subid_nss_db_head; - struct subid_nss_ops *ops; - struct subid_nss_db *new_db; - const char *delimiters = " \t\n"; - char *token; - char *line = NULL, *p; - char libname[64]; - FILE *nssfp = NULL; - size_t len = 0; + char libname[64]; + char *line = NULL; + char *p; + char *token; + FILE *nssfp = NULL; + size_t len = 0; + const char *delimiters = " \t\n"; + struct subid_nss_db *new_db; + struct subid_nss_db **tail = &subid_nss_db_head; + struct subid_nss_ops *ops; if (atomic_flag_test_and_set(&nss_init_started)) { // Another thread has started nss_init, wait for it to complete @@ -134,7 +144,7 @@ nss_init(const char *nsswitch_path) { nsswitch_path = NSSWITCH; // read nsswitch.conf to check for a line like: - // subid: files + // subid: sss files nssfp = fopen(nsswitch_path, "r"); if (!nssfp) { if (errno != ENOENT) @@ -222,4 +232,3 @@ get_subid_nss_db(void) { nss_init(NULL); return subid_nss_db_head; } - From 1861699dad55d183b22e365f3ce0c15d120faa9b Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 01:59:31 +0800 Subject: [PATCH 07/20] lib/subordinateio.c: want_subuid/gid_file: check for files in nss db list Update want_subuid_file() and want_subgid_file() to iterate through the subid NSS database list. These functions now return true if the local "files" database is present in the configuration. Signed-off-by: Yi Kuo --- lib/subordinateio.c | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/lib/subordinateio.c b/lib/subordinateio.c index acd3f1ffdc..168c3398dc 100644 --- a/lib/subordinateio.c +++ b/lib/subordinateio.c @@ -690,11 +690,23 @@ uid_t sub_uid_find_free_range(uid_t min, uid_t max, unsigned long count) */ bool want_subuid_file(void) { - if (get_subid_nss_handle() != NULL) - return false; + struct subid_nss_db *db; + if (getdef_ulong("SUB_UID_COUNT", 65536) == 0) return false; - return true; + + db = get_subid_nss_db(); + if (db == NULL) + return true; + + for (; db; db = db->next) { + if (db->ops == NULL) { + // Local "files" database is used. + return true; + } + } + + return false; } /* @@ -705,11 +717,23 @@ bool want_subuid_file(void) */ bool want_subgid_file(void) { - if (get_subid_nss_handle() != NULL) - return false; + struct subid_nss_db *db; + if (getdef_ulong("SUB_GID_COUNT", 65536) == 0) return false; - return true; + + db = get_subid_nss_db(); + if (db == NULL) + return true; + + for (; db; db = db->next) { + if (db->ops == NULL) { + // Local "files" database is used. + return true; + } + } + + return false; } static struct commonio_db subordinate_gid_db = { From c56db79714431b83ba128fb601274d177a1de2b8 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 02:03:30 +0800 Subject: [PATCH 08/20] lib/subordinateio.c: restrict subid mutations to files as first source Subid modification functions (add, remove, and range alloc/dealloc) are only supported if the local "files" database is the first source in the subid NSS list. Signed-off-by: Yi Kuo --- lib/subordinateio.c | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/subordinateio.c b/lib/subordinateio.c index 168c3398dc..50de23e1e6 100644 --- a/lib/subordinateio.c +++ b/lib/subordinateio.c @@ -647,7 +647,11 @@ bool have_sub_uids(const char *owner, uid_t start, unsigned long count) */ int sub_uid_add (const char *owner, uid_t start, unsigned long count) { - if (get_subid_nss_handle()) { + struct subid_nss_db *db; + + db = get_subid_nss_db(); + if (db && db->ops) { + // NSS module configured and the first database is not "files". errno = EOPNOTSUPP; return 0; } @@ -657,7 +661,11 @@ int sub_uid_add (const char *owner, uid_t start, unsigned long count) /* Return 1 on success. on failure, return 0 and set errno appropriately */ int sub_uid_remove (const char *owner, uid_t start, unsigned long count) { - if (get_subid_nss_handle()) { + struct subid_nss_db *db; + + db = get_subid_nss_db(); + if (db && db->ops) { + // NSS module configured and the first database is not "files". errno = EOPNOTSUPP; return 0; } @@ -809,7 +817,11 @@ bool local_sub_gid_assigned(const char *owner) */ int sub_gid_add (const char *owner, gid_t start, unsigned long count) { - if (get_subid_nss_handle()) { + struct subid_nss_db *db; + + db = get_subid_nss_db(); + if (db && db->ops) { + // NSS module configured and the first database is not "files". errno = EOPNOTSUPP; return 0; } @@ -819,7 +831,11 @@ int sub_gid_add (const char *owner, gid_t start, unsigned long count) /* Return 1 on success. on failure, return 0 and set errno appropriately */ int sub_gid_remove (const char *owner, gid_t start, unsigned long count) { - if (get_subid_nss_handle()) { + struct subid_nss_db *db; + + db = get_subid_nss_db(); + if (db && db->ops) { + // NSS module configured and the first database is not "files". errno = EOPNOTSUPP; return 0; } @@ -1050,11 +1066,16 @@ int find_subid_owners(unsigned long id, enum subid_type id_type, uid_t **uids) bool new_subid_range(struct subordinate_range *range, enum subid_type id_type, bool reuse) { struct commonio_db *db; + struct subid_nss_db *nss_db; const struct subordinate_range *r; bool ret; - if (get_subid_nss_handle()) + nss_db = get_subid_nss_db(); + if (nss_db && nss_db->ops) { + // NSS module configured and the first database is not "files". + errno = EOPNOTSUPP; return false; + } switch (id_type) { case ID_TYPE_UID: @@ -1123,10 +1144,15 @@ bool new_subid_range(struct subordinate_range *range, enum subid_type id_type, b bool release_subid_range(struct subordinate_range *range, enum subid_type id_type) { struct commonio_db *db; + struct subid_nss_db *nss_db; bool ret; - if (get_subid_nss_handle()) + nss_db = get_subid_nss_db(); + if (nss_db && nss_db->ops) { + // NSS module configured and the first database is not "files". + errno = EOPNOTSUPP; return false; + } switch (id_type) { case ID_TYPE_UID: From 23d79a9b31500bc742af39cd18c2ec4eb4307fc0 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 02:13:35 +0800 Subject: [PATCH 09/20] lib/subordinateio.c: have_sub_uids/gids: multi-provider lookup Update have_sub_uids() and have_sub_gids() to iterate through the configured NSS database list. The lookup logic now follows nsswitch semantics: the search continues to the next database only if the user does not exist in the current database. For the local files database, ensure the local subid files are opened (and closed if necessary) before performing the check. Signed-off-by: Yi Kuo --- lib/subordinateio.c | 104 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 88 insertions(+), 16 deletions(-) diff --git a/lib/subordinateio.c b/lib/subordinateio.c index 50de23e1e6..21f154a9d4 100644 --- a/lib/subordinateio.c +++ b/lib/subordinateio.c @@ -626,17 +626,53 @@ bool local_sub_uid_assigned(const char *owner) bool have_sub_uids(const char *owner, uid_t start, unsigned long count) { + enum subid_status status; + struct subid_nss_db *db; struct subid_nss_ops *h; + bool close_db = false; + bool exists; bool found; - enum subid_status status; - h = get_subid_nss_handle(); - if (h) { - status = h->has_range(owner, start, count, ID_TYPE_UID, &found); - if (status == SUBID_STATUS_SUCCESS && found) - return true; - return false; + + db = get_subid_nss_db(); + if (!db) { + // No NSS module configured, search local files only. + return have_range(&subordinate_uid_db, owner, start, count); } - return have_range (&subordinate_uid_db, owner, start, count); + + for (; db; db = db->next) { + h = db->ops; + if (h) { + status = h->has_range(owner, start, count, ID_TYPE_UID, &found); + if (status == SUBID_STATUS_SUCCESS) + return found; + if (status == SUBID_STATUS_UNKNOWN_USER) + continue; // User not found in this database, try the next one. + + return false; // Error occurred. + } else { + // Local "files" database + if (!subordinate_uid_db.isopen) { + if (sub_uid_open(O_RDONLY) == 0) + return false; + close_db = true; + } + + found = have_range(&subordinate_uid_db, owner, start, count); + exists = range_exists(&subordinate_uid_db, owner); + + if (close_db) + sub_uid_close(true); + + if (found) + return true; + if (!exists) + continue; // User does not have any ranges; try the next database. + return false; // User has ranges but does not own the requested range. + } + } + + // Searched all databases and didn't find the range. + return false; } /* @@ -791,17 +827,53 @@ int sub_gid_open (int mode) bool have_sub_gids(const char *owner, gid_t start, unsigned long count) { + enum subid_status status; + struct subid_nss_db *db; struct subid_nss_ops *h; + bool close_db = false; + bool exists; bool found; - enum subid_status status; - h = get_subid_nss_handle(); - if (h) { - status = h->has_range(owner, start, count, ID_TYPE_GID, &found); - if (status == SUBID_STATUS_SUCCESS && found) - return true; - return false; + + db = get_subid_nss_db(); + if (!db) { + // No NSS module configured, search local files only. + return have_range(&subordinate_gid_db, owner, start, count); } - return have_range(&subordinate_gid_db, owner, start, count); + + for (; db; db = db->next) { + h = db->ops; + if (h) { + status = h->has_range(owner, start, count, ID_TYPE_GID, &found); + if (status == SUBID_STATUS_SUCCESS) + return found; + if (status == SUBID_STATUS_UNKNOWN_USER) + continue; // Group not found in this database, try the next one. + + return false; // Error occurred. + } else { + // Local "files" database + if (!subordinate_gid_db.isopen) { + if (sub_gid_open(O_RDONLY) == 0) + return false; + close_db = true; + } + + found = have_range(&subordinate_gid_db, owner, start, count); + exists = range_exists(&subordinate_gid_db, owner); + + if (close_db) + sub_gid_close(true); + + if (found) + return true; + if (!exists) + continue; // Group does not have any ranges; try the next database. + return false; // Group has ranges but does not own the requested range. + } + } + + // Searched all databases and didn't find the range. + return false; } bool local_sub_gid_assigned(const char *owner) From c3a81ba86135a35c3a31051b9c86d260f40513b9 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 02:17:03 +0800 Subject: [PATCH 10/20] lib/subordinateio.c: have_range: fix check for sub_uid/gid_open() Correct the error handling in have_range() when opening subordinate ID databases. The sub_uid_open() and sub_gid_open() functions return 1 on success and 0 on failure. Signed-off-by: Yi Kuo --- lib/subordinateio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/subordinateio.c b/lib/subordinateio.c index 21f154a9d4..b320fb7470 100644 --- a/lib/subordinateio.c +++ b/lib/subordinateio.c @@ -564,7 +564,7 @@ static bool have_range(struct commonio_db *db, rc = sub_uid_open(O_RDONLY); else rc = sub_gid_open(O_RDONLY); - if (rc < 0) + if (!rc) return false; } From 529d41cae50e4cb701c95b6d74484ec1e2743668 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 02:21:57 +0800 Subject: [PATCH 11/20] lib/subordinateio.c: refactor list_owner_ranges to list_local_owner_ranges Rename and refactor list_owner_ranges() into a static helper function called list_local_owner_ranges(). This new function focuses solely on retrieving subordinate ID ranges from local shadow files, removing the previous legacy logic that attempted to consult a single NSS handle. This prepares the codebase for a new top-level list_owner_ranges() implementation that will correctly iterate through the multi-database NSS list. Signed-off-by: Yi Kuo --- lib/subordinateio.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/subordinateio.c b/lib/subordinateio.c index b320fb7470..1a7e71b14d 100644 --- a/lib/subordinateio.c +++ b/lib/subordinateio.c @@ -966,7 +966,7 @@ static bool get_owner_id(const char *owner, enum subid_type id_type, char *id) } /* - * int list_owner_ranges(const char *owner, enum subid_type id_type, struct subordinate_range ***ranges) + * static int list_local_owner_ranges(const char *owner, enum subid_type id_type, struct subid_range **in_ranges) * * @owner: username * @id_type: UID or GUID @@ -977,32 +977,23 @@ static bool get_owner_id(const char *owner, enum subid_type id_type, char *id) * UID number. If id_type is UID, then subuids are returned, else * subgids are given. - * Returns the number of ranges found, or < 0 on error. + * Returns the number of ranges found in local files only, or < 0 on error. + * NSS modules are not consulted by this function. * * The caller must free the subordinate range list. */ -int list_owner_ranges(const char *owner, enum subid_type id_type, struct subid_range **in_ranges) +static int list_local_owner_ranges(const char *owner, enum subid_type id_type, struct subid_range **in_ranges) { // TODO - need to handle owner being either uid or username struct subid_range *ranges = NULL; const struct subordinate_range *range; struct commonio_db *db; - enum subid_status status; int count = 0; - struct subid_nss_ops *h; char id[ID_SIZE]; bool have_owner_id; *in_ranges = NULL; - h = get_subid_nss_handle(); - if (h) { - status = h->list_owner_ranges(owner, id_type, in_ranges, &count); - if (status == SUBID_STATUS_SUCCESS) - return count; - return -1; - } - switch (id_type) { case ID_TYPE_UID: if (!sub_uid_open(O_RDONLY)) { From 4d8d60a7028aa040ad339c8f3d6bb46156029c0a Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 02:24:14 +0800 Subject: [PATCH 12/20] lib/subordinateio.c: list_owner_ranges: multi-database range listing Update list_owner_ranges() to iterate through the configured NSS database list. The logic now attempts to retrieve subid ranges from each source in order. Following standard NSS semantics, the search stops at the first database that successfully recognizes the user. For remote NSS modules, the implementation copies the results into locally managed memory and calls the module's specific free() afterwards. This is to ensure we have manage to all the publily returned memory pointers, for heap state consistency. (#1018) Signed-off-by: Yi Kuo --- lib/subordinateio.c | 85 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/lib/subordinateio.c b/lib/subordinateio.c index 1a7e71b14d..918a72dd5f 100644 --- a/lib/subordinateio.c +++ b/lib/subordinateio.c @@ -1036,6 +1036,91 @@ static int list_local_owner_ranges(const char *owner, enum subid_type id_type, s return count; } +/* + * int list_owner_ranges(const char *owner, enum subid_type id_type, struct subid_range ***ranges) + * + * @owner: username + * @id_type: UID or GUID + * @ranges: pointer to array of ranges into which results will be placed. + * + * Fills in the subuid or subgid ranges which are owned by the specified + * user. Username may be a username or a string representation of a + * UID number. If id_type is UID, then subuids are returned, else + * subgids are given. + + * Returns the number of ranges found, or < 0 on error. + * + * The caller must free the subordinate range list. + */ +int list_owner_ranges(const char *owner, enum subid_type id_type, struct subid_range **ranges) +{ + enum subid_status status; + int count = 0; + struct subid_nss_db *db; + struct subid_nss_ops *h = NULL; + struct subid_range *our_ranges; + bool error = false; + + *ranges = NULL; + + db = get_subid_nss_db(); + if (!db) { + // No NSS module configured, search local files only. + return list_local_owner_ranges(owner, id_type, ranges); + } + + for (; db; db = db->next) { + h = db->ops; + if (h) { + status = h->list_owner_ranges(owner, id_type, ranges, &count); + + if (status == SUBID_STATUS_SUCCESS) { + if (count > 0) { + our_ranges = malloc_T(count, struct subid_range); + if (!our_ranges) + goto fail; + + memcpy(our_ranges, *ranges, count * sizeof(struct subid_range)); + h->free(*ranges); + *ranges = our_ranges; + } + return count; + } + if (status == SUBID_STATUS_UNKNOWN_USER) + goto next; + if (status == SUBID_STATUS_ERROR || status == SUBID_STATUS_ERROR_CONN) + goto fail; + } else { + // Local "files" database. + count = list_local_owner_ranges(owner, id_type, ranges); + if (count > 0) + return count; + if (count == 0) + goto next; + if (count < 0) + goto fail; + } + + fail: + error = true; + + next: + if (*ranges) { + if (h) + h->free(*ranges); + else + free(*ranges); + *ranges = NULL; + } + + if (error) + return -1; + } + + // Searched all databases but 0 ranges found. + return 0; +} + static int append_uids(uid_t **uids, const char *owner, int n) { int i; From 2cb6cbd800bd70d0845ab188926303e74c20f720 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 02:30:15 +0800 Subject: [PATCH 13/20] lib/subordinateio.c: refactor find_subid_owners to find_local_subid_owners Rename and refactor find_subid_owners() into a static helper function called find_local_subid_owners(). This is a preparation for the upcoming top-level find_subid_owners() implementation, which will correctly iterate through the multi-database NSS list. Signed-off-by: Yi Kuo --- lib/subordinateio.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/subordinateio.c b/lib/subordinateio.c index 918a72dd5f..3786499e7d 100644 --- a/lib/subordinateio.c +++ b/lib/subordinateio.c @@ -1158,23 +1158,12 @@ static int append_uids(uid_t **uids, const char *owner, int n) return n+1; } -int find_subid_owners(unsigned long id, enum subid_type id_type, uid_t **uids) +static int find_local_subid_owners(unsigned long id, enum subid_type id_type, uid_t **uids) { const struct subordinate_range *range; - struct subid_nss_ops *h; - enum subid_status status; struct commonio_db *db; int n = 0; - h = get_subid_nss_handle(); - if (h) { - status = h->find_subid_owners(id, id_type, uids, &n); - // Several ways we could handle the error cases here. - if (status != SUBID_STATUS_SUCCESS) - return -1; - return n; - } - switch (id_type) { case ID_TYPE_UID: if (!sub_uid_open(O_RDONLY)) { From 2a36cc27f97b946426ea19d686b1d2a991ce7964 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 02:33:17 +0800 Subject: [PATCH 14/20] lib/subordinateio.c: find_subid_owners: multi-database owner search Update find_subid_owners() to iterate through the configured NSS database list. Unlike range listing, which stops at the first user match, this implementation aggregates all subordinate ID owners across all configured databases. The results from each source are merged into a single dynamically allocated array using reallocf_T(). For remote sources, the results are copied to the local heap before calling the module's free() function to ensure memory consistency. (#1018) Signed-off-by: Yi Kuo --- lib/subordinateio.c | 65 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/lib/subordinateio.c b/lib/subordinateio.c index 3786499e7d..b6e4aaa297 100644 --- a/lib/subordinateio.c +++ b/lib/subordinateio.c @@ -1200,6 +1200,71 @@ static int find_local_subid_owners(unsigned long id, enum subid_type id_type, ui return n; } +int find_subid_owners(unsigned long id, enum subid_type id_type, uid_t **uids) +{ + struct subid_nss_db *db; + struct subid_nss_ops *h; + enum subid_status status; + int n = 0; + int count = 0; + bool error = false; + uid_t *new_uids = NULL; + uid_t *all_uids = NULL; + + *uids = NULL; + + db = get_subid_nss_db(); + if (!db) { + // No NSS module configured, search local files only. + return find_local_subid_owners(id, id_type, uids); + } + + // Search for all databases and aggregate results. + for (; db; db = db->next) { + h = db->ops; + if (h) { + status = h->find_subid_owners(id, id_type, &new_uids, &count); + if (status != SUBID_STATUS_SUCCESS) + count = -1; + } else { + count = find_local_subid_owners(id, id_type, &new_uids); + } + + if (count <= 0) + goto next; + + if (count > 0) { + all_uids = reallocf_T(all_uids, n + count, uid_t); + if (!all_uids) + goto fail; + memcpy(all_uids + n, new_uids, count * sizeof(uid_t)); + n += count; + goto next; + } + + fail: + error = true; + + next: + if (new_uids) { + if (h) + h->free(new_uids); + else + free(new_uids); + new_uids = NULL; + } + + if (error) { + if (all_uids) + free(all_uids); + return -1; + } + } + + *uids = all_uids; + return n; +} + bool new_subid_range(struct subordinate_range *range, enum subid_type id_type, bool reuse) { struct commonio_db *db; From b526c5ac8987d483c7b83e5ab0099f1010592185 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 02:36:00 +0800 Subject: [PATCH 15/20] lib/subordinateio.c: free_subid_pointer: use standard free Update free_subid_pointer() to use the standard free() function directly, removing the need to use NSS module's free function. This change is made possible by previous refactors in list_owner_ranges() and find_subid_owners(), which now copy data from remote NSS modules into the local heap before releasing the module-specific memory. As a result, all pointers returned to the caller are guaranteed to be managed by the local allocator, ensuring heap consistency across the library. (#1018) Signed-off-by: Yi Kuo --- lib/subordinateio.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/subordinateio.c b/lib/subordinateio.c index b6e4aaa297..c9b9bf1c3b 100644 --- a/lib/subordinateio.c +++ b/lib/subordinateio.c @@ -1400,12 +1400,7 @@ bool release_subid_range(struct subordinate_range *range, enum subid_type id_typ void free_subid_pointer(void *ptr) { - struct subid_nss_ops *h = get_subid_nss_handle(); - if (h) { - h->free(ptr); - } else { - free(ptr); - } + free(ptr); } #else /* !ENABLE_SUBIDS */ From 90fa9b8fb9323f64f7679368d3df63ecd4e6fedd Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Mon, 2 Mar 2026 21:57:47 +0800 Subject: [PATCH 16/20] tests/libsubid/04_nss: Improve mock implementation Update the NSS mock module used in tests to be more precise with range checking and user validation. - Remove redundant shadow_subid_has_any_range. - Implement proper range bounds checking in shadow_subid_has_range. - Return SUBID_STATUS_UNKNOWN_USER if user is not expected Signed-off-by: Yi Kuo --- tests/libsubid/04_nss/libsubid_zzz.c | 82 ++++++++++------------------ 1 file changed, 28 insertions(+), 54 deletions(-) diff --git a/tests/libsubid/04_nss/libsubid_zzz.c b/tests/libsubid/04_nss/libsubid_zzz.c index 2e929687ec..0a76fe107f 100644 --- a/tests/libsubid/04_nss/libsubid_zzz.c +++ b/tests/libsubid/04_nss/libsubid_zzz.c @@ -6,60 +6,30 @@ #include #include "alloc/malloc.h" -enum subid_status shadow_subid_has_any_range(const char *owner, enum subid_type t, bool *result) +enum subid_status shadow_subid_has_range(const char *owner, unsigned long start, unsigned long count, enum subid_type t, bool *result) { if (strcmp(owner, "ubuntu") == 0) { - *result = true; + *result = start >= 200000 && start + count <= 200000 + 100000; return SUBID_STATUS_SUCCESS; } - if (strcmp(owner, "error") == 0) { - *result = false; - return SUBID_STATUS_ERROR; - } - if (strcmp(owner, "unknown") == 0) { - *result = false; - return SUBID_STATUS_UNKNOWN_USER; - } - if (strcmp(owner, "conn") == 0) { - *result = false; - return SUBID_STATUS_ERROR_CONN; - } - if (t == ID_TYPE_UID) { - *result = strcmp(owner, "user1") == 0; + + if (strcmp(owner, "user1") == 0 && t == ID_TYPE_UID) { + *result = start >= 100000 && start + count <= 100000 + 65536; return SUBID_STATUS_SUCCESS; } - *result = strcmp(owner, "group1") == 0; - return SUBID_STATUS_SUCCESS; -} - -enum subid_status shadow_subid_has_range(const char *owner, unsigned long start, unsigned long count, enum subid_type t, bool *result) -{ - if (strcmp(owner, "ubuntu") == 0 && - start >= 200000 && - count <= 100000) { - *result = true; + if (strcmp(owner, "group1") == 0 && t == ID_TYPE_GID) { + *result = start >= 100000 && start + count <= 100000 + 65536; return SUBID_STATUS_SUCCESS; } + *result = false; if (strcmp(owner, "error") == 0) return SUBID_STATUS_ERROR; - if (strcmp(owner, "unknown") == 0) - return SUBID_STATUS_UNKNOWN_USER; if (strcmp(owner, "conn") == 0) return SUBID_STATUS_ERROR_CONN; - if (t == ID_TYPE_UID && strcmp(owner, "user1") != 0) - return SUBID_STATUS_SUCCESS; - if (t == ID_TYPE_GID && strcmp(owner, "group1") != 0) - return SUBID_STATUS_SUCCESS; - - if (start < 100000) - return SUBID_STATUS_SUCCESS; - if (count >= 65536) - return SUBID_STATUS_SUCCESS; - *result = true; - return SUBID_STATUS_SUCCESS; + return SUBID_STATUS_UNKNOWN_USER; } // So if 'user1' or 'ubuntu' is defined in passwd, we'll return those values, @@ -109,34 +79,38 @@ enum subid_status shadow_subid_list_owner_ranges(const char *owner, enum subid_t *count = 0; if (strcmp(owner, "error") == 0) return SUBID_STATUS_ERROR; - if (strcmp(owner, "unknown") == 0) - return SUBID_STATUS_UNKNOWN_USER; if (strcmp(owner, "conn") == 0) return SUBID_STATUS_ERROR_CONN; *in_ranges = NULL; - if (strcmp(owner, "user1") != 0 && strcmp(owner, "ubuntu") != 0 && - strcmp(owner, "group1") != 0) - return SUBID_STATUS_SUCCESS; - if (id_type == ID_TYPE_GID && strcmp(owner, "user1") == 0) - return SUBID_STATUS_SUCCESS; - if (id_type == ID_TYPE_UID && strcmp(owner, "group1") == 0) - return SUBID_STATUS_SUCCESS; ranges = malloc_T(1, struct subid_range); if (!ranges) return SUBID_STATUS_ERROR; - if (strcmp(owner, "user1") == 0 || strcmp(owner, "group1") == 0) { + + *count = 1; + *in_ranges = ranges; + + if (strcmp(owner, "user1") == 0 && id_type == ID_TYPE_UID) { + ranges[0].start = 100000; + ranges[0].count = 65536; + return SUBID_STATUS_SUCCESS; + } + + if (strcmp(owner, "group1") == 0 && id_type == ID_TYPE_GID) { ranges[0].start = 100000; ranges[0].count = 65536; - } else { + return SUBID_STATUS_SUCCESS; + } + + if (strcmp(owner, "ubuntu") == 0) { ranges[0].start = 200000; ranges[0].count = 100000; + return SUBID_STATUS_SUCCESS; } - *count = 1; - *in_ranges = ranges; - - return SUBID_STATUS_SUCCESS; + free(ranges); + *in_ranges = NULL; + return SUBID_STATUS_UNKNOWN_USER; } void shadow_subid_free(void *ptr) From 4dfa195739f191856bfcbf60903c289367b20435 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Mon, 2 Mar 2026 22:00:42 +0800 Subject: [PATCH 17/20] tests/libsubid/04_nss: Update and extend test_nss Expand the NSS parsing tests to cover scenarios where multiple modules are defined in nsswitch.conf. - Added nsswitch4.conf (files zzz) and nsswitch5.conf (zzz files). - Update and add tests to check the subid_nss_db list structure. Signed-off-by: Yi Kuo --- tests/libsubid/04_nss/nsswitch4.conf | 22 +++++++ tests/libsubid/04_nss/nsswitch5.conf | 22 +++++++ tests/libsubid/04_nss/subidnss.test | 2 + tests/libsubid/04_nss/test_nss.c | 95 +++++++++++++++++++++++++--- 4 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 tests/libsubid/04_nss/nsswitch4.conf create mode 100644 tests/libsubid/04_nss/nsswitch5.conf diff --git a/tests/libsubid/04_nss/nsswitch4.conf b/tests/libsubid/04_nss/nsswitch4.conf new file mode 100644 index 0000000000..b824e89ed9 --- /dev/null +++ b/tests/libsubid/04_nss/nsswitch4.conf @@ -0,0 +1,22 @@ +# /etc/nsswitch.conf +# +# Example configuration of GNU Name Service Switch functionality. +# If you have the `glibc-doc-reference' and `info' packages installed, try: +# `info libc "Name Service Switch"' for information about this file. + +passwd: files systemd +group: files systemd +shadow: files +gshadow: files + +hosts: files mdns4_minimal [NOTFOUND=return] dns +networks: files + +protocols: db files +services: db files +ethers: db files +rpc: db files + +netgroup: nis + +subid: files zzz diff --git a/tests/libsubid/04_nss/nsswitch5.conf b/tests/libsubid/04_nss/nsswitch5.conf new file mode 100644 index 0000000000..e474dd9e92 --- /dev/null +++ b/tests/libsubid/04_nss/nsswitch5.conf @@ -0,0 +1,22 @@ +# /etc/nsswitch.conf +# +# Example configuration of GNU Name Service Switch functionality. +# If you have the `glibc-doc-reference' and `info' packages installed, try: +# `info libc "Name Service Switch"' for information about this file. + +passwd: files systemd +group: files systemd +shadow: files +gshadow: files + +hosts: files mdns4_minimal [NOTFOUND=return] dns +networks: files + +protocols: db files +services: db files +ethers: db files +rpc: db files + +netgroup: nis + +subid: zzz files diff --git a/tests/libsubid/04_nss/subidnss.test b/tests/libsubid/04_nss/subidnss.test index 400171fa88..2b20c22b07 100755 --- a/tests/libsubid/04_nss/subidnss.test +++ b/tests/libsubid/04_nss/subidnss.test @@ -14,6 +14,8 @@ export LD_LIBRARY_PATH=.:${build_path}/lib/.libs:$LD_LIBRARY_PATH ./test_nss 1 ./test_nss 2 ./test_nss 3 +./test_nss 4 +./test_nss 5 unshare -Urm ./test_range diff --git a/tests/libsubid/04_nss/test_nss.c b/tests/libsubid/04_nss/test_nss.c index 5d903ab41a..e92f1c6525 100644 --- a/tests/libsubid/04_nss/test_nss.c +++ b/tests/libsubid/04_nss/test_nss.c @@ -6,45 +6,118 @@ #include extern bool nss_is_initialized(); -extern struct subid_nss_ops *get_subid_nss_handle(); + +extern struct subid_nss_db *get_subid_nss_db(); + +void check_files(struct subid_nss_db *h) { + if (!h) { + exit(1); + } + if (h->ops) { + exit(1); + } +} + +void check_zzz(struct subid_nss_db *h) { + if (!h) { + exit(1); + } + if (!h->ops) { + exit(1); + } +} void test1() { // nsswitch1 has no subid: entry setenv("LD_LIBRARY_PATH", ".", 1); printf("Test with no subid entry\n"); nss_init("./nsswitch1.conf"); - if (!nss_is_initialized() || get_subid_nss_handle()) + if (!nss_is_initialized() || get_subid_nss_db()) exit(1); // second run should change nothing printf("Test with no subid entry, second run\n"); nss_init("./nsswitch1.conf"); - if (!nss_is_initialized() || get_subid_nss_handle()) + if (!nss_is_initialized() || get_subid_nss_db()) exit(1); } void test2() { + struct subid_nss_db *h; // nsswitch2 has a subid: files entry printf("test with 'files' subid entry\n"); nss_init("./nsswitch2.conf"); - if (!nss_is_initialized() || get_subid_nss_handle()) + if (!nss_is_initialized()) + exit(1); + h = get_subid_nss_db(); + check_files(h); + if (h->next) { exit(1); + } + // second run should change nothing printf("test with 'files' subid entry, second run\n"); nss_init("./nsswitch2.conf"); - if (!nss_is_initialized() || get_subid_nss_handle()) + if (!nss_is_initialized()) exit(1); + h = get_subid_nss_db(); + check_files(h); + if (h->next) { + exit(1); + } } void test3() { - // nsswitch3 has a subid: testnss entry - printf("test with 'test' subid entry\n"); + struct subid_nss_db *h; + // nsswitch3 has a subid: zzz entry + printf("test with 'zzz' subid entry\n"); nss_init("./nsswitch3.conf"); - if (!nss_is_initialized() || !get_subid_nss_handle()) + if (!nss_is_initialized()) exit(1); + h = get_subid_nss_db(); + check_zzz(h); + if (h->next) + exit(1); + // second run should change nothing - printf("test with 'test' subid entry, second run\n"); + printf("test with 'zzz' subid entry, second run\n"); nss_init("./nsswitch3.conf"); - if (!nss_is_initialized() || !get_subid_nss_handle()) + if (!nss_is_initialized()) + exit(1); + h = get_subid_nss_db(); + check_zzz(h); + if (h->next) + exit(1); +} + +void test4() { + struct subid_nss_db *h; + // nsswitch4 has a subid: files zzz + printf("test with 'files zzz' subid entry\n"); + nss_init("./nsswitch4.conf"); + if (!nss_is_initialized()) + exit(1); + h = get_subid_nss_db(); + check_files(h); + if (!h->next) + exit(1); + check_zzz(h->next); + if (h->next->next) + exit(1); +} + +void test5() { + struct subid_nss_db *h; + // nsswitch5 has a subid: zzz files + printf("test with 'zzz files' subid entry\n"); + nss_init("./nsswitch5.conf"); + if (!nss_is_initialized()) + exit(1); + h = get_subid_nss_db(); + check_zzz(h); + if (!h->next) + exit(1); + check_files(h->next); + if (h->next->next) exit(1); } @@ -64,6 +137,8 @@ int main(int argc, char *argv[]) case 1: test1(); break; case 2: test2(); break; case 3: test3(); break; + case 4: test4(); break; + case 5: test5(); break; default: exit(1); } From 3d4cfa998c0c9ad27ec10b209ec5ebd399fac904 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Mon, 2 Mar 2026 22:07:51 +0800 Subject: [PATCH 18/20] tests/libsubid/04_nss: Extend range tests to multiple NSS providers Update the range tests to verify the behavior of check_subid_range when multiple NSS modules are configured in nsswitch.conf. - Created passwd and subuid mock files for testing the files backend. - Add tests in test_range to run scenarios against nsswitch4.conf (files zzz) and nsswitch5.conf (zzz files). Signed-off-by: Yi Kuo --- tests/libsubid/04_nss/passwd | 24 ++++++ tests/libsubid/04_nss/subuid | 4 + tests/libsubid/04_nss/test_range | 127 +++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 tests/libsubid/04_nss/passwd create mode 100644 tests/libsubid/04_nss/subuid diff --git a/tests/libsubid/04_nss/passwd b/tests/libsubid/04_nss/passwd new file mode 100644 index 0000000000..fb5a6fe443 --- /dev/null +++ b/tests/libsubid/04_nss/passwd @@ -0,0 +1,24 @@ +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/bin/sh +bin:x:2:2:bin:/bin:/bin/sh +sys:x:3:3:sys:/dev:/bin/sh +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/bin/sh +man:x:6:12:man:/var/cache/man:/bin/sh +lp:x:7:7:lp:/var/spool/lpd:/bin/sh +mail:x:8:8:mail:/var/mail:/bin/sh +news:x:9:9:news:/var/spool/news:/bin/sh +uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh +proxy:x:13:13:proxy:/bin:/bin/sh +www-data:x:33:33:www-data:/var/www:/bin/sh +backup:x:34:34:backup:/var/backups:/bin/sh +list:x:38:38:Mailing List Manager:/var/list:/bin/sh +irc:x:39:39:ircd:/var/run/ircd:/bin/sh +gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh +nobody:x:65534:65534:nobody:/nonexistent:/bin/sh +Debian-exim:x:102:102::/var/spool/exim4:/bin/false +ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash +user1:x:1001:1001::/home/user1:/bin/bash +user2:x:1002:1002::/home/user2:/bin/bash +error:x:1003:1003::/home/error:/bin/bash +conn:x:1004:1004::/home/conn:/bin/bash \ No newline at end of file diff --git a/tests/libsubid/04_nss/subuid b/tests/libsubid/04_nss/subuid new file mode 100644 index 0000000000..30eacbff6b --- /dev/null +++ b/tests/libsubid/04_nss/subuid @@ -0,0 +1,4 @@ +user2:300000:65536 +ubuntu:400000:65536 +error:500000:65536 +conn:600000:65536 diff --git a/tests/libsubid/04_nss/test_range b/tests/libsubid/04_nss/test_range index 45a791c746..1fee910af1 100755 --- a/tests/libsubid/04_nss/test_range +++ b/tests/libsubid/04_nss/test_range @@ -4,6 +4,9 @@ set -x echo "starting check_range tests" +touch /etc/passwd +mount --bind ./passwd /etc/passwd + export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH touch /etc/nsswitch.conf mount --bind ./nsswitch3.conf /etc/nsswitch.conf @@ -48,5 +51,129 @@ if [ $? -eq 0 ]; then exit 1 fi +umount /etc/subuid +umount /etc/nsswitch.conf + +# nsswitch4: files zzz +# subuid has user2, ubuntu, error, conn +# zzz has user1, ubuntu (different range) +mount --bind ./subuid /etc/subuid +mount --bind ./nsswitch4.conf /etc/nsswitch.conf + +cleanup3() { + umount /etc/subuid + umount /etc/nsswitch.conf +} +trap cleanup3 EXIT HUP INT TERM + +# user1 100000 -> not in files -> in zzz -> yes +${build_path}/src/check_subid_range user1 u 100000 65535 +if [ $? -ne 0 ]; then + exit 1 +fi + +# user1 200000 -> not in files -> in zzz -> no +${build_path}/src/check_subid_range user1 u 200000 65535 +if [ $? -eq 0 ]; then + exit 1 +fi + +# user2 300000 -> in files -> yes +${build_path}/src/check_subid_range user2 u 300000 65535 +if [ $? -ne 0 ]; then + exit 1 +fi + +# user2 400000 -> in files -> no +${build_path}/src/check_subid_range user2 u 400000 65535 +if [ $? -eq 0 ]; then + exit 1 +fi + +# unknown 500000 -> not in files -> not in zzz -> no +${build_path}/src/check_subid_range unknown u 500000 65535 +if [ $? -eq 0 ]; then + exit 1 +fi + +# ubuntu 200000 -> in files -> no +# Even though ubuntu 200000 is in zzz, it should stop at files because user is in files. +${build_path}/src/check_subid_range ubuntu u 200000 65535 +if [ $? -eq 0 ]; then + exit 1 +fi + +# ubuntu 400000 -> in files -> yes +${build_path}/src/check_subid_range ubuntu u 400000 65535 +if [ $? -ne 0 ]; then + exit 1 +fi + +umount /etc/nsswitch.conf + +# nsswitch5: zzz files +mount --bind ./nsswitch5.conf /etc/nsswitch.conf + +cleanup4() { + umount /etc/subuid + umount /etc/nsswitch.conf +} +trap cleanup4 EXIT HUP INT TERM + +# user1 100000 -> in zzz -> yes +${build_path}/src/check_subid_range user1 u 100000 65535 +if [ $? -ne 0 ]; then + exit 1 +fi + +# user1 200000 -> in zzz -> no +${build_path}/src/check_subid_range user1 u 200000 65535 +if [ $? -eq 0 ]; then + exit 1 +fi + +# user2 300000 -> not in zzz -> in files -> yes +${build_path}/src/check_subid_range user2 u 300000 65535 +if [ $? -ne 0 ]; then + exit 1 +fi + +# user2 400000 -> not in zzz -> in files -> no +${build_path}/src/check_subid_range user2 u 400000 65535 +if [ $? -eq 0 ]; then + exit 1 +fi + +# unknown 500000 -> not in zzz -> not in files -> no +${build_path}/src/check_subid_range unknown u 500000 65535 +if [ $? -eq 0 ]; then + exit 1 +fi + +# ubuntu 200000 -> in zzz -> yes +${build_path}/src/check_subid_range ubuntu u 200000 65535 +if [ $? -ne 0 ]; then + exit 1 +fi + +# ubuntu 400000 -> in zzz -> no +# zzz has the user ubuntu. So it won't lookup files +${build_path}/src/check_subid_range ubuntu u 400000 65535 +if [ $? -eq 0 ]; then + exit 1 +fi + +# error 500000 -> zzz errors -> no +${build_path}/src/check_subid_range error u 500000 65535 +if [ $? -eq 0 ]; then + exit 1 +fi + +# conn 600000 -> zzz errors -> no +${build_path}/src/check_subid_range conn u 600000 65535 +if [ $? -eq 0 ]; then + exit 1 +fi + echo "check_range tests complete" exit 0 From 768ad6f5340a8370281f9e7ae2e921c10f1d3d19 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Mon, 2 Mar 2026 22:56:11 +0800 Subject: [PATCH 19/20] man/subuid.5, man/subgid.5: document multiple subid delegation support Update the documentation to reflect that multiple subid delegation sources can be configured in /etc/nsswitch.conf. Signed-off-by: Yi Kuo --- man/subgid.5.xml | 26 ++++++++++++++++---------- man/subuid.5.xml | 26 ++++++++++++++++---------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/man/subgid.5.xml b/man/subgid.5.xml index 09a866fca6..2bfcd62a5c 100644 --- a/man/subgid.5.xml +++ b/man/subgid.5.xml @@ -41,20 +41,26 @@ The delegation of the subordinate gids can be configured via the subid field in - /etc/nsswitch.conf file. Only one value can be set - as the delegation source. Setting this field to - files configures the delegation of gids to - /etc/subgid. Setting any other value treats - the delegation as a plugin following with a name of the form - libsubid_$value.so. If the value or plugin is - missing, then the subordinate gid delegation falls back to + /etc/nsswitch.conf file. Multiple values can be + specified, separated by whitespace. These values will be consulted in order. + Setting this field to files configures the + delegation of gids to /etc/subgid. Setting any other + value treats the delegation as a plugin following with a name of the form + libsubid_$value.so. If the plugin is missing + or fails to load, it is ignored. If no plugin configures successfully, + the subordinate gid delegation falls back to files. - Note, that newusers, useradd, and + Note that the search stops as soon as the group exists in a delegation + database, even if no subids are defined for that group there. + + + Also note, that newusers, useradd, and usermod will only create entries in - /etc/subgid if subid delegation is managed via subid - files. + /etc/subgid if the subid + delegation is not configured or files is the + first configured value. diff --git a/man/subuid.5.xml b/man/subuid.5.xml index eb28eed7ff..7aed0df792 100644 --- a/man/subuid.5.xml +++ b/man/subuid.5.xml @@ -41,20 +41,26 @@ The delegation of the subordinate uids can be configured via the subid field in - /etc/nsswitch.conf file. Only one value can be set - as the delegation source. Setting this field to - files configures the delegation of uids to - /etc/subuid. Setting any other value treats - the delegation as a plugin following with a name of the form - libsubid_$value.so. If the value or plugin is - missing, then the subordinate uid delegation falls back to + /etc/nsswitch.conf file. Multiple values can be + specified, separated by whitespace. These values will be consulted in order. + Setting this field to files configures the + delegation of uids to /etc/subuid. Setting any other + value treats the delegation as a plugin following with a name of the form + libsubid_$value.so. If the plugin is missing + or fails to load, it is ignored. If no plugin configures successfully, + the subordinate uid delegation falls back to files. - Note, that newusers, useradd, and + Note that the search stops as soon as the user exists in a delegation + database, even if no subids are defined for that user there. + + + Also note, that newusers, useradd, and usermod will only create entries in - /etc/subuid if subid delegation is managed via subid - files. + /etc/subuid if the subid + delegation is not configured or files is the + first configured value. From ec8afae1e6432799f414af1d8435c538d6966219 Mon Sep 17 00:00:00 2001 From: Yi Kuo Date: Tue, 3 Mar 2026 04:13:51 +0800 Subject: [PATCH 20/20] tests/libsubid/04_nss: add tests for range listing Create a new test script, test_list, to verify the behavior of the getsubids program when configured with multiple NSS databases. The new test is added into the main subidnss.test runner. Signed-off-by: Yi Kuo --- tests/libsubid/04_nss/subidnss.test | 1 + tests/libsubid/04_nss/test_list | 89 +++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100755 tests/libsubid/04_nss/test_list diff --git a/tests/libsubid/04_nss/subidnss.test b/tests/libsubid/04_nss/subidnss.test index 2b20c22b07..3e49418645 100755 --- a/tests/libsubid/04_nss/subidnss.test +++ b/tests/libsubid/04_nss/subidnss.test @@ -18,6 +18,7 @@ export LD_LIBRARY_PATH=.:${build_path}/lib/.libs:$LD_LIBRARY_PATH ./test_nss 5 unshare -Urm ./test_range +unshare -Urm ./test_list log_status "$0" "SUCCESS" diff --git a/tests/libsubid/04_nss/test_list b/tests/libsubid/04_nss/test_list new file mode 100755 index 0000000000..2e72fee421 --- /dev/null +++ b/tests/libsubid/04_nss/test_list @@ -0,0 +1,89 @@ +#!/bin/sh + +set -x + +echo "starting getsubids tests" + +check_val() { + expected="$1" + shift + out=$("$@" 2>&1) + if [ "$out" != "$expected" ]; then + echo "FAIL: $*" + echo "Expected: $expected" + echo "Got: $out" + exit 1 + fi +} + +touch /etc/passwd +mount --bind ./passwd /etc/passwd + +export LD_LIBRARY_PATH=.:${build_path}/libsubid/.libs:$LD_LIBRARY_PATH +touch /etc/nsswitch.conf +mount --bind ./nsswitch3.conf /etc/nsswitch.conf +cleanup1() { + umount /etc/nsswitch.conf +} +trap cleanup1 EXIT HUP INT TERM +check_val "0: user1 100000 65536" ${build_path}/src/.libs/getsubids user1 +check_val "Error fetching ranges" ${build_path}/src/.libs/getsubids user2 +check_val "0: group1 100000 65536" ${build_path}/src/.libs/getsubids -g group1 +check_val "0: ubuntu 200000 100000" ${build_path}/src/.libs/getsubids ubuntu +check_val "Error fetching ranges" ${build_path}/src/.libs/getsubids error + +umount /etc/nsswitch.conf + +mount --bind ./nsswitch1.conf /etc/nsswitch.conf +touch /etc/subuid /etc/subgid +mount --bind ./subuid /etc/subuid +mount --bind ./subuid /etc/subgid + +cleanup2() { + umount /etc/subuid + umount /etc/subgid + umount /etc/nsswitch.conf +} +trap cleanup2 EXIT HUP INT TERM +check_val "Error fetching ranges" ${build_path}/src/.libs/getsubids user1 +check_val "0: user2 300000 65536" ${build_path}/src/.libs/getsubids user2 +check_val "0: user2 300000 65536" ${build_path}/src/.libs/getsubids -g user2 +check_val "0: ubuntu 400000 65536" ${build_path}/src/.libs/getsubids ubuntu + +umount /etc/subuid +umount /etc/nsswitch.conf + +# nsswitch4: files zzz +# subuid has user2, ubuntu, error, conn +# zzz has user1, ubuntu (different range) +mount --bind ./subuid /etc/subuid +mount --bind ./nsswitch4.conf /etc/nsswitch.conf + +cleanup3() { + umount /etc/subuid + umount /etc/nsswitch.conf +} +trap cleanup3 EXIT HUP INT TERM + +check_val "0: user1 100000 65536" ${build_path}/src/.libs/getsubids user1 +check_val "0: user2 300000 65536" ${build_path}/src/.libs/getsubids user2 +check_val "0: ubuntu 400000 65536" ${build_path}/src/.libs/getsubids ubuntu + +umount /etc/nsswitch.conf + +# nsswitch5: zzz files +mount --bind ./nsswitch5.conf /etc/nsswitch.conf + +cleanup4() { + umount /etc/subuid + umount /etc/nsswitch.conf +} +trap cleanup4 EXIT HUP INT TERM + +check_val "0: user1 100000 65536" ${build_path}/src/.libs/getsubids user1 +check_val "0: user2 300000 65536" ${build_path}/src/.libs/getsubids user2 +check_val "0: ubuntu 200000 100000" ${build_path}/src/.libs/getsubids ubuntu +check_val "Error fetching ranges" ${build_path}/src/.libs/getsubids error + +echo "check_range tests complete" +exit 0