homed: optionally, drop caches on logout

Fixes: #20857
This commit is contained in:
Lennart Poettering 2021-10-05 10:32:25 +02:00
parent 2aaf565a2d
commit 86019efa44
7 changed files with 107 additions and 5 deletions

View file

@ -610,6 +610,17 @@
node is not allowed if any of the other storage backends are used.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--drop-caches=</option><replaceable>BOOL</replaceable></term>
<listitem><para>Automatically flush OS file system caches on logout. This is useful in combination
with the fscrypt storage backend to ensure the OS does not keep decrypted versions of the files and
directories in memory (and accessible) after logout. This option is also supported on other backends,
but should not bring any benefit there. Defaults to off, except if the selected storage backend is
fscrypt, where it defaults to on. Note that flushing OS caches will negatively influence performance
of the OS shortly after logout.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--fs-type=</option><replaceable>TYPE</replaceable></term>

View file

@ -2131,6 +2131,7 @@ static int help(int argc, char *argv[], void *userdata) {
" --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n"
" subvolume, cifs)\n"
" --image-path=PATH Path to image file/directory\n"
" --drop-caches=BOOL Whether to automatically drop caches on logout\n"
"\n%4$sLUKS Storage User Record Properties:%5$s\n"
" --fs-type=TYPE File system type to use in case of luks\n"
" storage (btrfs, ext4, xfs)\n"
@ -2245,6 +2246,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_RECOVERY_KEY,
ARG_AND_RESIZE,
ARG_AND_CHANGE_PASSWORD,
ARG_DROP_CACHES,
};
static const struct option options[] = {
@ -2327,6 +2329,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "recovery-key", required_argument, NULL, ARG_RECOVERY_KEY },
{ "and-resize", required_argument, NULL, ARG_AND_RESIZE },
{ "and-change-password", required_argument, NULL, ARG_AND_CHANGE_PASSWORD },
{ "drop-caches", required_argument, NULL, ARG_DROP_CACHES },
{}
};
@ -3450,6 +3453,26 @@ static int parse_argv(int argc, char *argv[]) {
arg_and_change_password = true;
break;
case ARG_DROP_CACHES: {
bool drop_caches;
if (isempty(optarg)) {
r = drop_from_identity("dropCaches");
if (r < 0)
return r;
}
r = parse_boolean_argument("--drop-caches=", optarg, &drop_caches);
if (r < 0)
return r;
r = json_variant_set_field_boolean(&arg_identity_extra, "dropCaches", r);
if (r < 0)
return log_error_errno(r, "Failed to set drop caches field: %m");
break;
}
case '?':
return -EINVAL;

View file

@ -28,6 +28,7 @@
#include "rm-rf.h"
#include "stat-util.h"
#include "strv.h"
#include "sync-util.h"
#include "tmpfile-util.h"
#include "user-util.h"
#include "virt.h"
@ -283,6 +284,20 @@ int user_record_authenticate(
return 0;
}
static void drop_caches_now(void) {
int r;
/* Drop file system caches now. See https://www.kernel.org/doc/Documentation/sysctl/vm.txt for
* details. We write "2" into /proc/sys/vm/drop_caches to ensure dentries/inodes are flushed, but not
* more. */
r = write_string_file("/proc/sys/vm/drop_caches", "2\n", WRITE_STRING_FILE_DISABLE_BUFFER);
if (r < 0)
log_warning_errno(r, "Failed to drop caches, ignoring: %m");
else
log_debug("Dropped caches.");
}
int home_setup_undo(HomeSetup *setup) {
int r = 0, q;
@ -295,6 +310,9 @@ int home_setup_undo(HomeSetup *setup) {
r = q;
}
if (syncfs(setup->root_fd) < 0)
log_debug_errno(errno, "Failed to synchronize home directory, ignoring: %m");
setup->root_fd = safe_close(setup->root_fd);
}
@ -345,6 +363,9 @@ int home_setup_undo(HomeSetup *setup) {
setup->volume_key = mfree(setup->volume_key);
setup->volume_key_size = 0;
if (setup->do_drop_caches)
drop_caches_now();
return r;
}
@ -367,6 +388,9 @@ int home_prepare(
/* Makes a home directory accessible (through the root_fd file descriptor, not by path!). */
if (!already_activated) /* If we set up the directory, we should also drop caches once we are done */
setup->do_drop_caches = setup->do_drop_caches || user_record_drop_caches(h);
switch (user_record_storage(h)) {
case USER_LUKS:
@ -827,6 +851,13 @@ static int home_deactivate(UserRecord *h, bool force) {
return r;
}
/* Sync explicitly, so that the drop caches logic below can work as documented */
r = syncfs_path(AT_FDCWD, user_record_home_directory(h));
if (r < 0)
log_debug_errno(r, "Failed to synchronize home directory, ignoring: %m");
else
log_info("Syncing completed.");
if (umount2(user_record_home_directory(h), UMOUNT_NOFOLLOW | (force ? MNT_FORCE|MNT_DETACH : 0)) < 0)
return log_error_errno(errno, "Failed to unmount %s: %m", user_record_home_directory(h));
@ -846,6 +877,9 @@ static int home_deactivate(UserRecord *h, bool force) {
if (!done)
return log_error_errno(SYNTHETIC_ERRNO(ENOEXEC), "Home is not active.");
if (user_record_drop_caches(h))
drop_caches_now();
log_info("Everything completed.");
return 0;
}
@ -1268,9 +1302,21 @@ static int home_remove(UserRecord *h) {
if (unlink(ip) < 0) {
if (errno != ENOENT)
return log_error_errno(errno, "Failed to remove %s: %m", ip);
} else
} else {
_cleanup_free_ char *parent = NULL;
deleted = true;
r = path_extract_directory(ip, &parent);
if (r < 0)
log_debug_errno(r, "Failed to determine parent directory of '%s': %m", ip);
else {
r = fsync_path_at(AT_FDCWD, parent);
if (r < 0)
log_debug_errno(r, "Failed to synchronize disk after deleting '%s', ignoring: %m", ip);
}
}
} else if (S_ISBLK(st.st_mode))
log_info("Not removing file system on block device %s.", ip);
else
@ -1285,7 +1331,7 @@ static int home_remove(UserRecord *h) {
case USER_FSCRYPT:
assert(ip);
r = rm_rf(ip, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
r = rm_rf(ip, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_SYNCFS);
if (r < 0) {
if (r != -ENOENT)
return log_warning_errno(r, "Failed to remove %s: %m", ip);
@ -1316,9 +1362,12 @@ static int home_remove(UserRecord *h) {
deleted = true;
}
if (deleted)
if (deleted) {
if (user_record_drop_caches(h))
drop_caches_now();
log_info("Everything completed.");
else
} else
return log_notice_errno(SYNTHETIC_ERRNO(EALREADY),
"Nothing to remove.");

View file

@ -32,6 +32,7 @@ typedef struct HomeSetup {
bool do_offline_fitrim;
bool do_offline_fallocate;
bool do_mark_clean;
bool do_drop_caches;
uint64_t partition_offset;
uint64_t partition_size;

View file

@ -435,6 +435,9 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
if (hr->password_change_now >= 0)
printf("Pas. Ch. Now: %s\n", yes_no(hr->password_change_now));
if (hr->drop_caches >= 0 || user_record_drop_caches(hr))
printf(" Drop Caches: %s\n", yes_no(user_record_drop_caches(hr)));
if (!strv_isempty(hr->ssh_authorized_keys))
printf("SSH Pub. Key: %zu\n", strv_length(hr->ssh_authorized_keys));

View file

@ -202,6 +202,7 @@ UserRecord* user_record_new(void) {
.pkcs11_protected_authentication_path_permitted = -1,
.fido2_user_presence_permitted = -1,
.fido2_user_verification_permitted = -1,
.drop_caches = -1,
};
return h;
@ -1284,6 +1285,7 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "dropCaches", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, drop_caches), 0 },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
@ -1620,7 +1622,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0 },
{ "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0 },
{ "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0 },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
@ -1629,6 +1631,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "dropCaches", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, drop_caches), 0 },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
@ -2021,6 +2024,16 @@ bool user_record_can_authenticate(UserRecord *h) {
return !strv_isempty(h->hashed_password);
}
bool user_record_drop_caches(UserRecord *h) {
assert(h);
if (h->drop_caches >= 0)
return h->drop_caches;
/* By default drop caches on fscrypt, not otherwise. */
return user_record_storage(h) == USER_FSCRYPT;
}
uint64_t user_record_ratelimit_next_try(UserRecord *h) {
assert(h);

View file

@ -353,6 +353,7 @@ typedef struct UserRecord {
int removable;
int enforce_password_policy;
int auto_login;
int drop_caches;
uint64_t stop_delay_usec; /* How long to leave systemd --user around on log-out */
int kill_processes; /* Whether to kill user processes forcibly on log-out */
@ -419,6 +420,7 @@ int user_record_removable(UserRecord *h);
usec_t user_record_ratelimit_interval_usec(UserRecord *h);
uint64_t user_record_ratelimit_burst(UserRecord *h);
bool user_record_can_authenticate(UserRecord *h);
bool user_record_drop_caches(UserRecord *h);
int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret);