e9b97e42b1
The import will error out if there is not enough data, or too much data provided.
97 lines
3.3 KiB
Bash
Executable File
97 lines
3.3 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# This Admin API call is implemented as a custom script, instead of dumb
|
|
# passthrough to qubesd because it may get huge amount of data (whole root.img
|
|
# for example). qubesd cannot handle it because:
|
|
# 1. It loads the whole payload into memory, before even start looking at it
|
|
# (and later, do not allow to modify/append it).
|
|
# 2. There is 64kB limit on payload size that qubesd can handle (because of
|
|
# point 1).
|
|
# 3. Performance reasons (qubesd is not optimized for performance, passing
|
|
# such large data stream through it would take ages).
|
|
#
|
|
# The whole admin.vm.volume.Import consists of:
|
|
# 1. Permissions checks, getting a path from appropriate storage pool (done
|
|
# by qubesd)
|
|
# 2. Actual data import (done by this script, using dd)
|
|
# 3. Report final result, produce final response to the caller (done by
|
|
# qubesd)
|
|
#
|
|
# This way we do not pass all the data through qubesd, but still can
|
|
# control the process from there in a meaningful way. Note that the last
|
|
# part (second call to qubesd) may perform all kind of verification (like
|
|
# a signature check on the data, or so) and can also prevent VM from
|
|
# starting (hooking also domain-pre-start event) from not verified image.
|
|
#
|
|
# Note that this script implements two calls:
|
|
# - admin.vm.volume.Import
|
|
# - admin.vm.volume.ImportWithSize
|
|
# In the case of admin.vm.ImportWithSize, the first line of payload is then
|
|
# data size in bytes. This is so that we can already notify qubesd to create a
|
|
# volume with the new size.
|
|
|
|
set -e
|
|
|
|
# make dd output consistent
|
|
export LC_ALL=C
|
|
|
|
# use temporary file, because env variables deal poorly with \0 inside
|
|
tmpfile=$(mktemp)
|
|
trap 'rm -f $tmpfile' EXIT
|
|
|
|
requested_size=""
|
|
if [[ "${0##*/}" == admin.vm.volume.ImportWithSize ]]; then
|
|
read -r requested_size
|
|
fi
|
|
|
|
echo -n "$requested_size" | qubesd-query -c /var/run/qubesd.internal.sock \
|
|
"$QREXEC_REMOTE_DOMAIN" \
|
|
"internal.vm.volume.ImportBegin" \
|
|
"$QREXEC_REQUESTED_TARGET" \
|
|
"$1" >"$tmpfile"
|
|
|
|
# exit if qubesd returned an error (not '0\0')
|
|
if [ "$(head -c 2 "$tmpfile" | xxd -p)" != "3000" ]; then
|
|
cat "$tmpfile"
|
|
exit 1
|
|
fi
|
|
size=$(tail -c +3 "$tmpfile"|cut -d ' ' -f 1)
|
|
path=$(tail -c +3 "$tmpfile"|cut -d ' ' -f 2)
|
|
|
|
error=""
|
|
|
|
# now process stdin into this path
|
|
if ! sudo dd bs=128K of="$path" count="$size" iflag=count_bytes,fullblock \
|
|
conv=sparse,notrunc,nocreat,fdatasync 2>"$tmpfile"; then
|
|
error="error copying data"
|
|
fi
|
|
|
|
# Examine dd's output and check if number of bytes copied matches
|
|
if [ -z "$error" ]; then
|
|
bytes_copied=$(tail -n1 "$tmpfile" | cut -d ' ' -f 1)
|
|
if [ "$bytes_copied" -ne "$size" ]; then
|
|
error="not enough data (copied $bytes_copied bytes, expected $size bytes)"
|
|
fi
|
|
fi
|
|
|
|
# Check if there is nothing more to be read from stdin
|
|
if [ -z "$error" ]; then
|
|
if ! dd of="$tmpfile" bs=1 count=1 status=none || \
|
|
[ "$(stat -c %s "$tmpfile")" -ne 0 ]; then
|
|
error="too much data (expected $size bytes)"
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$error" ]; then
|
|
status="ok"
|
|
else
|
|
status="fail\n$error"
|
|
fi
|
|
|
|
# send status notification to qubesd, and pass its response to the caller
|
|
echo -ne "$status" | qubesd-query -c /var/run/qubesd.internal.sock \
|
|
"$QREXEC_REMOTE_DOMAIN" \
|
|
"internal.vm.volume.ImportEnd" \
|
|
"$QREXEC_REQUESTED_TARGET" \
|
|
"$1"
|