diff --git a/lib/nss.c b/lib/nss.c index 5957390741..8e71879ed9 100644 --- a/lib/nss.c +++ b/lib/nss.c @@ -25,37 +25,113 @@ // 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_ops *subid_nss; +static struct subid_nss_db *subid_nss_db_head; -bool nss_is_initialized() { +bool +nss_is_initialized(void) { 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; } } +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) { - char *line = NULL, *p; - char libname[64]; - FILE *nssfp = NULL; - void *h; - 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 @@ -68,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) @@ -86,66 +162,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; } - if (stpsep(p, " \t\n") == NULL) { - 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); - 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); - 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; + + 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; } - subid_nss->handle = h; - goto done; -close_lib: - dlclose(h); - free(subid_nss); -null_subid: - subid_nss = 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"); + } done: atomic_store(&nss_init_completed, true); @@ -156,7 +227,8 @@ nss_init(const char *nsswitch_path) { } } -struct subid_nss_ops *get_subid_nss_handle() { +struct subid_nss_db * +get_subid_nss_db(void) { nss_init(NULL); - return subid_nss; + return subid_nss_db_head; } diff --git a/lib/prototypes.h b/lib/prototypes.h index 411f8d559c..17841483ca 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 @@ -289,7 +294,7 @@ 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); /* pam_pass_non_interactive.c */ diff --git a/lib/subordinateio.c b/lib/subordinateio.c index acd3f1ffdc..c9b9bf1c3b 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; } @@ -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); + } + + 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. + } } - return have_range (&subordinate_uid_db, owner, start, count); + + // Searched all databases and didn't find the range. + return false; } /* @@ -647,7 +683,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 +697,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; } @@ -690,11 +734,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 +761,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 = { @@ -759,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); + } + + 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. + } } - return have_range(&subordinate_gid_db, owner, start, count); + + // Searched all databases and didn't find the range. + return false; } bool local_sub_gid_assigned(const char *owner) @@ -785,7 +889,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; } @@ -795,7 +903,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; } @@ -854,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 @@ -865,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)) { @@ -933,6 +1036,91 @@ int list_owner_ranges(const char *owner, enum subid_type id_type, struct subid_r 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; @@ -970,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)) { @@ -1023,14 +1200,84 @@ int find_subid_owners(unsigned long id, enum subid_type id_type, uid_t **uids) 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; + 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: @@ -1099,10 +1346,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: @@ -1148,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 */ 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. 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) 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/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/subidnss.test b/tests/libsubid/04_nss/subidnss.test index 400171fa88..3e49418645 100755 --- a/tests/libsubid/04_nss/subidnss.test +++ b/tests/libsubid/04_nss/subidnss.test @@ -14,8 +14,11 @@ 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 +unshare -Urm ./test_list log_status "$0" "SUCCESS" 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_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 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); } 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