grub-shell-luks-tester.in 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. #! @BUILD_SHEBANG@ -e
  2. # Test GRUBs ability to read various LUKS containers
  3. # Copyright (C) 2023 Free Software Foundation, Inc.
  4. #
  5. # GRUB is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # GRUB is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with GRUB. If not, see <http://www.gnu.org/licenses/>.
  17. # Initialize some variables.
  18. prefix="@prefix@"
  19. exec_prefix="@exec_prefix@"
  20. datarootdir="@datarootdir@"
  21. builddir="@builddir@"
  22. PACKAGE_NAME=@PACKAGE_NAME@
  23. PACKAGE_TARNAME=@PACKAGE_TARNAME@
  24. PACKAGE_VERSION=@PACKAGE_VERSION@
  25. # Force build directory components
  26. PATH="${builddir}:$PATH"
  27. export PATH
  28. grub_shell_opts=
  29. disksize=20M
  30. detached_header=
  31. keyfile=
  32. keyfile_offset=
  33. keyfile_size=
  34. KEYFILE_SIZE_MAX=4096
  35. debug="${GRUB_SHELL_LUKS_DEFAULT_DEBUG:-$GRUB_TEST_DEFAULT_DEBUG}"
  36. GRUB_SHELL_LUKS_TIMEOUT=${GRUB_SHELL_LUKS_TIMEOUT:-${GRUB_SHELL_DEFAULT_TIMEOUT:-600s}}
  37. # Usage: usage
  38. # Print the usage.
  39. usage () {
  40. cat <<EOF
  41. Usage: $0 [OPTION] [SOURCE]
  42. Create a LUKS disk with cryptsetup, then verify that it is accessible by grub
  43. running in a QEMU instance.
  44. -h, --help print this message and exit
  45. -v, --version print the version information and exit
  46. --modules=MODULES pre-load specified modules MODULES
  47. --qemu-opts=OPTIONS extra options to pass to Qemu instance
  48. --cs-opts=OPTIONS extra options to pass to cryptsetup instance
  49. --cs-script=FILE script of cryptsetup commands to be run after format
  50. --luks=1|2 Use LUKS1 or LUKS2 volumes
  51. --detached-header Use a detached header
  52. --keyfile[=FILE] Use a randomly generated key file of size $KEYFILE_SIZE_MAX if not
  53. given a FILE to use as the key file.
  54. $0 creates a LUKS disk with cryptsetup, then verify that it is accessible by
  55. grub running in a QEMU instance.
  56. Report bugs to <bug-grub@gnu.org>.
  57. EOF
  58. }
  59. . "${builddir}/grub-core/modinfo.sh"
  60. # TODO: We should be selecting the drive based on disk id, change this once
  61. # grub support searching by disk id.
  62. disk="hd0"
  63. case "${grub_modinfo_target_cpu}-${grub_modinfo_platform}" in
  64. i386-qemu)
  65. disk="ata0"
  66. ;;
  67. loongarch64-efi)
  68. disk="hd1"
  69. ;;
  70. esac
  71. # Check the arguments.
  72. for option in "$@"; do
  73. case "$option" in
  74. -h | --help)
  75. usage
  76. exit 0 ;;
  77. -v | --version)
  78. echo "$0 (GNU GRUB ${PACKAGE_VERSION})"
  79. exit 0 ;;
  80. -d | --debug)
  81. debug=$((${debug:-0}+1)) ;;
  82. --debug=*)
  83. debug=$((`echo "$option" | sed -e 's/--debug=//'`)) ;;
  84. --modules=*)
  85. ms=`echo "$option" | sed -e 's/--modules=//'`
  86. modules="$modules,$ms" ;;
  87. --qemu-opts=*)
  88. qs=`echo "$option" | sed -e 's/--qemu-opts=//'`
  89. qemuopts="$qemuopts $qs" ;;
  90. --cs-opts=*)
  91. qs=`echo "$option" | sed -e 's/--cs-opts=//'`
  92. csopts="$csopts $qs" ;;
  93. --cs-script=*)
  94. qs=`echo "$option" | sed -e 's/--cs-script=//'`
  95. csscripts="$csscripts $qs" ;;
  96. --luks=*)
  97. qs=`echo "$option" | sed -e 's/--luks=//'`
  98. csopts="$csopts --type luks$qs" ;;
  99. --detached-header)
  100. detached_header=1 ;;
  101. --keyfile=*)
  102. qs=`echo "$option" | sed -e 's/--keyfile=//'`
  103. keyfile="$qs" ;;
  104. --keyfile)
  105. keyfile=1 ;;
  106. --disksize=*)
  107. qs=`echo "$option" | sed -e 's/--disksize=//'`
  108. disksize="$qs" ;;
  109. -*)
  110. echo "Unrecognized option \`$option'" 1>&2
  111. usage
  112. exit 3
  113. ;;
  114. *)
  115. if [ "x${source}" != x ] ; then
  116. echo "too many parameters at the end" 1>&2
  117. usage
  118. exit 4
  119. fi
  120. source="${option}" ;;
  121. esac
  122. done
  123. [ "${debug:-0}" -gt 1 ] && set -x
  124. grub_shell_opts="$grub_shell_opts --timeout=${GRUB_SHELL_LUKS_TIMEOUT}"
  125. if [ "${debug:-0}" -gt 2 ]; then
  126. grub_shell_opts="$grub_shell_opts --qemu-opts=-nographic"
  127. fi
  128. # Make sure that the dm-crypto device is shutdown
  129. cleanup() {
  130. if [ -e "$luksdev" ]; then
  131. cryptsetup close "$luksdev"
  132. fi
  133. if [ -z "$debug" ] && [ "${RET:-1}" -eq 0 ]; then
  134. rm -rf "$lukstestdir" || :
  135. fi
  136. }
  137. trap cleanup EXIT INT TERM KILL QUIT
  138. get_random_bytes() {
  139. local NUM_BYTES=$1
  140. dd if=/dev/urandom bs=512 count=$((($NUM_BYTES / 512)+2)) 2>/dev/null \
  141. | tr -d '\0' | dd bs=1 count=$(($NUM_BYTES)) 2>/dev/null
  142. }
  143. # create a random directory to be hold generated files
  144. lukstestdir="`mktemp -d "${TMPDIR:-/tmp}/$(basename "$0").XXXXXXXXXX"`" || exit 20
  145. luksfile=$lukstestdir/luks.disk
  146. lukshdrfile=$lukstestdir/luks.header
  147. lukskeyfile=$lukstestdir/luks.key
  148. vfile=$lukstestdir/mnt/test.verify
  149. vtext="TEST VERIFIED"
  150. testvars=$lukstestdir/testvars
  151. testcase=$lukstestdir/testcase.cfg
  152. testoutput=$lukstestdir/testoutput
  153. password=testpass
  154. [ -n "$debug" ] && echo "LUKS TEST directory: $lukstestdir" >&2
  155. # If testing keyfiles, create a big one.
  156. if [ -e "$keyfile" ]; then
  157. password=`cat "$keyfile"`
  158. elif [ -n "$keyfile" ]; then
  159. password=`get_random_bytes $KEYFILE_SIZE_MAX`
  160. fi
  161. if [ -n "$detached_header" ]; then
  162. csopts="$csopts --header $lukshdrfile"
  163. fi
  164. # create the key file
  165. echo -n "$password" > $lukskeyfile
  166. # Create a very small LUKS container for the test
  167. truncate -s $disksize $luksfile || exit 21
  168. # Format the luks disk file
  169. cryptsetup luksFormat -q $csopts $luksfile $lukskeyfile || exit 22
  170. # Run any cryptsetup scripts
  171. export luksdiskfile=${detached_header:+$lukshdrfile}${detached_header:-$luksfile}
  172. export lukskeyfile
  173. for csscript in $csscripts; do
  174. [ -f "$csscript" ] && . $csscript
  175. done
  176. # Look for --keyfile-offset and --keyfile-size options in the cryptsetup
  177. # options, and process them specially.
  178. csopen_opts=
  179. get_args=0
  180. varname=
  181. for option in $csopts; do
  182. if [ "$get_args" -gt 0 ]; then
  183. csopen_opts=" $csopen_opts $option"
  184. get_args=$(($get_args - 1))
  185. eval ${varname}=$option
  186. continue
  187. fi
  188. case "$option" in
  189. --keyfile-offset)
  190. varname=keyfile_offset
  191. get_args=1 ;;
  192. --keyfile-offset=*)
  193. keyfile_offset=`echo "$option" | sed -e 's/--keyfile-offset=//'` ;;
  194. --keyfile-size | -l)
  195. varname=keyfile_size
  196. get_args=1 ;;
  197. --keyfile-size=*)
  198. keyfile_size=`echo "$option" | sed -e 's/--keyfile-size=//'` ;;
  199. *)
  200. continue ;;
  201. esac
  202. csopen_opts=" $csopen_opts $option"
  203. done
  204. # Open LUKS device
  205. luksdev=/dev/mapper/`basename $lukstestdir`
  206. cryptsetup open ${detached_header:+--header $lukshdrfile} $csopen_opts \
  207. --key-file $lukskeyfile $luksfile `basename $luksdev` || exit 23
  208. # Make filesystem on the luks disk
  209. mkfs.vfat $luksdev >/dev/null 2>&1 || exit 24
  210. # Add verification file to filesystem
  211. mkdir $lukstestdir/mnt
  212. mount $luksdev $lukstestdir/mnt || exit 25
  213. echo "$vtext" > $vfile
  214. # Unmount filesystem
  215. umount $lukstestdir/mnt || exit 26
  216. . "@builddir@/grub-core/modinfo.sh"
  217. if [ x"${grub_modinfo_platform}" = xemu ]; then
  218. grub_testvars="(host)$testvars"
  219. grub_key_file="(host)$lukskeyfile"
  220. grub_lukshdr_file="(host)$lukshdrfile"
  221. else
  222. grub_testvars="/testvars"
  223. grub_key_file="/keyfile"
  224. grub_lukshdr_file="/luks.header"
  225. fi
  226. # Can not use --disk with a raw LUKS container because it appears qemu
  227. # tries to convert the image to and is failing with:
  228. # "Parameter 'key-secret' is required for cipher"
  229. qemuopts="$qemuopts -drive file=$luksfile,index=0,media=disk,format=raw"
  230. # Add crypto modules
  231. modules="$modules cryptodisk luks luks2 fat"
  232. # Create randomly generated trim line
  233. trim_line=`mktemp -u XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`
  234. # Create vars to import into grub script
  235. cat >$testvars <<EOF
  236. grub_debug="$debug"
  237. grub_lukshdr_file="$grub_lukshdr_file"
  238. grub_key_file="$grub_key_file"
  239. grub_keyfile_offset="$keyfile_offset"
  240. grub_keyfile_size="$keyfile_size"
  241. vfilename="`basename $vfile`"
  242. vtext="$vtext"
  243. trim_line="$trim_line"
  244. disk="$disk"
  245. EOF
  246. # If testing keyfiles, do not use password variable
  247. if [ -z "$keyfile" ]; then
  248. echo "grub_password=\"$password\"" >>$testvars
  249. fi
  250. # Create testcase script
  251. cat >$testcase <<'EOF'
  252. set debug=all
  253. search -n -f --set=testvarsdev /testvars
  254. if [ "$?" -ne 0 ]; then
  255. echo; echo "$trim_line"
  256. echo "Could not find testvars file."
  257. ${halt_cmd}
  258. fi
  259. set debug=
  260. . ($testvarsdev)/testvars
  261. # If key file exists, use it instead of password
  262. if [ -e "$grub_key_file" ]; then
  263. cryptomount_opts="$cryptomount_opts -k $grub_key_file"
  264. else
  265. cryptomount_opts="$cryptomount_opts -p $grub_password"
  266. fi
  267. if [ -n "$grub_keyfile_offset" ]; then
  268. cryptomount_opts="$cryptomount_opts -O $grub_keyfile_offset"
  269. fi
  270. if [ -n "$grub_keyfile_size" ]; then
  271. cryptomount_opts="$cryptomount_opts -S $grub_keyfile_size"
  272. fi
  273. if [ -e "$grub_lukshdr_file" ]; then
  274. cryptomount_opts="$cryptomount_opts -H $grub_lukshdr_file"
  275. fi
  276. cdisk=crypto0
  277. if test -n "$grub_debug" -a "$grub_debug" -gt 0; then
  278. echo cmd: cryptomount $cryptomount_opts ($disk)
  279. echo -n "devices: "
  280. ls
  281. fi
  282. if test -n "$grub_debug" -a "$grub_debug" -gt 1; then
  283. set debug=all
  284. fi
  285. cryptomount $cryptomount_opts ($disk)
  286. ret="$?"
  287. if test -n "$grub_debug" -a "$grub_debug" -eq 2; then
  288. set debug=
  289. fi
  290. echo; echo "$trim_line"
  291. if test $ret -eq 0; then
  292. cat ($cdisk)/$vfilename
  293. else
  294. echo "cryptomount failed: $ret"
  295. fi
  296. EOF
  297. grub_shell_opts="$grub_shell_opts --trim=${trim_line}"
  298. if [ -n "$keyfile" ]; then
  299. grub_shell_opts="$grub_shell_opts --files=${keyfile:+${grub_key_file}=${lukskeyfile}}"
  300. fi
  301. if [ -n "$detached_header" ]; then
  302. grub_shell_opts="$grub_shell_opts --files=${detached_header:+${grub_lukshdr_file}=${lukshdrfile}}"
  303. fi
  304. # Run the test in grub-shell
  305. @builddir@/grub-shell ${debug:+--debug=$debug} $grub_shell_opts \
  306. --modules="$modules" --qemu-opts="$qemuopts" \
  307. --files="${grub_testvars}=${testvars}" "$testcase" \
  308. >$testoutput
  309. ret=$?
  310. if [ "$ret" -eq 0 ]; then
  311. if ! grep -q "^${vtext}$" "$testoutput"; then
  312. echo "error: test not verified [`cat $testoutput`]" >&2
  313. exit 1
  314. fi
  315. else
  316. echo "grub-shell exited with error: $ret" >&2
  317. exit 27
  318. fi
  319. exit $ret