]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
homed: optionally, drop caches on logout
authorLennart Poettering <lennart@poettering.net>
Tue, 5 Oct 2021 08:32:25 +0000 (10:32 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 11 Oct 2021 14:00:34 +0000 (16:00 +0200)
Fixes: #20857
man/homectl.xml
src/home/homectl.c
src/home/homework.c
src/home/homework.h
src/shared/user-record-show.c
src/shared/user-record.c
src/shared/user-record.h

index 245ebcee003f5af76db050e2b58d6ec6911402a9..c2b1ec6c9b2855e6cfd3734c80ee7205b7d08daf 100644 (file)
         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>
 
index d5b699a2424e676b189538722c2235340ff08b35..3af1381eb427ffd9aa105112c50d24f67ac8f2d3 100644 (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;
 
index d35712c3514d8d4a99820ca1ffad5d9939738976..49533b242293af19fc1eb0bc76ff8f1a438f8e94 100644 (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.");
 
index fb53fd49b0376d9fc77718caea8c59739d7a44f2..f20a23a918788ee9139059aaff954b1423ec2ca6 100644 (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;
index 29aa5c0c7c4abbc68ec04fe673b31915c461ba2a..dee6c4e5ee3d6df96bd81544ede9337e08111aa7 100644 (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));
 
index f4e509e13e1021d469b3503f012681ab838c2385..44f629a910b19f4b0597f29c2cf78e847552ad11 100644 (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);
 
index fa58dfdb6e6e33662e36403d3c47f76cdebaa9d5..975e3e175bcb75a42162c25bf515fa751ac898e1 100644 (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);