From c4a53ebf7a51674a14f2273bb6fac382ca75f9eb Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 21 Jul 2020 22:30:16 +0100 Subject: [PATCH 1/2] firstboot: Tighten up passwd/shadow handling There are a lot of edge cases that the current implementation doesn't handle, especially in cases where one of passwd/shadow exists and the other doesn't exist. For example, if --root-password is specified, we will write /etc/shadow but won't add a root entry to /etc/passwd if there is none. To fix some of these issues, we constrain systemd-firstboot to only modify /etc/passwd and /etc/shadow if both do not exist already (or --force) is specified. On top of that, we calculate all necessary information for both passwd and shadow upfront so we can take it all into account when writing the actual files. If no root password options are given --force is specified or both files do not exist, we lock the root account for security purposes. --- man/systemd-firstboot.xml | 5 +-- src/firstboot/firstboot.c | 74 ++++++++++++++++++--------------------- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml index 491ca6e9bf..8c9ea80f15 100644 --- a/man/systemd-firstboot.xml +++ b/man/systemd-firstboot.xml @@ -164,9 +164,10 @@ - Sets the password of the system's root user. This creates a + Sets the password of the system's root user. This creates/modifies the + passwd5 and shadow5 - file. This setting exists in three forms: accepts the password to + files. This setting exists in three forms: accepts the password to set directly on the command line, reads it from a file and accepts an already hashed password on the command line. See shadow5 diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 5c9ee779ca..61565145dd 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -606,6 +606,8 @@ static int write_root_passwd(const char *passwd_path, const char *password) { _cleanup_(unlink_and_freep) char *passwd_tmp = NULL; int r; + assert(password); + r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp); if (r < 0) return r; @@ -669,6 +671,8 @@ static int write_root_shadow(const char *shadow_path, const char *hashed_passwor _cleanup_(unlink_and_freep) char *shadow_tmp = NULL; int r; + assert(hashed_password); + r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp); if (r < 0) return r; @@ -734,70 +738,53 @@ static int write_root_shadow(const char *shadow_path, const char *hashed_passwor static int process_root_password(void) { _cleanup_close_ int lock = -1; struct crypt_data cd = {}; - const char *hashed_password; - const char *etc_shadow; + const char *password, *hashed_password; + const char *etc_passwd, *etc_shadow; int r; + etc_passwd = prefix_roota(arg_root, "/etc/passwd"); etc_shadow = prefix_roota(arg_root, "/etc/shadow"); - if (laccess(etc_shadow, F_OK) >= 0 && !arg_force) + + /* We only mess with passwd and shadow if both do not exist or --force is specified. These files are + * tightly coupled and hence we make sure we have permission from the user to create/modify both + * files. */ + if ((laccess(etc_passwd, F_OK) >= 0 || laccess(etc_shadow, F_OK) >= 0) && !arg_force) return 0; - (void) mkdir_parents(etc_shadow, 0755); + (void) mkdir_parents(etc_passwd, 0755); lock = take_etc_passwd_lock(arg_root); if (lock < 0) - return log_error_errno(lock, "Failed to take a lock: %m"); - - if (arg_delete_root_password) { - const char *etc_passwd; - - /* Mixing alloca() and other stuff that touches the stack in one expression is not portable. */ - etc_passwd = prefix_roota(arg_root, "/etc/passwd"); - - r = write_root_passwd(etc_passwd, ""); - if (r < 0) - return log_error_errno(r, "Failed to write %s: %m", etc_passwd); - - log_info("%s written", etc_passwd); - - return 0; - } + return log_error_errno(lock, "Failed to take a lock on %s: %m", etc_passwd); if (arg_copy_root_password && arg_root) { struct spwd *p; errno = 0; p = getspnam("root"); - if (p || errno != ENOENT) { - if (!p) { - if (!errno) - errno = EIO; + if (!p) + return log_error_errno(errno_or_else(EIO), "Failed to find shadow entry for root: %m"); - return log_error_errno(errno, "Failed to find shadow entry for root: %m"); - } + r = free_and_strdup(&arg_root_password, p->sp_pwdp); + if (r < 0) + return log_oom(); - r = write_root_shadow(etc_shadow, p->sp_pwdp); - if (r < 0) - return log_error_errno(r, "Failed to write %s: %m", etc_shadow); - - log_info("%s copied.", etc_shadow); - return 0; - } + arg_root_password_is_hashed = true; } r = prompt_root_password(); if (r < 0) return r; - if (!arg_root_password) - return 0; - - if (arg_root_password_is_hashed) + if (arg_root_password && arg_root_password_is_hashed) { + password = "x"; hashed_password = arg_root_password; - else { + } else if (arg_root_password) { _cleanup_free_ char *salt = NULL; /* hashed_password points inside cd after crypt_r returns so cd has function scope. */ + password = "x"; + r = make_salt(&salt); if (r < 0) return log_error_errno(r, "Failed to get salt: %m"); @@ -807,7 +794,16 @@ static int process_root_password(void) { if (!hashed_password) return log_error_errno(errno == 0 ? SYNTHETIC_ERRNO(EINVAL) : errno, "Failed to encrypt password: %m"); - } + } else if (arg_delete_root_password) + password = hashed_password = ""; + else + password = hashed_password = "!"; + + r = write_root_passwd(etc_passwd, password); + if (r < 0) + return log_error_errno(r, "Failed to write %s: %m", etc_passwd); + + log_info("%s written", etc_passwd); r = write_root_shadow(etc_shadow, hashed_password); if (r < 0) From 28900a1bfe919fbb10bd3b7b248b54c7aff96c2e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 21 Jul 2020 22:35:21 +0100 Subject: [PATCH 2/2] firstboot: Add --root-shell option --- man/systemd-firstboot.xml | 16 ++++++- src/firstboot/firstboot.c | 99 +++++++++++++++++++++++++++++++++++---- 2 files changed, 105 insertions(+), 10 deletions(-) diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml index 8c9ea80f15..0976394b66 100644 --- a/man/systemd-firstboot.xml +++ b/man/systemd-firstboot.xml @@ -177,6 +177,14 @@ + + + + Sets the shell of the system's root user. This creates/modifies the + passwd5 + file. + + @@ -192,6 +200,7 @@ + Prompt the user interactively for a specific basic setting. Note that any explicit configuration settings @@ -208,7 +217,8 @@ , , , - in combination. + , + in combination. @@ -217,6 +227,7 @@ + Copy a specific basic setting from the host. This only works in combination with @@ -231,7 +242,8 @@ , , , - in combination. + , + in combination. diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 61565145dd..82cd4040f9 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -49,16 +49,19 @@ static char *arg_timezone = NULL; static char *arg_hostname = NULL; static sd_id128_t arg_machine_id = {}; static char *arg_root_password = NULL; +static char *arg_root_shell = NULL; static char *arg_kernel_cmdline = NULL; static bool arg_prompt_locale = false; static bool arg_prompt_keymap = false; static bool arg_prompt_timezone = false; static bool arg_prompt_hostname = false; static bool arg_prompt_root_password = false; +static bool arg_prompt_root_shell = false; static bool arg_copy_locale = false; static bool arg_copy_keymap = false; static bool arg_copy_timezone = false; static bool arg_copy_root_password = false; +static bool arg_copy_root_shell = false; static bool arg_force = false; static bool arg_delete_root_password = false; static bool arg_root_password_is_hashed = false; @@ -601,7 +604,40 @@ static int prompt_root_password(void) { return 0; } -static int write_root_passwd(const char *passwd_path, const char *password) { +static int prompt_root_shell(void) { + int r; + + if (arg_root_shell || !arg_prompt_root_shell) + return 0; + + print_welcome(); + putchar('\n'); + + for (;;) { + _cleanup_free_ char *s = NULL; + + r = ask_string(&s, "%s Please enter root shell for new system (empty to skip): ", special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET)); + if (r < 0) + return log_error_errno(r, "Failed to query root shell: %m"); + + if (isempty(s)) { + log_warning("No shell entered, skipping."); + break; + } + + if (!valid_shell(s)) { + log_error("Specified shell invalid."); + continue; + } + + arg_root_shell = TAKE_PTR(s); + break; + } + + return 0; +} + +static int write_root_passwd(const char *passwd_path, const char *password, const char *shell) { _cleanup_fclose_ FILE *original = NULL, *passwd = NULL; _cleanup_(unlink_and_freep) char *passwd_tmp = NULL; int r; @@ -622,8 +658,11 @@ static int write_root_passwd(const char *passwd_path, const char *password) { while ((r = fgetpwent_sane(original, &i)) > 0) { - if (streq(i->pw_name, "root")) + if (streq(i->pw_name, "root")) { i->pw_passwd = (char *) password; + if (shell) + i->pw_shell = (char *) shell; + } r = putpwent_sane(i, passwd); if (r < 0) @@ -640,7 +679,7 @@ static int write_root_passwd(const char *passwd_path, const char *password) { .pw_gid = 0, .pw_gecos = (char *) "Super User", .pw_dir = (char *) "/root", - .pw_shell = (char *) "/bin/sh", + .pw_shell = (char *) (shell ?: "/bin/sh"), }; if (errno != ENOENT) @@ -735,7 +774,7 @@ static int write_root_shadow(const char *shadow_path, const char *hashed_passwor return 0; } -static int process_root_password(void) { +static int process_root_args(void) { _cleanup_close_ int lock = -1; struct crypt_data cd = {}; const char *password, *hashed_password; @@ -757,6 +796,23 @@ static int process_root_password(void) { if (lock < 0) return log_error_errno(lock, "Failed to take a lock on %s: %m", etc_passwd); + if (arg_copy_root_shell && arg_root) { + struct passwd *p; + + errno = 0; + p = getpwnam("root"); + if (!p) + return log_error_errno(errno_or_else(EIO), "Failed to find passwd entry for root: %m"); + + r = free_and_strdup(&arg_root_shell, p->pw_shell); + if (r < 0) + return log_oom(); + } + + r = prompt_root_shell(); + if (r < 0) + return r; + if (arg_copy_root_password && arg_root) { struct spwd *p; @@ -799,7 +855,7 @@ static int process_root_password(void) { else password = hashed_password = "!"; - r = write_root_passwd(etc_passwd, password); + r = write_root_passwd(etc_passwd, password, arg_root_shell); if (r < 0) return log_error_errno(r, "Failed to write %s: %m", etc_passwd); @@ -964,6 +1020,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_ROOT_PASSWORD, ARG_ROOT_PASSWORD_FILE, ARG_ROOT_PASSWORD_HASHED, + ARG_ROOT_SHELL, ARG_KERNEL_COMMAND_LINE, ARG_PROMPT, ARG_PROMPT_LOCALE, @@ -971,11 +1028,13 @@ static int parse_argv(int argc, char *argv[]) { ARG_PROMPT_TIMEZONE, ARG_PROMPT_HOSTNAME, ARG_PROMPT_ROOT_PASSWORD, + ARG_PROMPT_ROOT_SHELL, ARG_COPY, ARG_COPY_LOCALE, ARG_COPY_KEYMAP, ARG_COPY_TIMEZONE, ARG_COPY_ROOT_PASSWORD, + ARG_COPY_ROOT_SHELL, ARG_SETUP_MACHINE_ID, ARG_FORCE, ARG_DELETE_ROOT_PASSWORD, @@ -996,6 +1055,7 @@ static int parse_argv(int argc, char *argv[]) { { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD }, { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE }, { "root-password-hashed", required_argument, NULL, ARG_ROOT_PASSWORD_HASHED }, + { "root-shell", required_argument, NULL, ARG_ROOT_SHELL }, { "kernel-command-line", required_argument, NULL, ARG_KERNEL_COMMAND_LINE }, { "prompt", no_argument, NULL, ARG_PROMPT }, { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE }, @@ -1003,11 +1063,13 @@ static int parse_argv(int argc, char *argv[]) { { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE }, { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME }, { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD }, + { "prompt-root-shell", no_argument, NULL, ARG_PROMPT_ROOT_SHELL }, { "copy", no_argument, NULL, ARG_COPY }, { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE }, { "copy-keymap", no_argument, NULL, ARG_COPY_KEYMAP }, { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE }, { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD }, + { "copy-root-shell", no_argument, NULL, ARG_COPY_ROOT_SHELL }, { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID }, { "force", no_argument, NULL, ARG_FORCE }, { "delete-root-password", no_argument, NULL, ARG_DELETE_ROOT_PASSWORD }, @@ -1104,6 +1166,17 @@ static int parse_argv(int argc, char *argv[]) { arg_root_password_is_hashed = true; break; + case ARG_ROOT_SHELL: + if (!valid_shell(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s is not a valid shell path", optarg); + + r = free_and_strdup(&arg_root_shell, optarg); + if (r < 0) + return log_oom(); + + break; + case ARG_HOSTNAME: if (!hostname_is_valid(optarg, true)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -1131,7 +1204,8 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_PROMPT: - arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true; + arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = + arg_prompt_root_password = arg_prompt_root_shell = true; break; case ARG_PROMPT_LOCALE: @@ -1154,8 +1228,13 @@ static int parse_argv(int argc, char *argv[]) { arg_prompt_root_password = true; break; + case ARG_PROMPT_ROOT_SHELL: + arg_prompt_root_shell = true; + break; + case ARG_COPY: - arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = true; + arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = + arg_copy_root_shell = true; break; case ARG_COPY_LOCALE: @@ -1174,6 +1253,10 @@ static int parse_argv(int argc, char *argv[]) { arg_copy_root_password = true; break; + case ARG_COPY_ROOT_SHELL: + arg_copy_root_shell = true; + break; + case ARG_SETUP_MACHINE_ID: r = sd_id128_randomize(&arg_machine_id); if (r < 0) @@ -1274,7 +1357,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - r = process_root_password(); + r = process_root_args(); if (r < 0) return r;