summaryrefslogtreecommitdiffstats
path: root/debian/initramfs/hooks/cryptroot
blob: 90a32dd65260def4b4435e8418294f4d6b8bfa2e (plain)
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
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
#!/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"


# device_uuid($device)
#   Print the UUID attribute of given block special $device.  Return 0
#   on success, 1 on error.
device_uuid() {
    local device="$1" uuid
    if uuid="$(blkid -s UUID -o value -- "$device")" && [ -n "$uuid" ]; then
        printf '%s\n' "$uuid"
    else
        return 1
    fi
}

# resolve_device({$device | $spec})
#   Take a path to (or spec for) a block special device, and set DEV to
#   the (symlink to block) device, and MAJ (resp. MIN) to its major-ID
#   (resp. minor ID) decimal value.  On error these variables are not
#   changed and 1 is returned.
resolve_device() {
    local spec="$1" dev devno maj min
    if dev="$(resolve_device_spec "$spec")" &&
            devno="$(stat -L -c"%t:%T" -- "$dev" 2>/dev/null)" &&
            maj="${devno%:*}" && min="${devno#*:}" &&
            [ "$devno" = "$maj:$min" ] && [ -n "$maj" ] && [ -n "$min" ] &&
            maj=$(( 0x$maj )) && min=$(( 0x$min )) && [ $maj -gt 0 ]; then
        DEV="$dev"
        MAJ="$maj"
        MIN="$min"
        return 0
    else
        cryptsetup_message "ERROR: Couldn't resolve device $spec"
    fi
    return 1
}

# get_mnt_devno($mountpoint)
#   Print the major:minor device ID(s) holding the file system currently
#   mounted currenty mounted on $mountpoint.
#   Return 0 on success, 1 on error (if $mountpoint is not a mountpoint).
get_mnt_devno() {
    local wantmount="$1" devnos="" uuid dev IFS
    local spec mountpoint fstype _ DEV MAJ MIN

    while IFS=" 	" read -r spec mountpoint fstype _; do
        # treat lines starting with '#' as comments; /proc/mounts
        # doesn't seem to contain these but per procfs(5) the format of
        # that file is analogous to fstab(5)'s
        if [ "${spec#\#}" = "$spec" ] && [ -n "$spec" ] &&
                [ "$(printf '%b' "$mountpoint")" = "$wantmount" ]; then
            # take the last mountpoint if used several times (shadowed)
            unset -v devnos
            spec="$(printf '%b' "$spec")"
            resolve_device "$spec" || continue # resolve_device() already warns on error
            fstype="$(printf '%b' "$fstype")"
            if [ "$fstype" = "btrfs" ]; then
                # btrfs can span over multiple devices
                if uuid="$(device_uuid "$DEV")"; then
                    for dev in "/sys/fs/$fstype/$uuid/devices"/*/dev; do
                        devnos="${devnos:+$devnos }$(cat "$dev")"
                    done
                else
                    cryptsetup_message "ERROR: $spec: Couldn't determine UUID"
                fi
            elif [ -n "$fstype" ]; then
                devnos="$MAJ:$MIN"
            fi
        fi
    done </proc/mounts

    if [ -z "${devnos:+x}" ]; then
        return 1 # not found
    else
        printf '%s' "$devnos"
    fi
}

# get_dmcrypt_slaves({/sys/dev/block/$maj:$min | /sys/devices/...})
#   Recursively print the crypttab(5) entries to FD nr. 3, for each
#   dm-crypt device holding $maj:$min (typically corresponding to an md,
#   lv, or dm-crypt device).  Slaves that aren't dm-crypt devices are
#   NOT printed.
get_dmcrypt_slaves() {
    local d="$1" devno maj min name slave
    [ -d "$d/slaves" ] || return 0
    if [ -d "$d/dm" ] && devno="$(cat "$d/dev")" &&
            maj="${devno%:*}" && min="${devno#*:}" &&
            [ "$devno" = "$maj:$min" ] && [ -n "$maj" ] && [ -n "$min" ] &&
            [ "$(dmsetup info -c --noheadings -o subsystem -j "$maj" -m "$min")" = "CRYPT" ] &&
            name="$(dmsetup info -c --noheadings -o unmangled_name -j "$maj" -m "$min")"; then
        # search for a crypttab entry with the given unmangled name
        crypttab_find_and_print_entry "$name"
    fi
    for slave in "$d/slaves"/*; do
        if [ -d "$slave" ] && slave="$(realpath -e -- "$slave")"; then
            get_dmcrypt_slaves "$slave"
        fi
    done
}

# 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 /dev/mapper/$sourcename for mapped
#   sources, otherwise by UUID=<uuid> if possible (eg, for LUKS).  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 sourcename 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 sourcename="$(dmsetup info -c --noheadings -o mangled_name -j "$MAJ" -m "$MIN" 2>/dev/null)" &&
               [ -b "/dev/mapper/$sourcename" ]; then
            # for mapped sources, use the dm name as the lvm
            # local-block script doesn't work with UUIDs (#902943)
            _CRYPTTAB_SOURCE="/dev/mapper/$sourcename"
        elif uuid="$(device_uuid "$DEV")"; then
            # otherwise, use its UUID if possible (eg. for LUKS)
            _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"
    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_cryptdevs($maj:$min, [$maj:$min ..])
#   Print the list of crypttab(5) entries to FD nr. 3, for dm-crypt
#   devices holding any of the given devices (which can be an md, a lv,
#   a mapped device, or a regular block device).
get_cryptdevs() {
    local devno base
    for devno in "$@"; do
        base="/sys/dev/block/$devno"
        if [ ! -d "$base" ]; then
            cryptsetup_message "ERROR: Couldn't find sysfs directory for $devno"
            return 1
        fi
        get_dmcrypt_slaves "$base"
    done
}

# 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)"
    dev="$(printf '%b' "$dev")" # unmangle
    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 get_cryptdevs $devnos
        else
            cryptsetup_message "WARNING: Couldn't determine root device"
        fi

        if devnos="$(get_resume_devno)"; then
            usage=resume get_cryptdevs $devnos
        fi

        if devnos="$(get_mnt_devno /usr)"; then
            usage="" get_cryptdevs $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_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]"
        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%.ko}"
        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
}


#######################################################################
# Begin real processing

unset -v CRYPTSETUP KEYFILE_PATTERN
KEYFILE_PATTERN=

# Load the hook config
if [ -f "/etc/cryptsetup-initramfs/conf-hook" ]; then
    . /etc/cryptsetup-initramfs/conf-hook
fi

if [ "${CRYPTSETUP-}" = "n" ] || [ "${CRYPTSETUP-}" = "N" ]; then
    # XXX post-Buster: remove this warning and the auto-detection logic below
    cryptsetup_message "WARNING: Honoring CRYPTSETUP=\"n\" will deprecated in the future." \
        "Please uninstall the 'cryptsetup-initramfs' package instead" \
        "if you don't want the cryptsetup initramfs integration."
    exit 0
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
fi

# per comment in the config file a non-empty KEYFILE_PATTERN value
# implies CRYPTSETUP=y
if [ -z "${CRYPTSETUP-}" ] && [ -z "${KEYFILE_PATTERN-}" ] && [ -z "$CRYPTO_MODULES" ]; then
    cryptsetup_message "WARNING: The initramfs image may not contain cryptsetup binaries nor crypto modules." \
        "If that's on purpose, you may want to uninstall the 'cryptsetup-initramfs' package in" \
        "order to disable the cryptsetup initramfs integration and avoid this warning."
else
    # add required components
    manual_add_modules dm_mod
    manual_add_modules dm_crypt

    copy_exec /sbin/cryptsetup
    copy_exec /sbin/dmsetup
    copy_exec /lib/cryptsetup/askpass

    # libargon2 uses pthread_cancel
    LIBC_DIR="$(ldd /sbin/cryptsetup | sed -nr 's#.* => (/lib.*)/libc\.so\.[0-9.-]+ \(0x[[:xdigit:]]+\)$#\1#p')"
    find -L "$LIBC_DIR" -maxdepth 1 -name 'libgcc_s.*' -type f | while read so; do
        copy_exec "$so"
    done

    # We need sed. Either via busybox or as standalone binary.
    if [ "$BUSYBOX" = n ] || [ ! -e "$BUSYBOXDIR/busybox" ]; 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"
    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
fi