summaryrefslogtreecommitdiffstats
path: root/debian/initramfs/scripts/local-top/cryptroot
blob: 90b521b5e2a7dd4bdd05e7c5e4ec01f44867bda5 (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
#!/bin/sh

PREREQ="cryptroot-prepare"

#
# Standard initramfs preamble
#
prereqs()
{
	# Make sure that cryptroot is run last in local-top
	local req
	for req in "${0%/*}"/*; do
		script="${req##*/}"
		if [ "$script" != "${0##*/}" ]; then
			printf '%s\n' "$script"
		fi
	done
}

case $1 in
prereqs)
	prereqs
	exit 0
	;;
esac

. /scripts/functions

[ -f /lib/cryptsetup/functions ] || return 0
. /lib/cryptsetup/functions


# wait_for_source()
#   Wait for encrypted $CRYPTTAB_SOURCE . Set $CRYPTTAB_SOURCE
#   to its normalized device name when it shows up;
#   return 1 if timeout.
wait_for_source() {
    wait_for_udev 10

    if crypttab_resolve_source; then
        # the device is here already, no need to loop
        return 0
    fi

    # If the source device hasn't shown up yet, give it a little while
    # to allow for asynchronous device discovery (e.g. USB).
    #
    # We also need to take into account RAID or other devices that may
    # only be available on local-block stage. So, wait 5 seconds upfront,
    # in local-top; if that fails, end execution relying on local-block
    # invocations. Allow $ROOTDELAY/4 invocations with 1s sleep times (with
    # a minimum of 20 invocations), and if after that we still fail, then it's
    # really time to give-up. Variable $initrd_cnt tracks the re-invocations.
    #
    # Part of the lines below has been taken from initramfs-tools
    # scripts/local's local_device_setup(), as suggested per
    # https://launchpad.net/bugs/164044 .

    local slumber=5
    if [ "${CRYPTROOT_STAGE-}" = "local-block" ]; then
         slumber=1
    fi

    cryptsetup_message "Waiting for encrypted source device $CRYPTTAB_SOURCE..."

    while [ $slumber -gt 0 ]; do
        sleep 1

        if crypttab_resolve_source; then
            wait_for_udev 10
            return 0
        fi

        slumber=$(( $slumber - 1 ))
    done
    return 1
}

# setup_mapping()
#   Set up a crypttab(5) mapping defined by $CRYPTTAB_NAME,
#   $CRYPTTAB_SOURCE, $CRYPTTAB_KEY, $CRYPTTAB_OPTIONS.
setup_mapping() {
    local dev initrd_cnt

    # We control here the number of re-invocations of this script from
    # local-block - the heuristic is $ROOTDELAY/4, with a minimum of 20.

    if [ -f "$CRYPTROOT_COUNT_FILE" ]; then
        initrd_cnt="$(cat <"$CRYPTROOT_COUNT_FILE")"
    else
        initrd_cnt="${ROOTDELAY:-180}"
        initrd_cnt=$(( initrd_cnt/4 ))
        if [ $initrd_cnt -lt 20 ]; then
            initrd_cnt=20
        fi
        echo "$initrd_cnt" >"$CRYPTROOT_COUNT_FILE"
    fi

    # The same target can be specified multiple times
    # e.g. root and resume lvs-on-lvm-on-crypto
    if dm_blkdevname "$CRYPTTAB_NAME" >/dev/null; then
        return 0
    fi

    crypttab_parse_options --export --missing-path=fail || return 1

    if ! wait_for_source; then
        if [ $initrd_cnt -eq 0 ]; then
            # we've given up
            if [ -n "$panic" ]; then
                panic "ALERT! encrypted source device $CRYPTTAB_SOURCE does not exist, can't unlock $CRYPTTAB_NAME."
            else
                # let the user fix matters if they can
                echo "	ALERT! encrypted source device $CRYPTTAB_SOURCE does not exist, can't unlock $CRYPTTAB_NAME."
                echo "	Check cryptopts=source= bootarg: cat /proc/cmdline"
                echo "	or missing modules, devices: cat /proc/modules; ls /dev"
                panic "Dropping to a shell."
            fi
            return 1 # can't continue because environment is lost
        else
            initrd_cnt=$(( initrd_cnt - 1 ))
            echo "$initrd_cnt" >"$CRYPTROOT_COUNT_FILE"
            return 0 # allow some attempts on local-block stage
        fi
    fi

    # our `cryptroot-unlock` script searches for cryptsetup processes
    # with a given CRYPTTAB_NAME it their environment
    export CRYPTTAB_NAME

    if [ -z "${CRYPTTAB_OPTION_keyscript+x}" ]; then
        # no keyscript: interactive unlocking, or key file

        if [ "${CRYPTTAB_KEY#/FIXME-initramfs-rootmnt/}" != "$CRYPTTAB_KEY" ]; then
            # skip the mapping for now if the root FS is not mounted yet
            sed -rn 's/^\s*[^#[:blank:]]\S*\s+(\S+)\s.*/\1/p' /proc/mounts | grep -Fxq -- "$rootmnt" || return 1
            # substitute the "/FIXME-initramfs-rootmnt/" prefix by the real root FS mountpoint otherwise
            CRYPTTAB_KEY="$rootmnt/${CRYPTTAB_KEY#/FIXME-initramfs-rootmnt/}"
        fi

        if [ "$CRYPTTAB_KEY" != "none" ]; then
            if [ ! -e "$CRYPTTAB_KEY" ]; then
                cryptsetup_message "ERROR: Skipping target $CRYPTTAB_NAME: non-existing key file $CRYPTTAB_KEY"
                return 1
            fi
            # try only once if we have a key file
            CRYPTTAB_OPTION_tries=1
        fi
    fi

    local count=0 maxtries="${CRYPTTAB_OPTION_tries:-3}" fstype vg rv
    while [ $maxtries -le 0 ] || [ $count -lt $maxtries ]; do
        if [ -z "${CRYPTTAB_OPTION_keyscript+x}" ] && [ "$CRYPTTAB_KEY" != "none" ]; then
            # unlock via keyfile
            unlock_mapping "$CRYPTTAB_KEY"
        else
            # unlock interactively or via keyscript
            run_keyscript "$count" | unlock_mapping
        fi
        rv=$?
        count=$(( $count + 1 ))

        if [ $rv -ne 0 ]; then
            cryptsetup_message "ERROR: $CRYPTTAB_NAME: cryptsetup failed, bad password or options?"
            sleep 1
            continue
        elif ! dev="$(dm_blkdevname "$CRYPTTAB_NAME")"; then
            cryptsetup_message "ERROR: $CRYPTTAB_NAME: unknown error setting up device mapping"
            return 1
        fi

        if ! fstype="$(get_fstype "$dev")" || [ "$fstype" = "unknown" ]; then
            if [ "$CRYPTTAB_TYPE" != "luks" ]; then
                # bad password for plain dm-crypt device?  or mkfs not run yet?
                cryptsetup_message "ERROR: $CRYPTTAB_NAME: unknown fstype, bad password or options?"
                wait_for_udev 10
                /sbin/cryptsetup remove -- "$CRYPTTAB_NAME"
                sleep 1
                continue
            fi
        fi

        cryptsetup_message "$CRYPTTAB_NAME: set up successfully"
        wait_for_udev 10
        return 0
    done

    cryptsetup_message "ERROR: $CRYPTTAB_NAME: maximum number of tries exceeded"
    exit 1
}


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

mkdir -p /cryptroot # might not exist yet if the main system has no crypttab(5)

# Do we have any kernel boot arguments?
if ! grep -qE '^(.*\s)?cryptopts=' /proc/cmdline; then
    # ensure $TABFILE exists and has a mtime greater than the boot time
    # (existing $TABFILE is preserved)
    touch -- "$TABFILE"
else
    # let the read builtin unescape the '\' as GRUB substitutes '\' by '\\' in the cmdline
    tr ' ' '\n' </proc/cmdline | sed -n 's/^cryptopts=//p' | while IFS= read cryptopts; do
        # skip empty values (which can be used to disable the initramfs
        # scripts for a particular boot, cf. #873840)
        [ -n "$cryptopts" ] || continue
        unset -v target source key options

        IFS=","
        for x in $cryptopts; do
            case "$x" in
                target=*) target="${x#target=}";;
                source=*) source="${x#source=}";;
                key=*) key="${x#key=}";;
                *) options="${options+$options,}$x";;
            esac
        done

        if [ -z "${source:+x}" ]; then
            cryptsetup_message "ERROR: Missing source= value in kernel parameter cryptopts=$cryptopts"
        else
            # preserve mangling
            printf '%s %s %s %s\n' "${target:-cryptroot}" "$source" "${key:-none}" "${options-}"
        fi
    done >"$TABFILE"
fi

# Do we have any settings from the $TABFILE?
if [ -s "$TABFILE" ]; then
    # Create locking directory before invoking cryptsetup(8) to avoid warnings
    mkdir -pm0700 /run/cryptsetup
    modprobe -q dm_crypt

    crypttab_foreach_entry setup_mapping
fi

exit 0