/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #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" UidRange *uid_range_free(UidRange *range) { if (!range) return NULL; free(range->entries); return mfree(range); } static bool uid_range_entry_intersect(const UidRangeEntry *a, const UidRangeEntry *b) { assert(a); assert(b); return a->start <= b->start + b->nr && a->start + a->nr >= b->start; } static int uid_range_entry_compare(const UidRangeEntry *a, const UidRangeEntry *b) { int r; assert(a); assert(b); r = CMP(a->start, b->start); if (r != 0) return r; return CMP(a->nr, b->nr); } static void uid_range_coalesce(UidRange *range) { assert(range); if (range->n_entries <= 0) return; typesafe_qsort(range->entries, range->n_entries, uid_range_entry_compare); for (size_t i = 0; i < range->n_entries; i++) { UidRangeEntry *x = range->entries + i; for (size_t j = i + 1; j < range->n_entries; j++) { UidRangeEntry *y = range->entries + j; uid_t begin, end; if (!uid_range_entry_intersect(x, y)) break; begin = MIN(x->start, y->start); end = MAX(x->start + x->nr, y->start + y->nr); x->start = begin; x->nr = end - begin; if (range->n_entries > j + 1) memmove(y, y + 1, sizeof(UidRangeEntry) * (range->n_entries - j - 1)); range->n_entries--; j--; } } } int uid_range_add_internal(UidRange **range, uid_t start, uid_t nr, bool coalesce) { _cleanup_(uid_range_freep) UidRange *range_new = NULL; UidRange *p; assert(range); if (nr <= 0) return 0; if (start > UINT32_MAX - nr) /* overflow check */ return -ERANGE; if (*range) p = *range; else { range_new = new0(UidRange, 1); if (!range_new) return -ENOMEM; p = range_new; } if (!GREEDY_REALLOC(p->entries, p->n_entries + 1)) return -ENOMEM; p->entries[p->n_entries++] = (UidRangeEntry) { .start = start, .nr = nr, }; if (coalesce) uid_range_coalesce(p); TAKE_PTR(range_new); *range = p; return 0; } int uid_range_add_str(UidRange **range, const char *s) { uid_t start, end; int r; assert(range); assert(s); r = parse_uid_range(s, &start, &end); if (r < 0) return r; return uid_range_add_internal(range, start, end - start + 1, /* coalesce = */ true); } int uid_range_next_lower(const UidRange *range, uid_t *uid) { uid_t closest = UID_INVALID, candidate; assert(range); assert(uid); if (*uid == 0) return -EBUSY; candidate = *uid - 1; for (size_t i = 0; i < range->n_entries; i++) { uid_t begin, end; begin = range->entries[i].start; end = range->entries[i].start + range->entries[i].nr - 1; if (candidate >= begin && candidate <= end) { *uid = candidate; return 1; } if (end < candidate) closest = end; } if (closest == UID_INVALID) return -EBUSY; *uid = closest; return 1; } bool uid_range_covers(const UidRange *range, uid_t start, uid_t nr) { if (nr == 0) /* empty range? always covered... */ return true; if (start > UINT32_MAX - nr) /* range overflows? definitely not covered... */ return false; if (!range) return false; for (size_t i = 0; i < range->n_entries; i++) if (start >= range->entries[i].start && start + nr <= range->entries[i].start + range->entries[i].nr) return true; return false; } int uid_range_load_userns(UidRange **ret, const char *path) { _cleanup_(uid_range_freep) UidRange *range = NULL; _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. */ assert(ret); if (!path) path = "/proc/self/uid_map"; f = fopen(path, "re"); if (!f) { r = -errno; if (r == -ENOENT && path_startswith(path, "/proc/")) return proc_mounted() > 0 ? -EOPNOTSUPP : -ENOSYS; return r; } range = new0(UidRange, 1); if (!range) return -ENOMEM; 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); break; } if (k != 3) return -EBADMSG; r = uid_range_add_internal(&range, uid_base, uid_range, /* coalesce = */ false); if (r < 0) return r; } uid_range_coalesce(range); *ret = TAKE_PTR(range); return 0; }