Commit d50830f2 authored by intrigeri's avatar intrigeri

Merge branch 'feature/15292-usb-image' into devel

We do need this branch merged in time before the 3.12 freeze.
There are still a few things to fix here and there but their
ETA is either after the freeze, or unknown.

refs: #15292
parents b8bf6b87 6a79c74a
......@@ -98,6 +98,7 @@ DEBOOTSTRAP_OPTIONS="${DEBOOTSTRAP_OPTIONS:-} --no-merged-usr"
# use our own APT repository's key:
DEBOOTSTRAP_GNUPG_HOMEDIR=$(mktemp -d)
gpg --homedir "$DEBOOTSTRAP_GNUPG_HOMEDIR" \
--no-tty \
--import config/chroot_sources/tails.chroot.gpg
if [ -e "$DEBOOTSTRAP_GNUPG_HOMEDIR/pubring.gpg" ]; then
DEBOOTSTRAP_GNUPG_KEYRING="$DEBOOTSTRAP_GNUPG_HOMEDIR/pubring.gpg"
......
This diff is collapsed.
......@@ -27,32 +27,30 @@ def target_file_url(channel, filename):
}
def idf_content(build_target, channel, product_name, version, img, iso):
installation_paths = [
{
'type': 'iso',
'target-files': [{
'url': target_file_url(channel, iso),
'sha256': sha256_file(iso),
'size': Path(iso).stat().st_size,
}],
},
]
if img is not None:
installation_paths += {
'type': 'img',
'target-files': [{
'url': target_file_url(channel, img),
'sha256': sha256_file(img),
'size': Path(img).stat().st_size,
}],
}
return to_json({
'build_target': build_target,
'channel': channel,
'product-name': product_name,
'installations': [{
'version': version,
'installation-paths': installation_paths,
'installation-paths': [
{
'type': 'img',
'target-files': [{
'url': target_file_url(channel, img),
'sha256': sha256_file(img),
'size': Path(img).stat().st_size,
}],
},
{
'type': 'iso',
'target-files': [{
'url': target_file_url(channel, iso),
'sha256': sha256_file(iso),
'size': Path(iso).stat().st_size,
}],
},
],
}],
})
......@@ -64,7 +62,7 @@ if __name__ == '__main__':
parser.add_argument('--product-name', dest='product_name', default='Tails')
parser.add_argument('--version', default=None, required=True,
help='Version of Tails .')
parser.add_argument('--img', default=None,
parser.add_argument('--img', default=None, required=True,
help='Path to the USB image.')
parser.add_argument('--iso', default=None, required=True,
help='Path to the ISO file.')
......
......@@ -41,6 +41,7 @@ sed -i -e "s/Boot menu//" "${CFG_FILE}"
sed -i -e "s/menu label Live/menu label Tails/" "${SYSLINUX_PATH}"/live*.cfg
sed -i -r -e 's/(menu label .* )\(failsafe\)/\1(Troubleshooting Mode)/' \
"${SYSLINUX_PATH}"/live*.cfg
sed -i -r -e 's/(append .*)/\1\n\tsysappend 0x40000/g' "${SYSLINUX_PATH}"/live*.cfg
cat > "${SYSLINUX_PATH}/tails.cfg" << EOF
menu color sel * #ffffffff #55555555 *
......
......@@ -7,7 +7,7 @@ echo "Configuring compression of the initramfs"
# Import ensure_hook_dependency_is_installed()
. /usr/local/lib/tails-shell-library/build.sh
ensure_hook_dependency_is_installed initramfs-tools xz-utils
ensure_hook_dependency_is_installed initramfs-tools xz-utils fatresize
# Compress the initramfs using a more size-wise efficient algorithm.
......
#!/bin/sh -e
PREREQS=""
prereqs() { echo "$PREREQS"; }
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
. /usr/share/initramfs-tools/hook-functions
copy_exec /sbin/sgdisk /sbin
copy_exec /sbin/partprobe /sbin
copy_exec /usr/sbin/fatresize /sbin
copy_exec /usr/bin/mlabel /sbin
# Needed by mlabel that uses iconv_open
copy_exec /usr/lib/x86_64-linux-gnu/gconv/IBM850.so
copy_file "regular file" /usr/lib/x86_64-linux-gnu/gconv/gconv-modules
#!/bin/sh
PREREQS="plymouth"
prereqs() { echo "$PREREQS"; }
case "$1" in
prereqs)
prereqs
exit 0
;;
esac
set -eu
# Print executed commands for debugging
if [ -n "$debug" ]; then
set -x
fi
. /scripts/functions
if [ -z "${FSUUID:-}" ]; then
echo "\$FSUUID is unset, probably because the boot medium is an ISO, and not a USB image. Skipping partitioning."
exit 0
fi
# Wait for system partition
log_begin_msg "Waiting for system partition to become available"
success=
i=0
while [ "$i" -lt 300 ]; do
if [ -b "/dev/disk/by-uuid/${FSUUID}" ]; then
success=1
break
fi
sleep 0.2
i="$(($i + 1))"
done
if [ -n "$success" ]; then
log_end_msg
else
log_failure_msg "Reached timeout waiting for system partition. Skipping partitioning."
# The exit code is irrelevant in the current implementation of live-config
exit 1
fi
# Get parent device
# XXX: We assume here that the parent device is also ready if the
# system partition is.
SYSTEM_PARTITION=$(readlink -f "/dev/disk/by-uuid/${FSUUID}")
PARENT_PATH=$(udevadm info --query=property --name="${SYSTEM_PARTITION}" \
| sed -n '/^ID_PATH=/ s/^ID_PATH=// p')
PARENT_DEVICE=$(readlink -f "/dev/disk/by-path/${PARENT_PATH}")
DEVICE_NAME=$(basename "${PARENT_DEVICE}")
# Check if this is first boot
GUID=$(sgdisk --print "${PARENT_DEVICE}" \
| sed -n '/^Disk identifier (GUID)/ s/^Disk identifier (GUID): // p')
if [ "${GUID}" != "17B81DA0-8B1E-4269-9C39-FE5C7B9B58A3" ]; then
_log_msg "Skipping partitioning because this is not the first boot"
exit 0
fi
DEVICE_TOO_SMALL_ERROR_MESSAGE="Sorry, this device is too small to run Tails. Please use a device with at least 8 GiB. Press ENTER to shut down."
# Get new system partition size
DEVICE_SIZE=$(cat "/sys/block/${DEVICE_NAME}/size")
DEVICE_SIZE_IN_MiB=$((${DEVICE_SIZE} / 2 / 1024))
if [ "${DEVICE_SIZE_IN_MiB}" -lt 7200 ]; then
plymouth message --text="${DEVICE_TOO_SMALL_ERROR_MESSAGE}"
plymouth watch-keystroke > /dev/null
poweroff -f
elif [ "${DEVICE_SIZE_IN_MiB}" -lt 14500 ]; then
SYSTEM_PARTITION_SIZE=4096M
else
SYSTEM_PARTITION_SIZE=8192M
fi
# Update partition table. This includes the following operations:
# * Move GPT backup header to the end of the device
# * Set a random disk GUID and partition GUID
# * Delete the old system partition
# * Create a new system partition of size ${SYSTEM_PARTITION_SIZE}
# * Set the partition label to "Tails"
ESP_GUID="C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
log_begin_msg "Updating partition table"
sgdisk \
--move-second-header \
--randomize-guids \
--delete=1 \
--new=1:0:+"${SYSTEM_PARTITION_SIZE}" \
--typecode="1:${ESP_GUID}" \
--change-name=1:Tails \
"${PARENT_DEVICE}"
log_end_msg
# Tell the kernel to reload the partition table
partprobe
# fatresize overwrites the VBR, so we have to back it up to be able to
# restore the boot code later
dd if="${SYSTEM_PARTITION}" of=/tmp/vbr bs=512 count=1
# Grow the filesystem
# Note that fatresize resets partition attributes
# fatresize uses "Mi" for MiB, so we have to append an "i"
FS_SIZE="${SYSTEM_PARTITION_SIZE}"i
fatresize --size="${FS_SIZE}" "${SYSTEM_PARTITION}"
# Restore boot code overwritten by fatresize
dd if=/tmp/vbr of="${SYSTEM_PARTITION}" bs=1 skip=90 seek=90 count=414
# Restore JMP instruction which jumps to the bootcode
dd if=/tmp/vbr of="${SYSTEM_PARTITION}" bs=3 count=1
# Set a random filesystem UUID (aka. FAT "Volume ID" / "serial number")
MTOOLS_SKIP_CHECK=1 mlabel -i "${SYSTEM_PARTITION}" -n ::Tails
# Set the following attributes on the system partition (we have to
# set them after running fatresize, because fatresize resets them):
# 0: system partition
# 2: legacy BIOS bootable
# 60: read-only
# 62: hidden
# 63: do not automount
sgdisk \
--attributes=1:set:0 \
--attributes=1:set:2 \
--attributes=1:set:60 \
--attributes=1:set:62 \
--attributes=1:set:63 \
"${PARENT_DEVICE}"
# Tell the kernel to reload the partition table
partprobe
......@@ -424,3 +424,6 @@ python3-sh
# For Unlock VeraCrypt Volumes
gir1.2-gudev-1.0
# For partitioning script in initramfs
fatresize
......@@ -64,14 +64,11 @@ def checkpoints
'usb-install-tails-greeter' => {
:description => "I have started Tails without network from a USB drive without a persistent partition and stopped at Tails Greeter's login screen",
:parent_checkpoint => 'no-network-logged-in',
:parent_checkpoint => nil,
:steps => [
'I create a 7200 MiB disk named "__internal"',
'I plug USB drive "__internal"',
'I install Tails to USB drive "__internal" by cloning',
'the running Tails is installed on USB drive "__internal"',
'there is no persistence partition on USB drive "__internal"',
'I shutdown Tails and wait for the computer to power off',
'I write the Tails USB image to disk "__internal"',
'I start Tails from USB drive "__internal" with network unplugged',
'the boot device has safe access rights',
'Tails is running from USB drive "__internal"',
......
......@@ -29,9 +29,10 @@ Given /^I create an?( (\d+) ([[:alpha:]]+))? ([[:alnum:]]+) partition( labeled "
$vm.storage.disk_mkpartfs(name, parttype, fstype, opts)
end
Given /^I write the Tails ISO image to disk "([^"]+)"$/ do |name|
Given /^I write (|an old version of )the Tails (ISO|USB) image to disk "([^"]+)"$/ do |old, type, name|
src_disk = {
:path => TAILS_ISO,
:path => (old == '' ? (type == 'ISO' ? TAILS_ISO : TAILS_IMG)
: (type == 'ISO' ? OLD_TAILS_ISO : OLD_TAILS_IMG)),
:opts => {
:format => "raw",
:readonly => true
......
......@@ -274,7 +274,7 @@ def check_disk_integrity(name, dev, scheme)
"Unexpected partition scheme on USB drive '#{name}', '#{dev}'")
end
def check_part_integrity(name, dev, usage, fs_type, part_label, part_type = nil)
def check_part_integrity(name, dev, usage, fs_type, part_label = nil, part_type = nil)
info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
info_split = info.split("\n org\.freedesktop\.UDisks2\.Partition:\n")
dev_info = info_split[0]
......@@ -283,8 +283,10 @@ def check_part_integrity(name, dev, usage, fs_type, part_label, part_type = nil)
"Unexpected device field 'usage' on USB drive '#{name}', '#{dev}'")
assert(dev_info.match("^ IdType: +#{fs_type}$"),
"Unexpected device field 'IdType' on USB drive '#{name}', '#{dev}'")
assert(part_info.match("^ Name: +#{part_label}$"),
"Unexpected partition label on USB drive '#{name}', '#{dev}'")
if part_label
assert(part_info.match("^ Name: +#{part_label}$"),
"Unexpected partition label on USB drive '#{name}', '#{dev}'")
end
if part_type
assert(part_info.match("^ Type: +#{part_type}$"),
"Unexpected partition type on USB drive '#{name}', '#{dev}'")
......@@ -453,6 +455,32 @@ def boot_device_type
device_info(boot_device)['ID_BUS']
end
# Turn udisksctl info output into something more manipulable:
def parse_udisksctl_info(input)
tree = {}
section = nil
key = nil
input.chomp.split("\n").each { |line|
case line
when /^\/org\/freedesktop\/UDisks2\/block_devices\//
# no-op, ignore first line = device
when /^ (org\.freedesktop\.UDisks2\..+):$/
section = $1
tree[section] = {}
when /^\s+(.+?):\s+(.+)$/
key = $1
value = $2
tree[section][key] = value
else
# XXX: Best effort = consider this a continuation from previous
# line (e.g. Symlinks), and add the whole line, without
# stripping anything (e.g. leading whitespaces)
tree[section][key] += line
end
}
return tree
end
Then /^Tails is running from (.*) drive "([^"]+)"$/ do |bus, name|
bus = bus.downcase
case bus
......@@ -788,3 +816,83 @@ Then /^I can successfully install the incremental upgrade to version (.+)$/ do |
@screen.wait('TailsUpgraderApplyingUpgrade.png', 20)
@screen.wait('TailsUpgraderDone.png', 60)
end
Then /^the label of the system partition on "([^"]+)" is "([^"]+)"$/ do |name, label|
assert($vm.is_running?)
disk_dev = $vm.disk_dev(name)
part_dev = disk_dev + "1"
check_disk_integrity(name, disk_dev, "gpt")
check_part_integrity(name, part_dev, "filesystem", "vfat", label)
end
Then /^the system partition on "([^"]+)" is an EFI system partition$/ do |name|
assert($vm.is_running?)
disk_dev = $vm.disk_dev(name)
part_dev = disk_dev + "1"
check_disk_integrity(name, disk_dev, "gpt")
check_part_integrity(name, part_dev, "filesystem", "vfat", nil,
# EFI System Partition
'c12a7328-f81f-11d2-ba4b-00a0c93ec93b')
end
Then /^the FAT filesystem on the system partition on "([^"]+)" is at least (\d+)(.+) large$/ do |name, size, unit|
# Let's use bytes all the way:
wanted_size = convert_to_bytes(size.to_i, unit)
disk_dev = $vm.disk_dev(name)
part_dev = disk_dev + "1"
udisks_info = $vm.execute_successfully("udisksctl info --block-device #{part_dev}").stdout
partition_size = parse_udisksctl_info(udisks_info)['org.freedesktop.UDisks2.Partition']['Size'].to_i
# Partition size:
assert(partition_size >= wanted_size,
"FAT partition is too small: #{partition_size} is less than #{wanted_size}")
# -B 1 forces size to be expressed in bytes rather than (1K) blocks:
fs_size = $vm.execute_successfully(
"df --output=size -B 1 '/lib/live/mount/medium'"
).stdout.split("\n")[1].to_i
assert(fs_size >= wanted_size,
"FAT filesystem is too small: #{fs_size} is less than #{wanted_size}")
end
Then /^the UUID of the FAT filesystem on the system partition on "([^"]+)" was randomized$/ do |name|
disk_dev = $vm.disk_dev(name)
part_dev = disk_dev + "1"
# Get the UUID from the block area:
udisks_info = $vm.execute_successfully("udisksctl info --block-device #{part_dev}").stdout
fs_uuid = parse_udisksctl_info(udisks_info)['org.freedesktop.UDisks2.Block']['IdUUID']
static_uuid = 'A690-20D2'
assert(fs_uuid != static_uuid,
"FS UUID on #{name} wasn't randomized, it's still: #{fs_uuid}")
end
Then /^the label of the FAT filesystem on the system partition on "([^"]+)" is "([^"]+)"$/ do |name, label|
disk_dev = $vm.disk_dev(name)
part_dev = disk_dev + "1"
# Get FS label from the block area:
udisks_info = $vm.execute_successfully("udisksctl info --block-device #{part_dev}").stdout
fs_label = parse_udisksctl_info(udisks_info)['org.freedesktop.UDisks2.Block']['IdLabel']
assert(label == fs_label,
"FS label on #{part_dev} is #{fs_label} instead of the expected #{label}")
end
Then /^the system partition on "([^"]+)" has the expected flags$/ do |name|
disk_dev = $vm.disk_dev(name)
part_dev = disk_dev + "1"
# Look at the flags from the partition area:
udisks_info = $vm.execute_successfully("udisksctl info --block-device #{part_dev}").stdout
flags = parse_udisksctl_info(udisks_info)['org.freedesktop.UDisks2.Partition']['Flags']
# See SYSTEM_PARTITION_FLAGS in create-usb-image-from-iso: 0xd000000000000005,
# displayed in decimal (14987979559889010693) in udisksctl's output:
expected_flags = 0xd000000000000005
assert(flags == expected_flags.to_s,
"Got #{flags} as partition flags on #{part_dev} (for #{name}), instead of the expected #{expected_flags}")
end
......@@ -42,7 +42,9 @@ GIT_DIR = ENV['PWD']
KEEP_SNAPSHOTS = !ENV['KEEP_SNAPSHOTS'].nil?
LIVE_USER = cmd_helper(". config/chroot_local-includes/etc/live/config.d/username.conf; echo ${LIVE_USERNAME}").chomp
TAILS_ISO = ENV['TAILS_ISO']
TAILS_IMG = TAILS_ISO.sub(/\.iso/, '.img')
OLD_TAILS_ISO = ENV['OLD_TAILS_ISO'] || TAILS_ISO
OLD_TAILS_IMG = OLD_TAILS_ISO.sub(/\.iso/, '.img')
TIME_AT_START = Time.now
loop do
ARTIFACTS_DIR = $config['TMPDIR'] + "/run-" +
......
......@@ -159,30 +159,36 @@ def add_lan_host(ipaddr, port)
end
BeforeFeature('@product') do |feature|
if TAILS_ISO.nil?
raise "No Tails ISO image specified, and none could be found in the " +
"current directory"
end
if File.exist?(TAILS_ISO)
# Workaround: when libvirt takes ownership of the ISO image it may
# become unreadable for the live user inside the guest in the
# host-to-guest share used for some tests.
images = {'ISO' => TAILS_ISO, 'IMG' => TAILS_IMG}
images.each { |type, path|
if path.nil?
raise "No Tails #{type} image specified, and none could be found in the " +
"current directory"
end
if File.exist?(path)
# Workaround: when libvirt takes ownership of the ISO/IMG image it may
# become unreadable for the live user inside the guest in the
# host-to-guest share used for some tests.
if !File.world_readable?(TAILS_ISO)
if File.owned?(TAILS_ISO)
File.chmod(0644, TAILS_ISO)
else
raise "warning: the Tails ISO image must be world readable or be " +
"owned by the current user to be available inside the guest " +
"VM via host-to-guest shares, which is required by some tests"
if !File.world_readable?(path)
if File.owned?(path)
File.chmod(0644, path)
else
raise "warning: the Tails #{type} image must be world readable or be " +
"owned by the current user to be available inside the guest " +
"VM via host-to-guest shares, which is required by some tests"
end
end
else
raise "The specified Tails #{type} image '#{path}' does not exist"
end
else
raise "The specified Tails ISO image '#{TAILS_ISO}' does not exist"
end
}
if !File.exist?(OLD_TAILS_ISO)
raise "The specified old Tails ISO image '#{OLD_TAILS_ISO}' does not exist"
end
if !File.exist?(OLD_TAILS_IMG)
raise "The specified old Tails IMG image '#{OLD_TAILS_IMG}' does not exist"