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 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; diff --git a/src/shared/uid-range.c b/src/shared/uid-range.c index 2c07a1b7a8..1b4396a34c 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" @@ -17,12 +22,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 +59,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 +69,10 @@ 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++) { + 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)) { found = true; @@ -100,7 +108,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,15 +146,18 @@ 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); assert(uid); + if (*uid == 0) + return -EBUSY; + 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,13 +179,62 @@ 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) { - assert(p); - assert(uid); +bool uid_range_covers(const UidRange *p, size_t n, uid_t start, uid_t nr) { + assert(p || n == 0); - for (unsigned i = 0; i < n; i++) - if (uid >= p[i].start && uid < p[i].start + p[i].nr) + 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 (start >= p[i].start && start + nr <= p[i].start + p[i].nr) return true; 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 ef168cdb33..7259c9e371 100644 --- a/src/shared/uid-range.h +++ b/src/shared/uid-range.h @@ -8,8 +8,14 @@ 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_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/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..be8530bdd8 100644 --- a/src/test/test-uid-range.c +++ b/src/test/test-uid-range.c @@ -3,15 +3,26 @@ #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" -int main(int argc, char *argv[]) { +TEST(uid_range) { _cleanup_free_ UidRange *p = NULL; - unsigned n = 0; + 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); @@ -22,6 +33,17 @@ int main(int argc, char *argv[]) { 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); @@ -69,6 +91,49 @@ int main(int argc, char *argv[]) { assert_se(n == 1); assert_se(p[0].start == 20); assert_se(p[0].nr == 1983); - - return 0; } + +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(uid_range_covers(p, n, 0, 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); 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"); }