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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
|
#!/bin/sh
PREREQ=""
prereqs()
{
echo "$PREREQ"
}
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
. /usr/share/initramfs-tools/hook-functions
. /lib/cryptsetup/functions
TABFILE="/etc/crypttab"
# crypttab_find_and_print_entry($target)
# Find the crypttab(5) entry for the given (unmangled) $target and
# print it - preserving the mangling - to FD nr. 3; but only if the
# target has not already been processed during an earlier function
# call. (Processed target names are stored in
# $DESTDIR/cryptroot/targets.)
# Return 0 on success, 1 on error.
crypttab_find_and_print_entry() {
local target="$1"
local _CRYPTTAB_NAME _CRYPTTAB_SOURCE _CRYPTTAB_KEY _CRYPTTAB_OPTIONS
if ! grep -Fxqz -e "$target" -- "$DESTDIR/cryptroot/targets"; then
printf '%s\0' "$target" >>"$DESTDIR/cryptroot/targets"
crypttab_find_entry "$target" || return 1
crypttab_parse_options --missing-path=warn || return 1
crypttab_print_entry
fi
}
# crypttab_print_entry()
# Print an unmangled crypttab(5) entry to FD nr. 3, using CRYPTTAB_*
# and _CRYPTTAB_* values.
# _CRYPTTAB_SOURCE is replaced with UUID=<uuid> if possible (eg, for
# LUKS), unless the value starts with /dev/disk/by- or /dev/mapper/,
# or is already a device specification (such as LABEL= or PARTUUID=).
# If the entry uses the 'decrypt_derived' keyscript, the other
# crypttab(5) entries it depends on are (recursively) printed before
# hand.
# Various checks are performed on the key and crypttab options, but no
# parsing is done so it's the responsibility of the caller to call
# crypttab_parse_options().
# Return 0 on success, 1 on error.
crypttab_print_entry() {
local DEV MAJ MIN uuid keyfile
if _resolve_device "$CRYPTTAB_SOURCE"; then
if [ "$(dmsetup info -c --noheadings -o devnos_used -- "$CRYPTTAB_NAME" 2>/dev/null)" != "$MAJ:$MIN" ]; then
cryptsetup_message "ERROR: $CRYPTTAB_NAME: Source mismatch"
elif [ "${_CRYPTTAB_SOURCE#[A-Za-z]*=}" = "$_CRYPTTAB_SOURCE" ] && \
[ "${CRYPTTAB_SOURCE#/dev/disk/by-}" = "$CRYPTTAB_SOURCE" ] && \
[ "${CRYPTTAB_SOURCE#/dev/mapper/}" = "$CRYPTTAB_SOURCE" ] && \
uuid="$(_device_uuid "$DEV")"; then
_CRYPTTAB_SOURCE="UUID=$uuid"
fi
# on failure _resolve_device() prints a warning and we try our
# luck with the unchanged _CRYPTTAB_SOURCE value
fi
# if keyscript is set, the "key" is just an argument to the script
if [ -z "${CRYPTTAB_OPTION_keyscript+x}" ] && [ "$CRYPTTAB_KEY" != "none" ]; then
crypttab_key_check || return 1
case "$CRYPTTAB_KEY" in
$KEYFILE_PATTERN)
mkdir -pm0700 -- "$DESTDIR/cryptroot/keyfiles"
# $CRYPTTAB_NAME can't contain '/' (even after unmangling)
keyfile="/cryptroot/keyfiles/$CRYPTTAB_NAME.key"
if [ ! -f "$DESTDIR$keyfile" ] && ! copy_file keyfile "$CRYPTTAB_KEY" "$keyfile"; then
cryptsetup_message "WARNING: couldn't copy keyfile $CRYPTTAB_KEY"
fi
_CRYPTTAB_KEY="/cryptroot/keyfiles/$_CRYPTTAB_NAME.key" # preserve mangled name
;;
*)
if [ "$usage" = rootfs ]; then
cryptsetup_message "WARNING: Skipping root target $CRYPTTAB_NAME: uses a key file"
return 1
elif [ "$usage" = resume ]; then
cryptsetup_message "WARNING: Resume target $CRYPTTAB_NAME uses a key file"
fi
if [ -L "$CRYPTTAB_KEY" ] && keyfile="$(readlink -- "$CRYPTTAB_KEY")" &&
[ "${keyfile#/}" != "$keyfile" ]; then
cryptsetup_message "WARNING: Skipping target $CRYPTTAB_NAME: key file is a symlink with absolute target"
return 1
elif [ -f "$CRYPTTAB_KEY" ] && [ "$(stat -L -c"%m" -- "$CRYPTTAB_KEY" 2>/dev/null)" != "/" ]; then
cryptsetup_message "WARNING: Skipping target $CRYPTTAB_NAME: key file is not on the root FS"
return 1
fi
if [ ! -e "$CRYPTTAB_KEY" ]; then
cryptsetup_message "WARNING: Target $CRYPTTAB_NAME has a non-existing key file $CRYPTTAB_KEY"
else
_CRYPTTAB_KEY="/FIXME-initramfs-rootmnt$_CRYPTTAB_KEY" # preserve mangled name
fi
esac
fi
if [ -n "${CRYPTTAB_OPTION_keyscript+x}" ]; then
copy_exec "$CRYPTTAB_OPTION_keyscript"
elif [ "$CRYPTTAB_KEY" = "none" ]; then
ASKPASS="y"
fi
if [ "${CRYPTTAB_OPTION_keyscript-}" = "/lib/cryptsetup/scripts/decrypt_derived" ]; then
# (recursively) list first the device to derive the key from (so
# the boot scripts unlock it first); since _CRYPTTAB_* are local
# to crypttab_find_and_print_entry() the new value won't
# override the new ones
crypttab_find_and_print_entry "$CRYPTTAB_KEY"
fi
printf '%s %s %s %s\n' \
"$_CRYPTTAB_NAME" "$_CRYPTTAB_SOURCE" "$_CRYPTTAB_KEY" "$_CRYPTTAB_OPTIONS" >&3
}
# get_resume_devno()
# Return the device ID(s) used for system suspend/hibernate.
get_resume_devno() {
local dev filename
# uswsusp
for filename in /etc/uswsusp.conf /etc/suspend.conf; do
[ -e "$filename" ] || continue
dev="$(sed -nr '/^resume device\s*[:=]\s*/ {s///p;q}' "$filename")"
if [ -n "$dev" ] && [ "$dev" != "<path_to_resume_device_file>" ]; then
# trim quotes
dev="$(printf '%s' "$dev" | sed -re 's/^"(.*)"\s*$/\1/' -e "s/^'(.*)'\\s*$/\\1/")"
_print_devno "$(printf '%b' "$dev")" # unmangle
fi
done
# regular swsusp
dev="$(sed -nr 's,^(.*\s)?resume=(\S+)(\s.*)?$,\2,p' /proc/cmdline)"
_print_devno "$(printf '%b' "$dev")" # unmangle
# initramfs-tools >=0.129
dev="${RESUME:-auto}"
if [ "$dev" != none ]; then
if [ "$dev" = auto ]; then
# next line from /usr/share/initramfs-tools/hooks/resume
dev="$(grep ^/dev/ /proc/swaps | sort -rnk3 | head -n 1 | cut -d " " -f 1)"
fi
_print_devno "$(printf '%b' "$dev")" # unmangle
fi
}
_print_devno() {
local DEV MAJ MIN # locally scope the 3 variables _resolve_device() sets
if [ -n "$1" ] && _resolve_device "$1"; then
printf '%d:%d\n' "$MAJ" "$MIN"
fi
}
# crypttab_print_initramfs_entry()
# Print a crypttab(5) entry - unless it was already processed - if it
# has the 'initramfs' option set.
crypttab_print_initramfs_entry() {
local usage=
if ! grep -Fxqz -e "$CRYPTTAB_NAME" -- "$DESTDIR/cryptroot/targets" &&
crypttab_parse_options --quiet &&
[ "${CRYPTTAB_OPTION_initramfs-no}" = "yes" ]; then
printf '%s\0' "$CRYPTTAB_NAME" >>"$DESTDIR/cryptroot/targets"
crypttab_print_entry
fi
}
# generate_initrd_crypttab()
# Generate the crypttab(5) snippet that is relevant at initramfs
# stage. (Devices that aren't required at initramfs stage are
# ignored.)
generate_initrd_crypttab() {
local devnos usage IFS="$(printf '\t\n ')"
mkdir -- "$DESTDIR/cryptroot"
true >"$DESTDIR/cryptroot/targets"
{
if devnos="$(get_mnt_devno /)"; then
usage=rootfs foreach_cryptdev crypttab_find_and_print_entry $devnos
else
cryptsetup_message "WARNING: Couldn't determine root device"
fi
if devnos="$(get_resume_devno)"; then
usage=resume foreach_cryptdev crypttab_find_and_print_entry $devnos
fi
if devnos="$(get_mnt_devno /usr)"; then
usage="" foreach_cryptdev crypttab_find_and_print_entry $devnos
fi
# add crypttab entries with the 'initramfs' option set
crypttab_foreach_entry crypttab_print_initramfs_entry
} 3>"$DESTDIR/cryptroot/crypttab"
rm -f "$DESTDIR/cryptroot/targets"
}
# populate_CRYPTO_HASHES()
# Find out which crypto hashes are required for a crypttab(5) entry,
# and append them to the CRYPTO_HASHES variable.
populate_CRYPTO_HASHES() {
local hash source newline="
"
if crypttab_parse_options --quiet && [ -n "${CRYPTTAB_OPTION_header+x}" ]; then
source="$CRYPTTAB_OPTION_header"
else
source="$(_resolve_device_spec "$CRYPTTAB_SOURCE")" || source=""
fi
if [ ! -e "$source" ]; then
# missing source device or detached header, can't determine hashing function(s)
hash="@@UNKNOWN@@"
elif [ "$CRYPTTAB_TYPE" = "luks" ]; then
# using --dump-json-metadata would be more robust for LUKS2 but
# we also have to support LUKS1 hence have to parse luksDump output
hash="$(/sbin/cryptsetup luksDump -- "$source" | sed -nr 's/^\s*(AF hash|Hash|Hash spec)\s*:\s*//Ip')"
elif [ "$CRYPTTAB_TYPE" = "plain" ]; then
# --hash is being ignored when opening via key file
if [ "$CRYPTTAB_KEY" = "none" ] && [ -z "${CRYPTTAB_OPTION_keyscript+x}" ]; then
hash="${CRYPTTAB_OPTION_hash-sha256}" # default password hashing as of cryptsetup 2.7
fi
else
hash="" # or hash="@@UNKNOWN@@"?
fi
if [ -n "$hash" ]; then
CRYPTO_HASHES="${CRYPTO_HASHES:+$CRYPTO_HASHES$newline}$hash"
fi
}
# populate_CRYPTO_MODULES()
# Find out which crypto modules are required for a crypttab(5) entry,
# and append them to the CRYPTO_MODULES variable.
populate_CRYPTO_MODULES() {
local cipher iv
# cf. dmsetup(8) and https://gitlab.com/cryptsetup/cryptsetup/wikis/DMCrypt
cipher="$(dmsetup table --target crypt -- "$CRYPTTAB_NAME" | cut -d' ' -f4)"
if [ -z "$cipher" ]; then
cryptsetup_message "WARNING: Couldn't determine cipher modules to load for $CRYPTTAB_NAME"
elif [ "${cipher#capi:}" = "$cipher" ]; then
# direct specification "cipher[:keycount]-chainmode-ivmode[:ivopts]"
CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }${cipher%%[-:]*}" # cipher
cipher="${cipher#"${cipher%%-*}-"}" # chainmode-ivmode[:ivopts]"
CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }${cipher%-*}" # chainmode
iv="${cipher##*-}" # ivmode[:ivopts]"
CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }${iv%%:*}" # ivmode
if [ "${iv#*:}" != "$iv" ]; then
CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }${iv#*:}" # ivopts
fi
else
# kernel crypto API format "capi:cipher_api_spec-ivmode[:ivopts]", since linux 4.12
cipher="${cipher#capi:}"
cryptsetup_message "WARNING: Couldn't determine cipher modules to load for $CRYPTTAB_NAME" \
"(kernel crypto API format isn't supported yet)"
fi
}
# add_modules($glob, $moduledir, [$moduledir ..])
# Add modules matching under the given $moduledir(s), the name of
# which matching $glob.
# Return 0 if any module was found found, 1 if not.
add_modules() {
local glob="$1" found=n
shift
for mod in $(find -H "$@" -name "$glob.ko*" -type f -printf '%f\n'); do
manual_add_modules "${mod%%.*}"
found=y
done
[ "$found" = y ] && return 0 || return 1
}
# add_crypto_modules($name, [$name ..])
# Determine kernel module name and add to initramfs.
add_crypto_modules() {
local mod
for mod in "$@"; do
# We have several potential sources of modules (in order of preference):
#
# a) /lib/modules/$VERSION/kernel/arch/$ARCH/crypto/$mod-$specific.ko
# b) /lib/modules/$VERSION/kernel/crypto/$mod_generic.ko
# c) /lib/modules/$VERSION/kernel/crypto/$mod.ko
#
# and (currently ignored):
#
# d) /lib/modules/$VERSION/kernel/drivers/crypto/$specific-$mod.ko
add_modules "$mod-*" "$MODULESDIR"/kernel/arch/*/crypto || true
add_modules "${mod}_generic" "$MODULESDIR/kernel/crypto" \
|| add_modules "$mod" "$MODULESDIR/kernel/crypto" \
|| true
done
}
# copy_libssl_legacy_library()
# Copy ossl-modules/legacy.so (from libssl library) to initramfs if needed.
# OpenSSL 3.0 moved support for some crypto hashes into legacy.so.
# See https://launchpad.net/bugs/1979159
copy_libssl_legacy_library() {
local libcryptodir CRYPTO_HASHES=""
libcryptodir="$(env --unset=LD_PRELOAD ldd /sbin/cryptsetup | sed -nr '/.*=>\s*(\S+)\/libcrypto\.so\..*/ {s//\1/p;q}')"
[ -d "$libcryptodir" ] || return
crypttab_foreach_entry populate_CRYPTO_HASHES
# See https://www.openssl.org/docs/man3.0/man7/OSSL_PROVIDER-legacy.html#Hashing-Algorithms-Message-Digests
if printf '%s\n' "$CRYPTO_HASHES" | grep -Fxq -e @@UNKNOWN@@ -e ripemd160 -e whirlpool; then
# legacy hashes are used so legacy.so needs to be copied to the initramfs
# (assume ossl-modules/legacy.so is relative to the linked libcrypto.so)
copy_exec "$libcryptodir/ossl-modules/legacy.so" || true
fi
}
# See #1032221: newer libargon2 are built with glibc ≥2.34 hence no
# longer links libpthread. This in turns means that initramfs-tool's
# copy_exec() is no longer able to detect pthread_*() need and thus
# doesn't copy libgcc_s.so anymore. So we need to do it manually
# instead.
copy_libgcc_argon2() {
local libdir rv=0
libdir="$(env --unset=LD_PRELOAD ldd /sbin/cryptsetup | sed -nr '/.*=>\s*(\S+)\/libargon2\.so\..*/ {s//\1/p;q}')"
copy_libgcc "$libdir" || rv=$?
if [ $rv -ne 0 ]; then
# merged-/usr mismatch, see #1032518
if [ "${libdir#/usr/}" != "$libdir" ]; then
libdir="${libdir#/usr}"
else
libdir="/usr/${libdir#/}"
fi
copy_libgcc "$libdir" && rv=0 || rv=$?
fi
return $rv
}
#######################################################################
# Begin real processing
unset -v ASKPASS KEYFILE_PATTERN
ASKPASS="y"
KEYFILE_PATTERN=
# Load the hook config
if [ -f "/etc/cryptsetup-initramfs/conf-hook" ]; then
. /etc/cryptsetup-initramfs/conf-hook
fi
if [ -n "$KEYFILE_PATTERN" ]; then
case "${UMASK:-$(umask)}" in
0[0-7]77) ;;
*) cryptsetup_message "WARNING: Permissive UMASK (${UMASK:-$(umask)})." \
"Private key material within the initrd might be left unprotected."
;;
esac
fi
CRYPTO_MODULES=
if [ -r "$TABFILE" ]; then
generate_initrd_crypttab
TABFILE="$DESTDIR/cryptroot/crypttab"
crypttab_foreach_entry populate_CRYPTO_MODULES
copy_libssl_legacy_library
fi
# add required components
manual_add_modules dm_mod
manual_add_modules dm_crypt
copy_exec /sbin/cryptsetup
copy_exec /sbin/dmsetup
copy_libgcc_argon2
[ "$ASKPASS" = n ] || copy_exec /lib/cryptsetup/askpass
# We need sed. Either via busybox or as standalone binary.
if [ "$BUSYBOX" = n ] || [ -z "$BUSYBOXDIR" ]; then
copy_exec /bin/sed
fi
# detect whether the host CPU has AES-NI support
if grep -Eq '^flags\s*:(.*\s)?aes(\s.*)?$' /proc/cpuinfo; then
CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }aesni"
else
# workaround for #883595/#901884 (xts depends on ecb)
CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }ecb"
fi
# add userspace crypto module (only required for opening LUKS2 devices
# we add the module unconditionally as it's the default format)
CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }algif_skcipher"
if [ "$MODULES" = most ]; then
for d in "$MODULESDIR"/kernel/arch/*/crypto; do
copy_modules_dir "${d#"$MODULESDIR/"}"
done
copy_modules_dir "kernel/crypto"
else
if [ "$MODULES" != "dep" ]; then
# with large initramfs, we always add a basic subset of modules
add_crypto_modules aes cbc chainiv cryptomgr krng sha256 xts
fi
add_crypto_modules $(printf '%s' "${CRYPTO_MODULES-}" | tr ' ' '\n' | sort -u)
fi
copy_file library /lib/cryptsetup/functions /lib/cryptsetup/functions
|