1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
|
/*
* Small program to LUKS suspend devices before system suspend
*
* Copyright: (c) 2018 Guilhem Moulin <guilhem@debian.org>
* (c) 2018-2020 Jonas Meurer <jonas@freesources.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <err.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <libcryptsetup.h>
#define SYSFS_POWER_SYNC_ON_SUSPEND "/sys/power/sync_on_suspend"
#define SYSFS_POWER_STATE "/sys/power/state"
void usage() {
printf("Usage: cryptsetup-suspend [-r|--reverse] <blkdev> [<blkdev> ...]\n"
" -r, --reverse process luks devices in reverse order\n\n");
exit(1);
}
/* Calculate free memory (MemAvailable + SwapFree) from /proc/meminfo */
uint32_t get_mem_swap_avail_kb() {
FILE *meminfo = fopen("/proc/meminfo", "r");
if (meminfo == NULL)
err(EXIT_FAILURE, "couldn't open /proc/meminfo");
int mem_avail_kb, swap_free_kb = 0;
char line[256];
while (fgets(line, sizeof(line), meminfo)) {
if (strncmp(line, "MemAvailable", strlen("MemAvailable")) == 0) {
if (sscanf(line, "MemAvailable: %d kB", &mem_avail_kb) != 1)
errx(EXIT_FAILURE, "couldn't read MemAvailable from /proc/meminfo");
} else if (strncmp(line, "SwapFree", strlen("SwapFree")) == 0) {
if (sscanf(line, "SwapFree: %d kB", &swap_free_kb) != 1)
errx(EXIT_FAILURE, "couldn't read SwapFree from /proc/meminfo");
}
}
fclose(meminfo);
uint32_t mem_swap_avail_kb = mem_avail_kb + swap_free_kb;
if (mem_swap_avail_kb == 0)
errx(EXIT_FAILURE, "error reading available memory and swap from /proc/meminfo");
return mem_swap_avail_kb;
}
int main(int argc, char *argv[]) {
int rv = 0;
bool reverse = 0;
int d_size;
bool sync_on_suspend_reset = 0;
FILE *sos = NULL;
/* Process commandline arguments */
if (argc < 2) {
usage();
} else if ((strcmp(argv[1], "-r") == 0) || (strcmp(argv[1], "--reverse") == 0)) {
if (argc < 3)
usage();
reverse = 1;
d_size = argc-2;
} else {
d_size = argc-1;
}
/* Read in devices */
const char *devices[d_size];
if (!reverse) {
for (int i = 0; i < d_size; i++) {
devices[i] = argv[i+1];
}
} else {
for (int i = 0; i < d_size; i++) {
devices[i] = argv[argc-i-1];
}
}
/* Disable sync_on_suspend in Linux kernel
*
* Only available in Linux kernel >= 5.6 */
if (access(SYSFS_POWER_SYNC_ON_SUSPEND, W_OK) < 0) {
if (errno == ENOENT)
warnx("kernel too old, can't disable sync on suspend");
} else {
sos = fopen(SYSFS_POWER_SYNC_ON_SUSPEND, "r+");
if (!sos)
err(EXIT_FAILURE, "couldn't open sysfs file");
int sos_c = fgetc(sos);
if (fgetc(sos) == EOF)
err(EXIT_FAILURE, "couldn't read from file");
if (sos_c == '0') {
/* Already disabled */
} else if (sos_c == '1') {
sync_on_suspend_reset = 1;
if (fputc('0', sos) <= 0)
err(EXIT_FAILURE, "couldn't write to file");
} else {
errx(EXIT_FAILURE, "unexpected value from %s", SYSFS_POWER_SYNC_ON_SUSPEND);
}
fclose(sos);
}
/* Change process priority to -20 (highest) to avoid races between
* the LUKS suspend(s) and the suspend-on-ram. */
if (setpriority(PRIO_PROCESS, 0, -20) == -1)
warn("can't lower process priority to -20");
/* Get memory settings of keyslots from processed LUKS2 devices */
uint32_t argon2i_max_memory_kb = 0;
for (int i = 0; i < d_size; i++) {
struct crypt_device *cd = NULL;
if (crypt_init_by_name(&cd, devices[i])) {
warnx("couldn't init LUKS device %s", devices[i]);
rv = EXIT_FAILURE;
} else {
/* Only LUKS2 devices may use argon2i PBKDF */
if (strcmp(crypt_get_type(cd), CRYPT_LUKS2) != 0)
continue;
int ks_max = crypt_keyslot_max(crypt_get_type(cd));
for (int j = 0; j < ks_max; j++) {
crypt_keyslot_info ki = crypt_keyslot_status(cd, j);
/* Only look at active keyslots */
if (ki != CRYPT_SLOT_ACTIVE && ki != CRYPT_SLOT_ACTIVE_LAST)
continue;
struct crypt_pbkdf_type pbkdf_ki;
if (crypt_keyslot_get_pbkdf(cd, j, &pbkdf_ki) < 0) {
warn("couldn't get PBKDF for keyslot %d of device %s", j, devices[i]);
rv = EXIT_FAILURE;
} else {
if (pbkdf_ki.max_memory_kb > argon2i_max_memory_kb)
argon2i_max_memory_kb = pbkdf_ki.max_memory_kb;
}
}
}
crypt_free(cd);
}
/* Add some more memory to be on the safe side
* TODO: find a reasonable value */
argon2i_max_memory_kb += 2 * 1024; // 2MB
/* Check if we have enough memory available to prevent mlock() from
* triggering the OOM killer. */
uint32_t mem_swap_avail_kb = get_mem_swap_avail_kb();
if (argon2i_max_memory_kb > mem_swap_avail_kb) {
errx(EXIT_FAILURE, "Error: Available memory (%d kb) less than required (%d kb)",
mem_swap_avail_kb, argon2i_max_memory_kb);
}
/* Allocate and lock memory for later usage by LUKS resume in order to
* prevent swapping out after LUKS devices (which might include swap
* storage) have been suspended. */
fprintf(stderr, "Allocating and mlocking memory: %d kb\n", argon2i_max_memory_kb);
char *mem;
if (!(mem = malloc(argon2i_max_memory_kb)))
err(EXIT_FAILURE, "couldn't allocate enough memory");
if (mlock(mem, argon2i_max_memory_kb) == -1)
err(EXIT_FAILURE, "couldn't lock enough memory");
/* Fill the allocated memory to make sure it's really reserved even if
* memory pages are copy-on-write. */
size_t i;
size_t page_size = getpagesize();
for (i = 0; i < argon2i_max_memory_kb; i += page_size)
mem[i] = 0;
/* Do the final filesystem sync since we disabled sync_on_suspend in
* Linux kernel. */
sync();
for (int i = 0; i < d_size; i++) {
struct crypt_device *cd = NULL;
if (crypt_init_by_name(&cd, devices[i]) || crypt_suspend(cd, devices[i])) {
warnx("couldn't suspend LUKS device %s", devices[i]);
rv = EXIT_FAILURE;
}
crypt_free(cd);
}
fprintf(stderr, "Sleeping...\n");
FILE *s = fopen(SYSFS_POWER_STATE, "w");
if (!s)
err(EXIT_FAILURE, "failed to open %s", SYSFS_POWER_STATE);
if (fputs("mem", s) <= 0)
err(EXIT_FAILURE, "couldn't write to %s", SYSFS_POWER_STATE);
fclose(s);
fprintf(stderr, "Resuming...\n");
/* Restore original sync_on_suspend value */
if (sync_on_suspend_reset) {
sos = fopen(SYSFS_POWER_SYNC_ON_SUSPEND, "w");
if (!sos)
err(EXIT_FAILURE, "couldn't open sysfs file");
if (fputc('1', sos) <= 0)
err(EXIT_FAILURE, "couldn't write to file");
fclose(sos);
}
return rv;
}
|