xref: /aosp_15_r20/external/vboot_reference/scripts/image_signing/ensure_secure_kernelparams.sh (revision 8617a60d3594060b7ecbd21bc622a7c14f3cf2bc)
1#!/bin/bash
2
3# Copyright 2011 The ChromiumOS Authors
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7# Abort on error.
8set -e
9
10# Load common constants and variables.
11. "$(dirname "$0")/common.sh"
12
13# Given a kernel boot param string which includes ...dm="dmstuff"...
14# this returns the dmstuff by itself.
15get_dmparams() {
16    echo "$1" | sed 's/^.*\ dm="\([^"]*\)".*/\1/'
17}
18
19# Given a kernel boot param string which includes ...dm="stuff"...
20# this returns the param string with the dm="..." section removed.
21# Useful in conjunction with get_dmparams to divide and process
22# the two sections of parameters in seperate passes
23kparams_remove_dm() {
24    echo "$1" | sed 's/dm="[^"]*"//'
25}
26
27# Given a dm param string which includes dynamic values, return the
28# same string with these values replaced by a magic string placeholder.
29# This same magic placeholder is used in the config file, for comparison
30# purposes.
31dmparams_mangle() {
32  local dmparams=$1
33  # First handle new key-value style verity parameters.
34  dmparams=$(echo "$dmparams" |
35    sed -e 's/root_hexdigest=[0-9a-fA-F]*/root_hexdigest=MAGIC_HASH/' |
36    sed -e 's/salt=[0-9a-fA-F]*/salt=MAGIC_SALT'/)
37  # If we didn't substitute the MAGIC_HASH yet, these are the old
38  # verity parameter format.
39  if [[ $dmparams != *MAGIC_HASH* ]]; then
40    dmparams=$(echo $dmparams | sed 's/sha1 [0-9a-fA-F]*/sha1 MAGIC_HASH/')
41  fi
42  # If we have bootcache enabled, replace its copy of the root_hexdigest
43  # with MAGIC_HASH. The parameter is positional.
44  if [[ $dmparams == *bootcache* ]]; then
45    dmparams=$(echo $dmparams |
46      sed -r 's:(bootcache (PARTUUID=)?%U(/PARTNROFF=|\+)1 [0-9]+) [0-9a-fA-F]+:\1 MAGIC_HASH:')
47  fi
48  echo $dmparams
49}
50
51# This escapes any non-alphanum character, since many such characters
52# are regex metacharacters.
53escape_regexmetas() {
54    echo "$1" | sed 's/\([^a-zA-Z0-9]\)/\\\1/g'
55}
56
57usage() {
58    echo "Usage $PROG image [config]"
59}
60
61main() {
62    # We want to catch all the discrepancies, not just the first one.
63    # So, any time we find one, we set testfail=1 and continue.
64    # When finished we will use testfail to determine our exit value.
65    local testfail=0
66    # A buffer to include useful information that we dump when things fail.
67    local output
68    # Copy of a string before it has been through sed
69    local pre_sed
70    # Where the disk image is mounted.
71    local loopdev
72
73    if [[ $# -ne 1 ]] && [[ $# -ne 2 ]]; then
74        usage
75        exit 1
76    fi
77
78    local image="$1"
79
80    # A byte that should not appear in the command line to use as a sed
81    # marker when doing regular expression replacements.
82    local M=$'\001'
83
84    # Default config location: same name/directory as this script,
85    # with a .config file extension, ie ensure_secure_kernelparams.config.
86    local configfile="$(dirname "$0")/${0/%.sh/.config}"
87    # Or, maybe a config was provided on the command line.
88    if [[ $# -eq 2 ]]; then
89        configfile="$2"
90    fi
91    # Either way, load test-expectations data from config.
92    . "$configfile" || return 1
93
94    # Set up the image on a loopback device so it's faster to access.
95    local loopdev
96    loopdev=$(loopback_partscan "${image}")
97
98    # TODO(jimhebert): Perform the kernel security tests on both the kernel
99    #                  partitions. Here, we just run it on kernel partition 4
100    #                  which is the install kernel on the recovery image.
101    #                  crosbug.com/24274
102    local loop_kern="${loopdev}p4"
103    local rootfs=$(make_temp_dir)
104    mount_loop_image_partition_ro "${loopdev}" 3 "${rootfs}"
105
106    # Pick the right set of test-expectation data to use.
107    local boardvar=$(get_boardvar_from_lsb_release "${rootfs}")
108    eval "required_kparams=(\"\${required_kparams_${boardvar}[@]}\")"
109    eval "required_kparams_regex=(\"\${required_kparams_regex_${boardvar}[@]}\")"
110    eval "optional_kparams=(\"\${optional_kparams_${boardvar}[@]}\")"
111    eval "optional_kparams_regex=(\"\${optional_kparams_regex_${boardvar}[@]}\")"
112    eval "required_dmparams=(\"\${required_dmparams_${boardvar}[@]}\")"
113    eval "required_dmparams_regex=(\"\${required_dmparams_regex_${boardvar}[@]}\")"
114    output+="required_kparams=(\n"
115    output+="$(printf "\t'%s'\n" "${required_kparams[@]}")\n)\n"
116    output+="required_kparams_regex=(\n"
117    output+="$(printf "\t'%s'\n" "${required_kparams_regex[@]}")\n)\n"
118    output+="optional_kparams=(\n"
119    output+="$(printf "\t'%s'\n" "${optional_kparams[@]}")\n)\n"
120    output+="optional_kparams_regex=(\n"
121    output+="$(printf "\t'%s'\n" "${optional_kparams_regex[@]}")\n)\n"
122    output+="required_dmparams=(\n"
123    output+="$(printf "\t'%s'\n" "${required_dmparams[@]}")\n)\n"
124    output+="required_dmparams_regex=(\n"
125    output+="$(printf "\t'%s'\n" "${required_dmparams_regex[@]}")\n)\n"
126
127    # Divide the dm params from the rest and process seperately.
128    local kparams=$(sudo futility dump_kernel_config "${loop_kern}")
129    local dmparams=$(get_dmparams "$kparams")
130    local kparams_nodm=$(kparams_remove_dm "$kparams")
131
132    output+="\nkparams='${kparams}'\n"
133    output+="\ndmparams='${dmparams}'\n"
134    output+="\nkparams_nodm='${kparams_nodm}'\n"
135
136    mangled_dmparams=$(dmparams_mangle "${dmparams}")
137    output+="\nmangled_dmparams='${mangled_dmparams}'\n"
138    # Special-case handling of the dm= param:
139    testfail=1
140    for expected_dmparams in "${required_dmparams[@]}"; do
141      # Filter out all dynamic parameters.
142      if [ "$mangled_dmparams" = "$expected_dmparams" ]; then
143        testfail=0
144        break
145      fi
146    done
147
148    local sedout
149    for expected_dmparams in "${required_dmparams_regex[@]}"; do
150      if ! sedout=$(echo "${mangled_dmparams}" | \
151                    sed "s${M}^${expected_dmparams}\$${M}${M}"); then
152        echo "INTERNAL ERROR from sed script: ${expected_dmparams}"
153        break
154      elif [[ -z "${sedout}" ]]; then
155        testfail=0
156        break
157      fi
158    done
159
160    if [ $testfail -eq 1 ]; then
161      echo "Kernel dm= parameter does not match any expected values!"
162      echo "Actual value:          ${dmparams}"
163      echo "Mangled testing value: ${mangled_dmparams}"
164      if [[ ${#required_dmparams[@]} -gt 0 ]]; then
165        echo "Expected -- only one need match:"
166        printf "  >>> %s\n" "${required_dmparams[@]}"
167      fi
168      if [[ ${#required_dmparams_regex[@]} -gt 0 ]]; then
169        echo "Expected (regex) -- only one need match:"
170        printf "  >>> %s\n" "${required_dmparams_regex[@]}"
171      fi
172    fi
173
174    # Ensure all other required params are present.
175    for param in "${required_kparams[@]}"; do
176        if [[ "$kparams_nodm" != *$param* ]]; then
177            echo "Kernel parameters missing required value: $param"
178            testfail=1
179        else
180            # Remove matched params as we go. If all goes well, kparams_nodm
181            # will be nothing left but whitespace by the end.
182            param=$(escape_regexmetas "$param")
183            kparams_nodm=$(echo " ${kparams_nodm} " |
184                           sed "s${M} ${param} ${M} ${M}")
185        fi
186    done
187
188    # Ensure all other required regex params are present.
189    for param in "${required_kparams_regex[@]}"; do
190      pre_sed=" ${kparams_nodm} "
191      kparams_nodm=$(echo "${pre_sed}" | sed "s${M} ${param} ${M} ${M}")
192      if [[ "${pre_sed}" == "${kparams_nodm}" ]]; then
193        echo "Kernel parameters missing required value: ${param}"
194        testfail=1
195      fi
196    done
197
198    # Check-off each of the allowed-but-optional params that were present.
199    for param in "${optional_kparams[@]}"; do
200        param=$(escape_regexmetas "$param")
201        kparams_nodm=$(echo " ${kparams_nodm} " |
202                       sed "s${M} ${param} ${M} ${M}")
203    done
204
205    # Check-off each of the allowed-but-optional params that were present.
206    for param in "${optional_kparams_regex[@]}"; do
207        kparams_nodm=$(echo " ${kparams_nodm} " |
208                       sed "s${M} ${param} ${M} ${M}")
209    done
210
211    # This section enforces the default-deny for any unexpected params
212    # not already processed by one of the above loops.
213    if [[ ! -z ${kparams_nodm// /} ]]; then
214        echo "Unexpected kernel parameters found:"
215        echo " $(echo "${kparams_nodm}" | sed -r 's:  +: :g')"
216        testfail=1
217    fi
218
219    if [[ ${testfail} -eq 1 ]]; then
220        echo "Debug output:"
221        printf '%b\n' "${output}"
222        echo "(actual error will be at the top of output)"
223    fi
224
225    exit $testfail
226}
227
228main $@
229