From 51a95db6dcb720608eccaac01328b66ef7cc0d30 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 2 Sep 2020 16:35:22 +0200 Subject: [PATCH 1/5] homed: don't block logins into accounts with future change time This might happen if the system clock is wrong, and we should allow access in this case (though certainly log about it). --- src/home/pam_systemd_home.c | 4 ++-- src/shared/user-record-show.c | 11 ++++++----- src/shared/user-record.c | 11 ++++++----- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index 13b164ecfc..db182dc999 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -846,8 +846,8 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( switch (r) { case -ESTALE: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record is newer than current system time, prohibiting access."); - return PAM_ACCT_EXPIRED; + pam_syslog(handle, LOG_WARNING, "User record for '%s' is newer than current system time, assuming incorrect system clock, allowing access.", ur->user_name); + break; case -ENOLCK: (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record is blocked, prohibiting access."); diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c index 33787c083f..7839f6c2b8 100644 --- a/src/shared/user-record-show.c +++ b/src/shared/user-record-show.c @@ -45,6 +45,10 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { if (hr->last_change_usec != USEC_INFINITY) { char buf[FORMAT_TIMESTAMP_MAX]; printf(" Last Change: %s\n", format_timestamp(buf, sizeof(buf), hr->last_change_usec)); + + if (hr->last_change_usec > now(CLOCK_REALTIME)) + printf(" %sModification time lies in the future, system clock wrong?%s\n", + ansi_highlight_yellow(), ansi_normal()); } if (hr->last_password_change_usec != USEC_INFINITY && @@ -56,10 +60,6 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { r = user_record_test_blocked(hr); switch (r) { - case -ESTALE: - printf(" Login OK: %sno%s (last change time is in the future)\n", ansi_highlight_red(), ansi_normal()); - break; - case -ENOLCK: printf(" Login OK: %sno%s (record is locked)\n", ansi_highlight_red(), ansi_normal()); break; @@ -72,10 +72,11 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { printf(" Login OK: %sno%s (record not valid anymore))\n", ansi_highlight_red(), ansi_normal()); break; + case -ESTALE: default: { usec_t y; - if (r < 0) { + if (r < 0 && r != -ESTALE) { errno = -r; printf(" Login OK: %sno%s (%m)\n", ansi_highlight_red(), ansi_normal()); break; diff --git a/src/shared/user-record.c b/src/shared/user-record.c index e04df4d78b..a80c4932d1 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -2025,19 +2025,20 @@ int user_record_test_blocked(UserRecord *h) { assert(h); - n = now(CLOCK_REALTIME); - if (h->last_change_usec != UINT64_MAX && - h->last_change_usec > n) /* Don't allow log ins when the record is from the future */ - return -ESTALE; - if (h->locked > 0) return -ENOLCK; + n = now(CLOCK_REALTIME); + if (h->not_before_usec != UINT64_MAX && n < h->not_before_usec) return -EL2HLT; if (h->not_after_usec != UINT64_MAX && n > h->not_after_usec) return -EL3HLT; + if (h->last_change_usec != UINT64_MAX && + h->last_change_usec > n) /* Complain during log-ins when the record is from the future */ + return -ESTALE; + return 0; } From 61a29a020c5c6611a22a84c1456e8da7aa656194 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 2 Sep 2020 16:36:27 +0200 Subject: [PATCH 2/5] homed: ignore ratelimiting counters when timestamp is from future This likely indicates that the system clock is simply wrong, hence allow access in this case. Fixes: #15917 --- src/home/user-record-util.c | 10 ++++++---- src/shared/user-record.c | 5 +++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c index 6928427730..3ed64128b2 100644 --- a/src/home/user-record-util.c +++ b/src/home/user-record-util.c @@ -1295,10 +1295,12 @@ int user_record_ratelimit(UserRecord *h) { usec = now(CLOCK_REALTIME); - if (h->ratelimit_begin_usec != UINT64_MAX && h->ratelimit_begin_usec > usec) - /* Hmm, time is running backwards? Say no! */ - return 0; - else if (h->ratelimit_begin_usec == UINT64_MAX || + if (h->ratelimit_begin_usec != UINT64_MAX && h->ratelimit_begin_usec > usec) { + /* Hmm, start-time is after the current time? If so, the RTC most likely doesn't work. */ + new_ratelimit_begin_usec = usec; + new_ratelimit_count = 1; + log_debug("Rate limit timestamp is in the future, assuming incorrect system clock, resetting limit."); + } else if (h->ratelimit_begin_usec == UINT64_MAX || usec_add(h->ratelimit_begin_usec, user_record_ratelimit_interval_usec(h)) <= usec) { /* Fresh start */ new_ratelimit_begin_usec = usec; diff --git a/src/shared/user-record.c b/src/shared/user-record.c index a80c4932d1..e14a8f44cb 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -1919,6 +1919,11 @@ uint64_t user_record_ratelimit_next_try(UserRecord *h) { h->ratelimit_count == UINT64_MAX) return UINT64_MAX; + if (h->ratelimit_begin_usec > now(CLOCK_REALTIME)) /* If the ratelimit time is in the future, then + * the local clock is probably incorrect. Let's + * not refuse login then. */ + return UINT64_MAX; + if (h->ratelimit_count < user_record_ratelimit_burst(h)) return 0; From 3e0b54867e22523cffda3b80e179df89b6d81bcd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 2 Sep 2020 16:37:42 +0200 Subject: [PATCH 3/5] user-record: don't refuse login when last pw change time is in the future The RTC is like just off, it's a weird system state, let's continue without requiring pw change. --- src/home/homed-home.c | 2 +- src/home/pam_systemd_home.c | 5 +++++ src/shared/user-record-show.c | 4 ++++ src/shared/user-record.c | 9 +++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/home/homed-home.c b/src/home/homed-home.c index e4757c724a..328ec32fd3 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -1574,7 +1574,7 @@ static int home_may_change_password( assert(h); r = user_record_test_password_change_required(h->record); - if (IN_SET(r, -EKEYREVOKED, -EOWNERDEAD, -EKEYEXPIRED)) + if (IN_SET(r, -EKEYREVOKED, -EOWNERDEAD, -EKEYEXPIRED, -ESTALE)) return 0; /* expired in some form, but changing is allowed */ if (IN_SET(r, -EKEYREJECTED, -EROFS)) return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Expiration settings of account %s do not allow changing of password.", h->user_name); diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index db182dc999..8fe52f44aa 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -904,6 +904,11 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Password will expire soon, please change."); break; + case -ESTALE: + /* If the system clock is wrong, let's log but continue */ + pam_syslog(handle, LOG_WARNING, "Couldn't check if password change is required, last change is in the future, system clock likely wrong."); + break; + case -EROFS: /* All good, just means the password if we wanted to change we couldn't, but we don't need to */ break; diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c index 7839f6c2b8..9046fafcb2 100644 --- a/src/shared/user-record-show.c +++ b/src/shared/user-record-show.c @@ -124,6 +124,10 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { printf(" Password OK: %schange not permitted%s\n", ansi_highlight_yellow(), ansi_normal()); break; + case -ESTALE: + printf(" Password OK: %slast password change in future%s\n", ansi_highlight_yellow(), ansi_normal()); + break; + default: if (r < 0) { errno = -r; diff --git a/src/shared/user-record.c b/src/shared/user-record.c index e14a8f44cb..4149205b8a 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -2061,6 +2061,7 @@ int user_record_test_password_change_required(UserRecord *h) { -EKEYEXPIRED: Password is about to expire, warn user -ENETDOWN: Record has expiration info but no password change timestamp -EROFS: No password change required nor permitted + -ESTALE: RTC likely incorrect, last password change is in the future 0: No password change required, but permitted */ @@ -2070,6 +2071,14 @@ int user_record_test_password_change_required(UserRecord *h) { n = now(CLOCK_REALTIME); + /* Password change in the future? Then our RTC is likely incorrect */ + if (h->last_password_change_usec != UINT64_MAX && + h->last_password_change_usec > n && + (h->password_change_min_usec != UINT64_MAX || + h->password_change_max_usec != UINT64_MAX || + h->password_change_inactive_usec != UINT64_MAX)) + return -ESTALE; + /* Then, let's check if password changing is currently allowed at all */ if (h->password_change_min_usec != UINT64_MAX) { From f2ba663ea5135419a0b69d3748975a74dbb88a87 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 21 Sep 2020 17:21:12 +0200 Subject: [PATCH 4/5] homed: show actual file system in messages about file systems --- src/home/homework-luks.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index b9e696f0ac..01374baf67 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -1885,7 +1885,7 @@ int home_create_luks( fstype = user_record_file_system_type(h); if (!supported_fstype(fstype)) - return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "Unsupported file system type: %s", h->file_system_type); + return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "Unsupported file system type: %s", fstype); if (sd_id128_is_null(h->partition_uuid)) { r = sd_id128_randomize(&partition_uuid); @@ -1964,7 +1964,8 @@ int home_create_luks( host_size = DISK_SIZE_ROUND_DOWN(h->disk_size); if (!supported_fs_size(fstype, host_size)) - return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Selected file system size too small for %s.", h->file_system_type); + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), + "Selected file system size too small for %s.", fstype); /* After creation we should reference this partition by its UUID instead of the block * device. That's preferable since the user might have specified a device node such as @@ -1997,7 +1998,7 @@ int home_create_luks( return r; if (!supported_fs_size(fstype, host_size)) - return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Selected file system size too small for %s.", h->file_system_type); + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Selected file system size too small for %s.", fstype); r = tempfn_random(ip, "homework", &temporary_image_path); if (r < 0) From a512e330ce3eb7150c28664e17603df2ef876a2a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 21 Sep 2020 17:30:03 +0200 Subject: [PATCH 5/5] homed: in images that lack mkfs.btrfs automatically fall back to ext4 It's better to tweak suboptimal defaults than to just fail with compiled-in defaults. --- src/home/homework-luks.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 01374baf67..b3082f144a 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -1887,6 +1887,23 @@ int home_create_luks( if (!supported_fstype(fstype)) return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "Unsupported file system type: %s", fstype); + r = mkfs_exists(fstype); + if (r < 0) + return log_error_errno(r, "Failed to check if mkfs binary for %s exists: %m", fstype); + if (r == 0) { + if (h->file_system_type || streq(fstype, "ext4") || !supported_fstype("ext4")) + return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkfs binary for file system type %s does not exist.", fstype); + + /* If the record does not explicitly declare a file system to use, and the compiled-in + * default does not actually exist, than do an automatic fallback onto ext4, as the baseline + * fs of Linux. We won't search for a working fs type here beyond ext4, i.e. nothing fancier + * than a single, conservative fallback to baseline. This should be useful in minimal + * environments where mkfs.btrfs or so are not made available, but mkfs.ext4 as Linux' most + * boring, most basic fs is. */ + log_info("Formatting tool for compiled-in default file system %s not available, falling back to ext4 instead.", fstype); + fstype = "ext4"; + } + if (sd_id128_is_null(h->partition_uuid)) { r = sd_id128_randomize(&partition_uuid); if (r < 0)