From 09bbaa419b6522bac9ec58d6bf4967d39f0a7891 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 31 Mar 2022 12:56:24 +0200 Subject: [PATCH 1/8] uid-range: use size_t for array size --- src/shared/uid-range.c | 20 ++++++++++---------- src/shared/uid-range.h | 8 ++++---- src/sysusers/sysusers.c | 2 +- src/test/test-uid-range.c | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/shared/uid-range.c b/src/shared/uid-range.c index 2c07a1b7a8..1adf4f62c2 100644 --- a/src/shared/uid-range.c +++ b/src/shared/uid-range.c @@ -17,12 +17,12 @@ static bool uid_range_intersect(UidRange *range, uid_t start, uid_t nr) { range->start + range->nr >= start; } -static void uid_range_coalesce(UidRange **p, unsigned *n) { +static void uid_range_coalesce(UidRange **p, size_t *n) { assert(p); assert(n); - for (unsigned i = 0; i < *n; i++) { - for (unsigned j = i + 1; j < *n; j++) { + for (size_t i = 0; i < *n; i++) { + for (size_t j = i + 1; j < *n; j++) { UidRange *x = (*p)+i, *y = (*p)+j; if (uid_range_intersect(x, y->start, y->nr)) { @@ -54,7 +54,7 @@ static int uid_range_compare(const UidRange *a, const UidRange *b) { return CMP(a->nr, b->nr); } -int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr) { +int uid_range_add(UidRange **p, size_t *n, uid_t start, uid_t nr) { bool found = false; UidRange *x; @@ -64,7 +64,7 @@ int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr) { if (nr <= 0) return 0; - for (unsigned i = 0; i < *n; i++) { + for (size_t i = 0; i < *n; i++) { x = (*p) + i; if (uid_range_intersect(x, start, nr)) { found = true; @@ -100,7 +100,7 @@ int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr) { return *n; } -int uid_range_add_str(UidRange **p, unsigned *n, const char *s) { +int uid_range_add_str(UidRange **p, size_t *n, const char *s) { uid_t start, nr; const char *t; int r; @@ -138,7 +138,7 @@ int uid_range_add_str(UidRange **p, unsigned *n, const char *s) { return uid_range_add(p, n, start, nr); } -int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid) { +int uid_range_next_lower(const UidRange *p, size_t n, uid_t *uid) { uid_t closest = UID_INVALID, candidate; assert(p); @@ -146,7 +146,7 @@ int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid) { candidate = *uid - 1; - for (unsigned i = 0; i < n; i++) { + for (size_t i = 0; i < n; i++) { uid_t begin, end; begin = p[i].start; @@ -168,11 +168,11 @@ int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid) { return 1; } -bool uid_range_contains(const UidRange *p, unsigned n, uid_t uid) { +bool uid_range_contains(const UidRange *p, size_t n, uid_t uid) { assert(p); assert(uid); - for (unsigned i = 0; i < n; i++) + for (size_t i = 0; i < n; i++) if (uid >= p[i].start && uid < p[i].start + p[i].nr) return true; diff --git a/src/shared/uid-range.h b/src/shared/uid-range.h index ef168cdb33..1e118ec42c 100644 --- a/src/shared/uid-range.h +++ b/src/shared/uid-range.h @@ -8,8 +8,8 @@ typedef struct UidRange { uid_t start, nr; } UidRange; -int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr); -int uid_range_add_str(UidRange **p, unsigned *n, const char *s); +int uid_range_add(UidRange **p, size_t *n, uid_t start, uid_t nr); +int uid_range_add_str(UidRange **p, size_t *n, const char *s); -int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid); -bool uid_range_contains(const UidRange *p, unsigned n, uid_t uid); +int uid_range_next_lower(const UidRange *p, size_t n, uid_t *uid); +bool uid_range_contains(const UidRange *p, size_t n, uid_t uid); diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 59b76c8620..1061b4e603 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -104,7 +104,7 @@ static Set *database_users = NULL, *database_groups = NULL; static uid_t search_uid = UID_INVALID; static UidRange *uid_range = NULL; -static unsigned n_uid_range = 0; +static size_t n_uid_range = 0; static UGIDAllocationRange login_defs = {}; static bool login_defs_need_warning = false; diff --git a/src/test/test-uid-range.c b/src/test/test-uid-range.c index 16cbab07d6..0c47e8ea53 100644 --- a/src/test/test-uid-range.c +++ b/src/test/test-uid-range.c @@ -9,7 +9,7 @@ int main(int argc, char *argv[]) { _cleanup_free_ UidRange *p = NULL; - unsigned n = 0; + size_t n = 0; uid_t search; assert_se(uid_range_add_str(&p, &n, "500-999") >= 0); From 2e37ebdae94b1604a6f5f7bf66a39758758a7d74 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 31 Mar 2022 13:11:16 +0200 Subject: [PATCH 2/8] test: port test-uid-range to tests.h --- src/test/test-uid-range.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/test-uid-range.c b/src/test/test-uid-range.c index 0c47e8ea53..2dcc2d04d5 100644 --- a/src/test/test-uid-range.c +++ b/src/test/test-uid-range.c @@ -3,11 +3,12 @@ #include #include "alloc-util.h" +#include "tests.h" #include "uid-range.h" #include "user-util.h" #include "util.h" -int main(int argc, char *argv[]) { +TEST(uid_range) { _cleanup_free_ UidRange *p = NULL; size_t n = 0; uid_t search; @@ -69,6 +70,6 @@ int main(int argc, char *argv[]) { assert_se(n == 1); assert_se(p[0].start == 20); assert_se(p[0].nr == 1983); - - return 0; } + +DEFINE_TEST_MAIN(LOG_DEBUG); From 5674aa7a2ca5c6c15659fa2c0fc0b1563ee0cceb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 31 Mar 2022 13:28:54 +0200 Subject: [PATCH 3/8] uid-range: add new uid_range_load_userns() for loading /proc/self/uid_map --- src/shared/uid-range.c | 49 +++++++++++++++++++++++++++++++++++++++ src/shared/uid-range.h | 2 ++ src/test/test-uid-range.c | 47 +++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/src/shared/uid-range.c b/src/shared/uid-range.c index 1adf4f62c2..c6b4b9d795 100644 --- a/src/shared/uid-range.c +++ b/src/shared/uid-range.c @@ -5,8 +5,13 @@ #include #include "alloc-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "format-util.h" #include "macro.h" +#include "path-util.h" #include "sort-util.h" +#include "stat-util.h" #include "uid-range.h" #include "user-util.h" @@ -178,3 +183,47 @@ bool uid_range_contains(const UidRange *p, size_t n, uid_t uid) { return false; } + +int uid_range_load_userns(UidRange **p, size_t *n, const char *path) { + _cleanup_fclose_ FILE *f = NULL; + int r; + + /* If 'path' is NULL loads the UID range of the userns namespace we run. Otherwise load the data from + * the specified file (which can be either uid_map or gid_map, in case caller needs to deal with GID + * maps). + * + * To simplify things this will modify the passed array in case of later failure. */ + + if (!path) + path = "/proc/self/uid_map"; + + f = fopen(path, "re"); + if (!f) { + r = -errno; + + if (r == -ENOENT && path_startswith(path, "/proc/") && proc_mounted() > 0) + return -EOPNOTSUPP; + + return r; + } + + for (;;) { + uid_t uid_base, uid_shift, uid_range; + int k; + + errno = 0; + k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT "\n", &uid_base, &uid_shift, &uid_range); + if (k == EOF) { + if (ferror(f)) + return errno_or_else(EIO); + + return 0; + } + if (k != 3) + return -EBADMSG; + + r = uid_range_add(p, n, uid_base, uid_range); + if (r < 0) + return r; + } +} diff --git a/src/shared/uid-range.h b/src/shared/uid-range.h index 1e118ec42c..d256a6eebb 100644 --- a/src/shared/uid-range.h +++ b/src/shared/uid-range.h @@ -13,3 +13,5 @@ int uid_range_add_str(UidRange **p, size_t *n, const char *s); int uid_range_next_lower(const UidRange *p, size_t n, uid_t *uid); bool uid_range_contains(const UidRange *p, size_t n, uid_t uid); + +int uid_range_load_userns(UidRange **p, size_t *n, const char *path); diff --git a/src/test/test-uid-range.c b/src/test/test-uid-range.c index 2dcc2d04d5..2e39628cef 100644 --- a/src/test/test-uid-range.c +++ b/src/test/test-uid-range.c @@ -3,10 +3,16 @@ #include #include "alloc-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" #include "tests.h" +#include "tmpfile-util.h" #include "uid-range.h" #include "user-util.h" #include "util.h" +#include "virt.h" TEST(uid_range) { _cleanup_free_ UidRange *p = NULL; @@ -72,4 +78,45 @@ TEST(uid_range) { assert_se(p[0].nr == 1983); } +TEST(load_userns) { + _cleanup_(unlink_and_freep) char *fn = NULL; + _cleanup_free_ UidRange *p = NULL; + _cleanup_fclose_ FILE *f = NULL; + size_t n = 0; + int r; + + r = uid_range_load_userns(&p, &n, NULL); + if (ERRNO_IS_NOT_SUPPORTED(r)) + return; + + assert_se(r >= 0); + assert_se(uid_range_contains(p, n, getuid())); + + r = running_in_userns(); + if (r == 0) { + assert_se(n == 1); + assert_se(p[0].start == 0); + assert_se(p[0].nr == UINT32_MAX); + } + + assert_se(fopen_temporary(NULL, &f, &fn) >= 0); + fputs("0 0 20\n" + "100 0 20\n", f); + assert_se(fflush_and_check(f) >= 0); + + p = mfree(p); + n = 0; + + assert_se(uid_range_load_userns(&p, &n, fn) >= 0); + + assert_se(uid_range_contains(p, n, 0)); + assert_se(uid_range_contains(p, n, 19)); + assert_se(!uid_range_contains(p, n, 20)); + + assert_se(!uid_range_contains(p, n, 99)); + assert_se(uid_range_contains(p, n, 100)); + assert_se(uid_range_contains(p, n, 119)); + assert_se(!uid_range_contains(p, n, 120)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 0a5c6a57c64345beb96171a5a1b824b9a185652f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 31 Mar 2022 14:28:00 +0200 Subject: [PATCH 4/8] uid-range: add some overflow checks --- src/shared/uid-range.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/shared/uid-range.c b/src/shared/uid-range.c index c6b4b9d795..1fa7218ee6 100644 --- a/src/shared/uid-range.c +++ b/src/shared/uid-range.c @@ -69,6 +69,9 @@ int uid_range_add(UidRange **p, size_t *n, uid_t start, uid_t nr) { if (nr <= 0) return 0; + if (start > UINT32_MAX - nr) /* overflow check */ + return -ERANGE; + for (size_t i = 0; i < *n; i++) { x = (*p) + i; if (uid_range_intersect(x, start, nr)) { @@ -149,6 +152,9 @@ int uid_range_next_lower(const UidRange *p, size_t n, uid_t *uid) { assert(p); assert(uid); + if (*uid == 0) + return -EBUSY; + candidate = *uid - 1; for (size_t i = 0; i < n; i++) { From 556560495ef695d7495e3bb930e8ce9f3964b99e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 31 Mar 2022 14:28:32 +0200 Subject: [PATCH 5/8] uid-range: replace uid_range_contains() by more generalized uid_range_covers() The former checks if one UID is inside the uid range set. The latter checks if a full UID range is inside the uid range set. The former is hence a special case of the latter. --- src/shared/uid-range.c | 13 +++++++++---- src/shared/uid-range.h | 6 +++++- src/test/test-uid-range.c | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/shared/uid-range.c b/src/shared/uid-range.c index 1fa7218ee6..1b4396a34c 100644 --- a/src/shared/uid-range.c +++ b/src/shared/uid-range.c @@ -179,12 +179,17 @@ int uid_range_next_lower(const UidRange *p, size_t n, uid_t *uid) { return 1; } -bool uid_range_contains(const UidRange *p, size_t n, uid_t uid) { - assert(p); - assert(uid); +bool uid_range_covers(const UidRange *p, size_t n, uid_t start, uid_t nr) { + assert(p || n == 0); + + if (nr == 0) /* empty range? always covered... */ + return true; + + if (start > UINT32_MAX - nr) /* range overflows? definitely not covered... */ + return false; for (size_t i = 0; i < n; i++) - if (uid >= p[i].start && uid < p[i].start + p[i].nr) + if (start >= p[i].start && start + nr <= p[i].start + p[i].nr) return true; return false; diff --git a/src/shared/uid-range.h b/src/shared/uid-range.h index d256a6eebb..7259c9e371 100644 --- a/src/shared/uid-range.h +++ b/src/shared/uid-range.h @@ -12,6 +12,10 @@ int uid_range_add(UidRange **p, size_t *n, uid_t start, uid_t nr); int uid_range_add_str(UidRange **p, size_t *n, const char *s); int uid_range_next_lower(const UidRange *p, size_t n, uid_t *uid); -bool uid_range_contains(const UidRange *p, size_t n, uid_t uid); +bool uid_range_covers(const UidRange *p, size_t n, uid_t start, uid_t nr); + +static inline bool uid_range_contains(const UidRange *p, size_t n, uid_t uid) { + return uid_range_covers(p, n, uid, 1); +} int uid_range_load_userns(UidRange **p, size_t *n, const char *path); diff --git a/src/test/test-uid-range.c b/src/test/test-uid-range.c index 2e39628cef..be8530bdd8 100644 --- a/src/test/test-uid-range.c +++ b/src/test/test-uid-range.c @@ -19,6 +19,10 @@ TEST(uid_range) { size_t n = 0; uid_t search; + assert_se(uid_range_covers(p, n, 0, 0)); + assert_se(!uid_range_covers(p, n, 0, 1)); + assert_se(!uid_range_covers(p, n, 100, UINT32_MAX)); + assert_se(uid_range_add_str(&p, &n, "500-999") >= 0); assert_se(n == 1); assert_se(p[0].start == 500); @@ -29,6 +33,17 @@ TEST(uid_range) { assert_se(uid_range_contains(p, n, 999)); assert_se(!uid_range_contains(p, n, 1000)); + assert_se(!uid_range_covers(p, n, 100, 150)); + assert_se(!uid_range_covers(p, n, 400, 200)); + assert_se(!uid_range_covers(p, n, 499, 1)); + assert_se(uid_range_covers(p, n, 500, 1)); + assert_se(uid_range_covers(p, n, 501, 10)); + assert_se(uid_range_covers(p, n, 999, 1)); + assert_se(!uid_range_covers(p, n, 999, 2)); + assert_se(!uid_range_covers(p, n, 1000, 1)); + assert_se(!uid_range_covers(p, n, 1000, 100)); + assert_se(!uid_range_covers(p, n, 1001, 100)); + search = UID_INVALID; assert_se(uid_range_next_lower(p, n, &search)); assert_se(search == 999); @@ -97,6 +112,8 @@ TEST(load_userns) { assert_se(n == 1); assert_se(p[0].start == 0); assert_se(p[0].nr == UINT32_MAX); + + assert_se(uid_range_covers(p, n, 0, UINT32_MAX)); } assert_se(fopen_temporary(NULL, &f, &fn) >= 0); From 63e8df046b40cdf7196462f41de4bca54882f7b2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 31 Mar 2022 14:37:29 +0200 Subject: [PATCH 6/8] pid1: add taint flag if uid/gid userns range too small MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will taint systemd if invoked in containers that do not have the full 16bit range of UIDs defined. we pretty much need uid root…nobody to be defined for a variety of purposes, hence let's add this taint flag. Of course taints are graceful, but it at least communicates the mess in some way... --- src/core/manager.c | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/core/manager.c b/src/core/manager.c index 768977dc3a..e92004edf3 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -82,6 +82,7 @@ #include "terminal-util.h" #include "time-util.h" #include "transaction.h" +#include "uid-range.h" #include "umask-util.h" #include "unit-name.h" #include "user-util.h" @@ -4350,16 +4351,34 @@ int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t re return 0; } +static int short_uid_range(const char *path) { + _cleanup_free_ UidRange *p = NULL; + size_t n = 0; + int r; + + assert(path); + + /* Taint systemd if we the UID range assigned to this environment doesn't at least cover 0…65534, + * i.e. from root to nobody. */ + + r = uid_range_load_userns(&p, &n, path); + if (ERRNO_IS_NOT_SUPPORTED(r)) + return false; + if (r < 0) + return log_debug_errno(r, "Failed to load %s: %m", path); + + return !uid_range_covers(p, n, 0, 65535); +} + char *manager_taint_string(Manager *m) { _cleanup_free_ char *destination = NULL, *overflowuid = NULL, *overflowgid = NULL; struct utsname uts; char *buf, *e; int r; - /* Returns a "taint string", e.g. "local-hwclock:var-run-bad". - * Only things that are detected at runtime should be tagged - * here. For stuff that is set during compilation, emit a warning - * in the configuration phase. */ + /* Returns a "taint string", e.g. "local-hwclock:var-run-bad". Only things that are detected at + * runtime should be tagged here. For stuff that is set during compilation, emit a warning in the + * configuration phase. */ assert(m); @@ -4370,7 +4389,9 @@ char *manager_taint_string(Manager *m) { "var-run-bad:" "overflowuid-not-65534:" "overflowgid-not-65534:" - "old-kernel:")); + "old-kernel:" + "short-uid-range:" + "short-gid-range:")); if (!buf) return NULL; @@ -4396,7 +4417,6 @@ char *manager_taint_string(Manager *m) { r = read_one_line_file("/proc/sys/kernel/overflowuid", &overflowuid); if (r >= 0 && !streq(overflowuid, "65534")) e = stpcpy(e, "overflowuid-not-65534:"); - r = read_one_line_file("/proc/sys/kernel/overflowgid", &overflowgid); if (r >= 0 && !streq(overflowgid, "65534")) e = stpcpy(e, "overflowgid-not-65534:"); @@ -4405,6 +4425,11 @@ char *manager_taint_string(Manager *m) { if (strverscmp_improved(uts.release, KERNEL_BASELINE_VERSION) < 0) e = stpcpy(e, "old-kernel:"); + if (short_uid_range("/proc/self/uid_map") > 0) + e = stpcpy(e, "short-uid-range:"); + if (short_uid_range("/proc/self/gid_map") > 0) + e = stpcpy(e, "short-gid-range:"); + /* remove the last ':' */ if (e != buf) e[-1] = 0; From 49888d31b6ccb3c16e9564049290f1d21df15abf Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 31 Mar 2022 14:39:24 +0200 Subject: [PATCH 7/8] update TODO --- TODO | 2 -- 1 file changed, 2 deletions(-) diff --git a/TODO b/TODO index 05794b2d7b..3908972328 100644 --- a/TODO +++ b/TODO @@ -761,8 +761,6 @@ Features: systemd-journald writes to /var/log/journal, which could be useful when we doing disk usage calculations and so on. -* taint systemd if there are fewer than 65536 users assigned (userns) to the system. - * deprecate RootDirectoryStartOnly= in favour of a new ExecStart= prefix char * add a new RuntimeDirectoryPreserve= mode that defines a similar lifecycle for From 9cce7fb193e5b12dee091ff7023669ef9034c1c0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 31 Mar 2022 15:22:33 +0200 Subject: [PATCH 8/8] userdbctl: also show available UID range in current userns Containers generally have a smaller UID range assigned than host systems. Let's visualize this in the user/group tables. We insert markers for unavailable regions. This way display is identical to status quo ante on host systems, but in containers unavailable ranges will be shown as that. And while we are at it, also hide well-known UID ranges when they are outside of userns uid_map range. This is mostly about the "container" range. It's pointless showing the cotnainer range (i.e. a range UID > 65535) if that range isn#t available in the container anyway. --- src/userdb/userdbctl.c | 220 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 206 insertions(+), 14 deletions(-) diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index c3d61f0059..b299bb8e11 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -17,6 +17,7 @@ #include "socket-util.h" #include "strv.h" #include "terminal-util.h" +#include "uid-range.h" #include "user-record-show.h" #include "user-util.h" #include "userdb.h" @@ -167,14 +168,22 @@ static const struct { }, }; -static int table_add_uid_boundaries(Table *table) { +static int table_add_uid_boundaries( + Table *table, + const UidRange *p, + size_t n) { int r; assert(table); + assert(p || n == 0); for (size_t i = 0; i < ELEMENTSOF(uid_range_table); i++) { _cleanup_free_ char *name = NULL, *comment = NULL; + if (n > 0 && + !uid_range_covers(p, n, uid_range_table[i].first, uid_range_table[i].last - uid_range_table[i].first + 1)) + continue; + name = strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN), " begin ", uid_range_table[i].name, " users ", special_glyph(SPECIAL_GLYPH_ARROW_DOWN)); @@ -199,7 +208,7 @@ static int table_add_uid_boundaries(Table *table) { TABLE_SET_COLOR, ansi_grey(), TABLE_EMPTY, TABLE_EMPTY, - TABLE_INT, -1); /* sort before an other entry with the same UID */ + TABLE_INT, -1); /* sort before any other entry with the same UID */ if (r < 0) return table_log_add_error(r); @@ -229,7 +238,7 @@ static int table_add_uid_boundaries(Table *table) { TABLE_SET_COLOR, ansi_grey(), TABLE_EMPTY, TABLE_EMPTY, - TABLE_INT, 1); /* sort after an other entry with the same UID */ + TABLE_INT, 1); /* sort after any other entry with the same UID */ if (r < 0) return table_log_add_error(r); } @@ -237,6 +246,104 @@ static int table_add_uid_boundaries(Table *table) { return ELEMENTSOF(uid_range_table) * 2; } +static int add_unavailable_uid(Table *table, uid_t start, uid_t end) { + _cleanup_free_ char *name = NULL; + int r; + + assert(table); + assert(start <= end); + + name = strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN), + " begin unavailable users ", + special_glyph(SPECIAL_GLYPH_ARROW_DOWN)); + if (!name) + return log_oom(); + + r = table_add_many( + table, + TABLE_STRING, special_glyph(SPECIAL_GLYPH_TREE_TOP), + TABLE_STRING, name, + TABLE_SET_COLOR, ansi_grey(), + TABLE_EMPTY, + TABLE_UID, start, + TABLE_SET_COLOR, ansi_grey(), + TABLE_EMPTY, + TABLE_STRING, "First unavailable user", + TABLE_SET_COLOR, ansi_grey(), + TABLE_EMPTY, + TABLE_EMPTY, + TABLE_INT, -1); /* sort before an other entry with the same UID */ + if (r < 0) + return table_log_add_error(r); + + free(name); + name = strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN), + " end unavailable users ", + special_glyph(SPECIAL_GLYPH_ARROW_DOWN)); + if (!name) + return log_oom(); + + r = table_add_many( + table, + TABLE_STRING, special_glyph(SPECIAL_GLYPH_TREE_RIGHT), + TABLE_STRING, name, + TABLE_SET_COLOR, ansi_grey(), + TABLE_EMPTY, + TABLE_UID, end, + TABLE_SET_COLOR, ansi_grey(), + TABLE_EMPTY, + TABLE_STRING, "Last unavailable user", + TABLE_SET_COLOR, ansi_grey(), + TABLE_EMPTY, + TABLE_EMPTY, + TABLE_INT, 1); /* sort after any other entry with the same UID */ + if (r < 0) + return table_log_add_error(r); + + return 2; +} + +static int table_add_uid_map( + Table *table, + const UidRange *p, + size_t n, + int (*add_unavailable)(Table *t, uid_t start, uid_t end)) { + + uid_t focus = 0; + int n_added = 0, r; + + assert(table); + assert(p || n == 0); + assert(add_unavailable); + + for (size_t i = 0; i < n; i++) { + if (focus < p[i].start) { + r = add_unavailable(table, focus, p[i].start-1); + if (r < 0) + return r; + + n_added += r; + } + + if (p[i].start > UINT32_MAX - p[i].nr) { /* overflow check */ + focus = UINT32_MAX; + break; + } + + focus = p[i].start + p[i].nr; + } + + if (focus < UINT32_MAX-1) { + r = add_unavailable(table, focus, UINT32_MAX-1); + if (r < 0) + return r; + + n_added += r; + } + + return n_added; +} + static int display_user(int argc, char *argv[], void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; bool draw_separator = false; @@ -322,12 +429,22 @@ static int display_user(int argc, char *argv[], void *userdata) { } if (table) { - int boundary_lines; + _cleanup_free_ UidRange *uid_range = NULL; + int boundary_lines, uid_map_lines; + size_t n_uid_range; - boundary_lines = table_add_uid_boundaries(table); + r = uid_range_load_userns(&uid_range, &n_uid_range, "/proc/self/uid_map"); + if (r < 0) + log_debug_errno(r, "Failed to load /proc/self/uid_map, ignoring: %m"); + + boundary_lines = table_add_uid_boundaries(table, uid_range, n_uid_range); if (boundary_lines < 0) return boundary_lines; + uid_map_lines = table_add_uid_map(table, uid_range, n_uid_range, add_unavailable_uid); + if (uid_map_lines < 0) + return uid_map_lines; + if (table_get_rows(table) > 1) { r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) @@ -335,8 +452,11 @@ static int display_user(int argc, char *argv[], void *userdata) { } if (arg_legend) { - if (table_get_rows(table) > 1) - printf("\n%zu users listed.\n", table_get_rows(table) - 1 - boundary_lines); + size_t k; + + k = table_get_rows(table) - 1 - boundary_lines - uid_map_lines; + if (k > 0) + printf("\n%zu users listed.\n", k); else printf("No users.\n"); } @@ -411,14 +531,22 @@ static int show_group(GroupRecord *gr, Table *table) { return 0; } -static int table_add_gid_boundaries(Table *table) { +static int table_add_gid_boundaries( + Table *table, + const UidRange *p, + size_t n) { int r; assert(table); + assert(p || n == 0); for (size_t i = 0; i < ELEMENTSOF(uid_range_table); i++) { _cleanup_free_ char *name = NULL, *comment = NULL; + if (n > 0 && + !uid_range_covers(p, n, uid_range_table[i].first, uid_range_table[i].last)) + continue; + name = strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN), " begin ", uid_range_table[i].name, " groups ", special_glyph(SPECIAL_GLYPH_ARROW_DOWN)); @@ -440,7 +568,7 @@ static int table_add_gid_boundaries(Table *table) { TABLE_SET_COLOR, ansi_grey(), TABLE_STRING, comment, TABLE_SET_COLOR, ansi_grey(), - TABLE_INT, -1); /* sort before an other entry with the same GID */ + TABLE_INT, -1); /* sort before any other entry with the same GID */ if (r < 0) return table_log_add_error(r); @@ -467,7 +595,7 @@ static int table_add_gid_boundaries(Table *table) { TABLE_SET_COLOR, ansi_grey(), TABLE_STRING, comment, TABLE_SET_COLOR, ansi_grey(), - TABLE_INT, 1); /* sort after an other entry with the same GID */ + TABLE_INT, 1); /* sort after any other entry with the same GID */ if (r < 0) return table_log_add_error(r); } @@ -475,6 +603,57 @@ static int table_add_gid_boundaries(Table *table) { return ELEMENTSOF(uid_range_table) * 2; } +static int add_unavailable_gid(Table *table, uid_t start, uid_t end) { + _cleanup_free_ char *name = NULL; + int r; + + assert(table); + assert(start <= end); + + name = strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN), + " begin unavailable groups ", + special_glyph(SPECIAL_GLYPH_ARROW_DOWN)); + if (!name) + return log_oom(); + + r = table_add_many( + table, + TABLE_STRING, special_glyph(SPECIAL_GLYPH_TREE_TOP), + TABLE_STRING, name, + TABLE_SET_COLOR, ansi_grey(), + TABLE_EMPTY, + TABLE_GID, start, + TABLE_SET_COLOR, ansi_grey(), + TABLE_STRING, "First unavailable group", + TABLE_SET_COLOR, ansi_grey(), + TABLE_INT, -1); /* sort before any other entry with the same GID */ + if (r < 0) + return table_log_add_error(r); + + free(name); + name = strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN), + " end unavailable groups ", + special_glyph(SPECIAL_GLYPH_ARROW_DOWN)); + if (!name) + return log_oom(); + + r = table_add_many( + table, + TABLE_STRING, special_glyph(SPECIAL_GLYPH_TREE_RIGHT), + TABLE_STRING, name, + TABLE_SET_COLOR, ansi_grey(), + TABLE_EMPTY, + TABLE_GID, end, + TABLE_SET_COLOR, ansi_grey(), + TABLE_STRING, "Last unavailable group", + TABLE_SET_COLOR, ansi_grey(), + TABLE_INT, 1); /* sort after any other entry with the same GID */ + if (r < 0) + return table_log_add_error(r); + + return 2; +} + static int display_group(int argc, char *argv[], void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; bool draw_separator = false; @@ -559,12 +738,22 @@ static int display_group(int argc, char *argv[], void *userdata) { } if (table) { - int boundary_lines; + _cleanup_free_ UidRange *gid_range = NULL; + int boundary_lines, gid_map_lines; + size_t n_gid_range; - boundary_lines = table_add_gid_boundaries(table); + r = uid_range_load_userns(&gid_range, &n_gid_range, "/proc/self/gid_map"); + if (r < 0) + log_debug_errno(r, "Failed to load /proc/self/gid_map, ignoring: %m"); + + boundary_lines = table_add_gid_boundaries(table, gid_range, n_gid_range); if (boundary_lines < 0) return boundary_lines; + gid_map_lines = table_add_uid_map(table, gid_range, n_gid_range, add_unavailable_gid); + if (gid_map_lines < 0) + return gid_map_lines; + if (table_get_rows(table) > 1) { r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) @@ -572,8 +761,11 @@ static int display_group(int argc, char *argv[], void *userdata) { } if (arg_legend) { - if (table_get_rows(table) > 1) - printf("\n%zu groups listed.\n", table_get_rows(table) - 1 - boundary_lines); + size_t k; + + k = table_get_rows(table) - 1 - boundary_lines - gid_map_lines; + if (k > 0) + printf("\n%zu groups listed.\n", k); else printf("No groups.\n"); }