diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index af745b6567..9c95c63e70 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -1276,6 +1276,32 @@ int bus_machine_method_open_root_directory(sd_bus_message *message, void *userda return sd_bus_reply_method_return(message, "h", fd); } +int bus_machine_method_get_uid_shift(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Machine *m = userdata; + uid_t shift = 0; + int r; + + assert(message); + assert(m); + + /* You wonder why this is a method and not a property? Well, properties are not supposed to return errors, but + * we kinda have to for this. */ + + if (m->class == MACHINE_HOST) + return sd_bus_reply_method_return(message, "u", UINT32_C(0)); + + if (m->class != MACHINE_CONTAINER) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "UID/GID shift may only be determined for container machines."); + + r = machine_get_uid_shift(m, &shift); + if (r == -ENXIO) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Machine %s uses a complex UID/GID mapping, cannot determine shift", m->name); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, "u", (uint32_t) shift); +} + const sd_bus_vtable machine_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Machine, name), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1293,6 +1319,7 @@ const sd_bus_vtable machine_vtable[] = { SD_BUS_METHOD("Kill", "si", NULL, bus_machine_method_kill, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("GetAddresses", NULL, "a(iay)", bus_machine_method_get_addresses, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_machine_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetUIDShift", NULL, "u", bus_machine_method_get_uid_shift, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("OpenPTY", NULL, "hs", bus_machine_method_open_pty, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("OpenLogin", NULL, "hs", bus_machine_method_open_login, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("OpenShell", "ssasas", "hs", bus_machine_method_open_shell, SD_BUS_VTABLE_UNPRIVILEGED), diff --git a/src/machine/machine-dbus.h b/src/machine/machine-dbus.h index c513783480..2aa7b4ce06 100644 --- a/src/machine/machine-dbus.h +++ b/src/machine/machine-dbus.h @@ -39,6 +39,7 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_open_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_get_uid_shift(sd_bus_message *message, void *userdata, sd_bus_error *error); int machine_send_signal(Machine *m, bool new_machine); int machine_send_create_reply(Machine *m, sd_bus_error *error); diff --git a/src/machine/machine.c b/src/machine/machine.c index 067a7e2866..d3433d9b96 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -38,6 +38,7 @@ #include "parse-util.h" #include "process-util.h" #include "special.h" +#include "stdio-util.h" #include "string-table.h" #include "terminal-util.h" #include "unit-name.h" @@ -604,6 +605,96 @@ void machine_release_unit(Machine *m) { m->unit = mfree(m->unit); } +int machine_get_uid_shift(Machine *m, uid_t *ret) { + char p[strlen("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1]; + uid_t uid_base, uid_shift, uid_range; + gid_t gid_base, gid_shift, gid_range; + _cleanup_fclose_ FILE *f = NULL; + int k; + + assert(m); + assert(ret); + + /* Return the base UID/GID of the specified machine. Note that this only works for containers with simple + * mappings. In most cases setups should be simple like this, and administrators should only care about the + * basic offset a container has relative to the host. This is what this function exposes. + * + * If we encounter any more complex mappings we politely refuse this with ENXIO. */ + + if (m->class == MACHINE_HOST) { + *ret = 0; + return 0; + } + + if (m->class != MACHINE_CONTAINER) + return -EOPNOTSUPP; + + xsprintf(p, "/proc/" PID_FMT "/uid_map", m->leader); + f = fopen(p, "re"); + if (!f) { + if (errno == ENOENT) { + /* If the file doesn't exist, user namespacing is off in the kernel, return a zero mapping hence. */ + *ret = 0; + return 0; + } + + return -errno; + } + + /* Read the first line. There's at least one. */ + errno = 0; + k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT "\n", &uid_base, &uid_shift, &uid_range); + if (k != 3) { + if (ferror(f)) + return -errno; + + return -EBADMSG; + } + + /* Not a mapping starting at 0? Then it's a complex mapping we can't expose here. */ + if (uid_base != 0) + return -ENXIO; + /* Insist that at least the nobody user is mapped, everything else is weird, and hence complex, and we don't support it */ + if (uid_range < (uid_t) 65534U) + return -ENXIO; + + /* If there's more than one line, then we don't support this mapping. */ + if (fgetc(f) != EOF) + return -ENXIO; + + fclose(f); + + xsprintf(p, "/proc/" PID_FMT "/gid_map", m->leader); + f = fopen(p, "re"); + if (!f) + return -errno; + + /* Read the first line. There's at least one. */ + errno = 0; + k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT "\n", &gid_base, &gid_shift, &gid_range); + if (k != 3) { + if (ferror(f)) + return -errno; + + return -EBADMSG; + } + + /* If there's more than one line, then we don't support this file. */ + if (fgetc(f) != EOF) + return -ENXIO; + + /* If the UID and GID mapping doesn't match, we don't support this mapping. */ + if (uid_base != (uid_t) gid_base) + return -ENXIO; + if (uid_shift != (uid_t) gid_shift) + return -ENXIO; + if (uid_range != (uid_t) gid_range) + return -ENXIO; + + *ret = uid_shift; + return 0; +} + static const char* const machine_class_table[_MACHINE_CLASS_MAX] = { [MACHINE_CONTAINER] = "container", [MACHINE_VM] = "vm", diff --git a/src/machine/machine.h b/src/machine/machine.h index e5d75361a9..6bdb204ed6 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -108,3 +108,5 @@ KillWho kill_who_from_string(const char *s) _pure_; int machine_openpt(Machine *m, int flags); int machine_open_terminal(Machine *m, const char *path, int mode); + +int machine_get_uid_shift(Machine *m, uid_t *ret); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index fe4f1b7726..99be391e56 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -611,6 +611,37 @@ static int print_os_release(sd_bus *bus, const char *method, const char *name, c return 0; } +static int print_uid_shift(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + uint32_t shift; + int r; + + assert(bus); + assert(name); + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineUIDShift", + &error, + &reply, + "s", name); + if (r < 0) + return log_debug_errno(r, "Failed to query UID/GID shift: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "u", &shift); + if (r < 0) + return r; + + if (shift == 0) /* Don't show trivial mappings */ + return 0; + + printf(" UID Shift: %" PRIu32 "\n", shift); + return 0; +} + typedef struct MachineStatusInfo { char *name; sd_id128_t id; @@ -714,6 +745,8 @@ static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { print_os_release(bus, "GetMachineOSRelease", i->name, "\t OS: "); + print_uid_shift(bus, i->name); + if (i->unit) { printf("\t Unit: %s\n", i->unit); show_unit_cgroup(bus, i->unit, i->leader); diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index fd9e5b56fc..c9b92d2765 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -729,6 +729,26 @@ static int method_open_machine_root_directory(sd_bus_message *message, void *use return bus_machine_method_open_root_directory(message, machine, error); } +static int method_get_machine_uid_shift(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_get_uid_shift(message, machine, error); +} + static int method_remove_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_(image_unrefp) Image* i = NULL; const char *name; @@ -1416,6 +1436,7 @@ const sd_bus_vtable manager_vtable[] = { SD_BUS_METHOD("CopyFromMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("CopyToMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("OpenMachineRootDirectory", "s", "h", method_open_machine_root_directory, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetMachineUIDShift", "s", "u", method_get_machine_uid_shift, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, SD_BUS_VTABLE_UNPRIVILEGED), diff --git a/src/machine/org.freedesktop.machine1.conf b/src/machine/org.freedesktop.machine1.conf index 82ebfba50c..daa365a9dd 100644 --- a/src/machine/org.freedesktop.machine1.conf +++ b/src/machine/org.freedesktop.machine1.conf @@ -64,6 +64,10 @@ send_interface="org.freedesktop.machine1.Manager" send_member="GetMachineOSRelease"/> + + @@ -148,6 +152,10 @@ send_interface="org.freedesktop.machine1.Machine" send_member="GetOSRelease"/> + +