Commit 5f588e52 authored by intrigeri's avatar intrigeri
Browse files

Merge remote-tracking branch 'origin/bugfix/12354-drop-kexec-memory-wipe' into...

Merge remote-tracking branch 'origin/bugfix/12354-drop-kexec-memory-wipe' into feature/stretch (Fix-committed: #12428, #12354)
parents 29918347 33085184
#!/bin/sh
set -e
set -u
PATH="/usr/local/bin:${PATH}"
KEXEC_CONF=/etc/default/kexec
KERNEL_IMAGE=$(tails-get-bootinfo kernel)
INITRD=$(tails-get-bootinfo initrd)
echo "KERNEL_IMAGE=\"${KERNEL_IMAGE}\"" >> "$KEXEC_CONF"
echo "INITRD=\"${INITRD}\"" >> "$KEXEC_CONF"
if grep -qw debug=wipemem /proc/cmdline; then
echo 'APPEND="${APPEND} sdmemdebug=1"' >> "$KEXEC_CONF"
else
echo 'APPEND="${APPEND} quiet"' >> "$KEXEC_CONF"
fi
#!/bin/sh
set -e
set -u
PATH="/usr/local/bin:${PATH}"
MEMLOCKD_CONF=/etc/memlockd.cfg
tails-get-bootinfo kernel >> "$MEMLOCKD_CONF"
tails-get-bootinfo initrd >> "$MEMLOCKD_CONF"
#!/bin/sh
set -e
# FIXME: what is this used for? dependency-based hooks ordering?
PREREQ=""
prereqs() {
echo "${PREREQ}"
}
case "${1}" in
prereqs)
prereqs
exit 0
;;
esac
. /usr/share/initramfs-tools/hook-functions
copy_exec /sbin/poweroff
copy_exec /sbin/reboot
copy_exec /usr/bin/sdmem
copy_exec /usr/bin/seq
#!/bin/sh
set -e
PREREQ=""
prereqs () {
echo "${PREREQ}"
}
case "${1}" in
prereqs)
prereqs
exit 0
;;
esac
. /usr/share/initramfs-tools/hook-functions
# systemd-shutdown itself
mkdir -p $DESTDIR/lib/systemd
copy_exec /lib/systemd/systemd-shutdown /shutdown
# Our shutdown hook (run from inside the initramfs)
mkdir -p $DESTDIR/lib/systemd/system-shutdown
copy_file "regular file" \
/usr/local/lib/initramfs-pre-shutdown-hook \
/lib/systemd/system-shutdown/tails
# Ensure systemd detects when we're in the initramfs on shutdown
# (see the in_initrd function in the systemd source tree)
touch $DESTDIR/etc/initrd-release
exit 0
#!/bin/sh
PREREQ=""
# Duplicated in features/step_definitions/erase_memory.rb
KERNEL_MEM_RESERVED_K=$((64*1024))
ADMIN_MEM_RESERVED_K=$((128*1024))
prereqs() {
echo "${PREREQ}"
}
tweak_sysctl() {
echo 3 > /proc/sys/kernel/printk
echo 3 > /proc/sys/vm/drop_caches
echo 0 > /proc/sys/vm/overcommit_memory
echo 0 > /proc/sys/vm/oom_kill_allocating_task
echo 0 > /proc/sys/vm/oom_dump_tasks
if [ "${sdmemdebug}" = 1 ] ; then
# Make sure the kernel doesn't starve, for better reliability when
# running the test suite.
echo "$KERNEL_MEM_RESERVED_K" > /proc/sys/vm/min_free_kbytes
echo "$ADMIN_MEM_RESERVED_K" > /proc/sys/vm/admin_reserve_kbytes
fi
}
case ${1} in
prereqs)
prereqs
exit 0
;;
esac
if [ -n "${sdmem}" ] ; then
tweak_sysctl
if [ -z "${sdmemopts}" ] ; then
sdmemopts="v"
fi
/usr/bin/sdmem "-${sdmemopts}"
echo "Memory has been wiped!"
fi
if [ "${sdmemdebug}" = 1 ] ; then
clear
echo "Going to sleep 10 minutes. Happy dumping!"
sleep 1200
fi
case "${sdmem}" in
halt)
echo "Powering off..."
/sbin/poweroff -fd
;;
reboot)
echo "Restarting..."
/sbin/reboot -fd
;;
*)
;;
esac
......@@ -139,7 +139,6 @@ iptables
isolinux
ferm
keepassx
kexec-tools
keyringer
memlockd
less
......@@ -267,6 +266,7 @@ firmware-b43legacy-installer
### Xorg
xorg
xserver-xorg-input-all
xserver-xorg-legacy
xserver-xorg-video-all
xserver-xorg-video-cirrus
xserver-xorg-video-intel
......
--- a/etc/init.d/kexec-load 2014-12-01 20:23:12.826065938 +0100
+++ b/etc/init.d/kexec-load 2014-12-01 20:23:31.389572352 +0100
@@ -106,19 +106,6 @@
exit 3
;;
stop)
- # If running systemd, we want kexec reboot only if current
- # command is reboot. If not running systemd, check if
- # runlevel has been changed to 6 which indicates we are
- # rebooting
- if [ -d /run/systemd/system ]; then
- systemctl list-jobs systemd-kexec.service | grep -q systemd-kexec.service
- if [ $? -ne 0 ]; then
- exit 0
- fi
- else if [ -x /sbin/runlevel -a "$(runlevel | awk '{ print $2 }')" != "6" ]; then
- exit 0
- fi
- fi
do_stop
;;
*)
Tails specific: we use kexec on both halt (runlevel 0) on top of reboot
(runlevel 6).
--- chroot.orig/etc/init.d/kexec-load 2011-01-14 12:30:05.089859516 +0100
+++ chroot/etc/init.d/kexec-load 2011-01-14 12:30:29.159667183 +0100
@@ -8,1 +8,1 @@
-# Default-Stop: 6
+# Default-Stop: 0 6
live-boot live-boot/smem boolean true
live-boot live-boot/sdmem boolean true
......@@ -2,21 +2,62 @@
Feature: Emergency shutdown
Scenario: The emergency shutdown applet can shutdown Tails
Given I have started Tails from DVD without network and logged in
Given I have started Tails from DVD and logged in and the network is connected
When I request a shutdown using the emergency shutdown applet
Then Tails eventually shuts down
Scenario: The emergency shutdown applet can reboot Tails
Given I have started Tails from DVD without network and logged in
Given I have started Tails from DVD and logged in and the network is connected
When I request a reboot using the emergency shutdown applet
Then Tails eventually restarts
# Test something close to real-world usage, without interfering,
# i.e. without the "I prepare Tails for memory erasure tests" step
Scenario: Tails shuts down on DVD boot medium removal
Given I have started Tails from DVD without network and logged in
Given I have started Tails from DVD and logged in and the network is connected
When I eject the boot medium
Then Tails eventually shuts down
Scenario: Tails shuts down on USB boot medium removal
Given I have started Tails without network from a USB drive without a persistent partition and logged in
Scenario: Tails erases memory and shuts down on DVD boot medium removal: aufs read-write branch
Given I have started Tails from DVD without network and logged in
And I prepare Tails for memory erasure tests
And I fill a 128 MiB file with a known pattern on the root filesystem
And patterns cover at least 128 MiB in the guest's memory
When I eject the boot medium
Then Tails eventually shuts down
And I wait for Tails to finish wiping the memory
Then I find very few patterns in the guest's memory
And Tails eventually shuts down
Scenario: Tails erases memory and shuts down on DVD boot medium removal: vfat
Given I have started Tails from DVD without network and logged in
And I prepare Tails for memory erasure tests
And I plug and mount a 128 MiB USB drive with a vfat filesystem
And I fill the USB drive with a known pattern
And I read the content of the test FS
And patterns cover at least 99% of the test FS size in the guest's memory
When I eject the boot medium
And I wait for Tails to finish wiping the memory
Then I find very few patterns in the guest's memory
And Tails eventually shuts down
Scenario: Tails erases memory and shuts down on DVD boot medium removal: LUKS-encrypted ext4
Given I have started Tails from DVD without network and logged in
And I prepare Tails for memory erasure tests
And I plug and mount a 128 MiB USB drive with an ext4 filesystem encrypted with password "asdf"
And I fill the USB drive with a known pattern
And I read the content of the test FS
And patterns cover at least 99% of the test FS size in the guest's memory
When I eject the boot medium
And I wait for Tails to finish wiping the memory
Then I find very few patterns in the guest's memory
And Tails eventually shuts down
Scenario: Tails erases memory and shuts down on USB boot medium removal: persistent data
Given I have started Tails without network from a USB drive with a persistent partition enabled and logged in
And I prepare Tails for memory erasure tests
And I fill a 128 MiB file with a known pattern on the persistent filesystem
And patterns cover at least 128 MiB in the guest's memory
When I eject the boot medium
And I wait for Tails to finish wiping the memory
Then I find very few patterns in the guest's memory
And Tails eventually shuts down
......@@ -6,10 +6,9 @@ Feature: System memory erasure on shutdown
# These tests rely on the Linux kernel's memory poisoning features.
# The feature is called "on shutdown" as this is the security guarantee
# we document, but in practice we have no good way to test behavior on shutdown
# per-se (known patterns allocated in memory will be erased _before_ shutdown
# by the kernel). So we test that some important bits of memory are erased
# _before_ shutdown.
# we document, but in practice we test that some important bits of memory
# are erased _before_ shutdown, while for some others we really test
# behavior at shutdown time.
Scenario: Erasure of memory freed by killed userspace processes
Given I have started Tails from DVD without network and logged in
......@@ -59,3 +58,22 @@ Feature: System memory erasure on shutdown
Then patterns cover at least 99% of the test FS size in the guest's memory
When I umount the USB drive
Then I find very few patterns in the guest's memory
Scenario: Erasure of the aufs read-write branch on shutdown
Given I have started Tails from DVD without network and logged in
And I prepare Tails for memory erasure tests
When I fill a 128 MiB file with a known pattern on the root filesystem
# ensure the pattern is in memory due to tmpfs, not to disk cache
And I drop all kernel caches
Then patterns cover at least 128 MiB in the guest's memory
When I trigger shutdown
And I wait 20 seconds
Then I find very few patterns in the guest's memory
Scenario: Erasure of read and write disk caches of persistent data on shutdown
Given I have started Tails without network from a USB drive with a persistent partition enabled and logged in
And I prepare Tails for memory erasure tests
When I fill a 128 MiB file with a known pattern on the persistent filesystem
When I trigger shutdown
And I wait 20 seconds
Then I find very few patterns in the guest's memory
features/images/MemoryWipeCompleted.png

2.25 KB | W: | H:

features/images/MemoryWipeCompleted.png

425 Bytes | W: | H:

features/images/MemoryWipeCompleted.png
features/images/MemoryWipeCompleted.png
features/images/MemoryWipeCompleted.png
features/images/MemoryWipeCompleted.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -48,10 +48,6 @@ Given /^a computer$/ do
$vm = VM.new($virt, VM_XML_PATH, $vmnet, $vmstorage, DISPLAY)
end
Given /^the computer has (\d+) ([[:alpha:]]+) of RAM$/ do |size, unit|
$vm.set_ram_size(size, unit)
end
Given /^the computer is set to boot from the Tails DVD$/ do
$vm.set_cdrom_boot(TAILS_ISO)
end
......@@ -193,15 +189,8 @@ def boot_menu_tab_msg_image
end
end
def memory_wipe_timeout
nr_gigs_of_ram = convert_from_bytes($vm.get_ram_size_in_bytes, 'GiB').ceil
nr_gigs_of_ram*30
end
Given /^Tails is at the boot menu's cmdline( after rebooting)?$/ do |reboot|
boot_timeout = 3*60
# We need some extra time for memory wiping if rebooting
boot_timeout += memory_wipe_timeout if reboot
# Simply looking for the boot splash image is not robust; sometimes
# sikuli is not fast enough to see it. Here we hope that spamming
# TAB, which will halt the boot process by showing the prompt for
......@@ -512,26 +501,12 @@ Given /^I kill the process "([^"]+)"$/ do |process|
end
Then /^Tails eventually (shuts down|restarts)$/ do |mode|
nr_gibs_of_ram = convert_from_bytes($vm.get_ram_size_in_bytes, 'GiB').ceil
timeout = nr_gibs_of_ram*5*60
# Work around Tails bug #11786, where something goes wrong when we
# kexec to the new kernel for memory wiping and gets dropped to a
# BusyBox shell instead.
try_for(timeout) do
if @screen.existsAny(['TailsBug11786a.png', 'TailsBug11786b.png'])
puts "We were hit by bug #11786: memory wiping got stuck"
if mode == 'restarts'
$vm.reset
else
$vm.power_off
end
try_for(3*60) do
if mode == 'restarts'
@screen.find('TailsGreeter.png')
true
else
if mode == 'restarts'
@screen.find('TailsGreeter.png')
true
else
! $vm.is_running?
end
! $vm.is_running?
end
end
end
......@@ -956,12 +931,13 @@ def share_host_files(files)
return mount_dir
end
def mount_USB_drive(disk, fs)
def mount_USB_drive(disk, fs_options = {})
fs_options[:encrypted] ||= false
@tmp_usb_drive_mount_dir = $vm.execute_successfully('mktemp -d').stdout.chomp
dev = $vm.disk_dev(disk)
partition = dev + '1'
if /\bencrypted with password\b/.match(fs)
password = /encrypted with password "([^"]+)"/.match(fs)[1]
if fs_options[:encrypted]
password = fs_options[:password]
assert_not_nil(password)
luks_mapping = "#{disk}_unlocked"
$vm.execute_successfully(
......@@ -971,13 +947,11 @@ def mount_USB_drive(disk, fs)
$vm.execute_successfully(
"mount /dev/mapper/#{luks_mapping} #{@tmp_usb_drive_mount_dir}"
)
@tmp_filesystem_is_encrypted = true
else
$vm.execute_successfully("mount #{partition} #{@tmp_usb_drive_mount_dir}")
@tmp_filesystem_is_encrypted = false
end
@tmp_filesystem_disk = disk
@tmp_filesystem_fs = fs
@tmp_filesystem_options = fs_options
@tmp_filesystem_partition = partition
return @tmp_usb_drive_mount_dir
end
......@@ -989,7 +963,13 @@ When(/^I plug and mount a (\d+) MiB USB drive with an? (.*)$/) do |size_MiB, fs|
step "I create a gpt partition labeled \"#{disk}\" with " +
"an #{fs} on disk \"#{disk}\""
step "I plug USB drive \"#{disk}\""
mount_dir = mount_USB_drive(disk, fs)
fs_options = {}
fs_options[:filesystem] = /(.*) filesystem/.match(fs)[1]
if /\bencrypted with password\b/.match(fs)
fs_options[:encrypted] = true
fs_options[:password] = /encrypted with password "([^"]+)"/.match(fs)[1]
end
mount_dir = mount_USB_drive(disk, fs_options)
@tmp_filesystem_size_b = convert_to_bytes(
avail_space_in_mountpoint_kB(mount_dir),
'KB'
......@@ -997,12 +977,12 @@ When(/^I plug and mount a (\d+) MiB USB drive with an? (.*)$/) do |size_MiB, fs|
end
When(/^I mount the USB drive again$/) do
mount_USB_drive(@tmp_filesystem_disk, @tmp_filesystem_fs)
mount_USB_drive(@tmp_filesystem_disk, @tmp_filesystem_options)
end
When(/^I umount the USB drive$/) do
$vm.execute_successfully("umount #{@tmp_usb_drive_mount_dir}")
if @tmp_filesystem_is_encrypted
if @tmp_filesystem_options[:encrypted]
$vm.execute_successfully("cryptsetup luksClose #{@tmp_filesystem_disk}_unlocked")
end
end
......
......@@ -58,7 +58,10 @@ Given /^I prepare Tails for memory erasure tests$/ do
@detected_ram_m = detected_ram_in_MiB
# Free some more memory by dropping the caches etc.
$vm.execute_successfully("echo 3 > /proc/sys/vm/drop_caches")
step "I drop all kernel caches"
# Have our initramfs-pre-shutdown-hook sleep for a while
$vm.execute_successfully("touch /run/initramfs/tails_shutdown_debugging")
# The (guest) kernel may freeze when approaching full memory without
# adjusting the OOM killer and memory overcommitment limitations.
......@@ -92,6 +95,10 @@ Given /^I prepare Tails for memory erasure tests$/ do
free_mem_before_fill_m = @detected_ram_m - used_mem_before_fill_m -
kernel_mem_reserved_m - admin_mem_reserved_m
@free_mem_before_fill_b = convert_to_bytes(free_mem_before_fill_m, 'MiB')
['initramfs-shutdown', 'memlockd', 'tails-shutdown-on-media-removal'].each do |srv|
assert($vm.execute("systemctl status #{srv}.service").success?)
end
end
Given /^I fill the guest's memory with a known pattern and the allocating processes get killed$/ do
......@@ -179,6 +186,26 @@ Then /^patterns cover at least (\d+)% of the test FS size in the guest's memory$
"was expected")
end
Then(/^patterns cover at least (\d+) MiB in the guest's memory$/) do |expected_patterns_MiB|
reference_memory_b = convert_to_bytes(expected_patterns_MiB.to_i, 'MiB')
coverage = pattern_coverage_in_guest_ram(reference_memory_b)
min_coverage = 1
assert(coverage >= min_coverage,
"#{"%.3f" % (coverage*100)}% of the expected size (#{expected_patterns_MiB} MiB) " +
"has the pattern, but more than #{"%.3f" % (min_coverage*100)}% " +
"was expected")
end
Then(/^patterns cover less than (\d+) MiB in the guest's memory$/) do |expected_patterns_MiB|
reference_memory_b = convert_to_bytes(expected_patterns_MiB.to_i, 'MiB')
coverage = pattern_coverage_in_guest_ram(reference_memory_b)
max_coverage = 1
assert(coverage < max_coverage,
"#{"%.3f" % (coverage*100)}% of the expected size (#{expected_patterns_MiB} MiB) " +
"has the pattern, but less than #{"%.3f" % (max_coverage*100)}% " +
"was expected")
end
When(/^I umount "([^"]*)"$/) do |mount_arg|
$vm.execute_successfully("umount '#{mount_arg}'")
end
......@@ -191,33 +218,35 @@ Then /^I find very few patterns in the guest's memory$/ do
"pattern, but less than #{"%.3f" % (max_coverage*100)}% was expected")
end
When /^I reboot without wiping the memory$/ do
$vm.reset
end
When /^I stop the boot at the bootloader menu$/ do
step "Tails is at the boot menu's cmdline"
end
When /^I shutdown and wait for Tails to finish wiping the memory$/ do
$vm.spawn("halt")
match = nil
begin
try_for(memory_wipe_timeout, msg: "memory wipe didn't finish, probably the VM crashed") do
# We spam keypresses to prevent console blanking from hiding the
# image we're waiting for
@screen.type(" ")
match, _ = @screen.findAny(
['MemoryWipeCompleted.png', 'TailsBug11786a.png', 'TailsBug11786b.png']
)
match != nil
end
# Just throw the same exception as a if the try_for would fail
raise Timeout::Error if match != 'MemoryWipeCompleted.png'
rescue Timeout::Error
puts "Cannot tell if memory wipe completed. " +
"One possible reason for this is #11786, " +
"so let's go on and rely on the next steps to check " +
"how well memory was wiped."
When /^I wait for Tails to finish wiping the memory$/ do
@screen.wait("MemoryWipeCompleted.png", 30)
end
When(/^I fill a (\d+) MiB file with a known pattern on the (persistent|root) filesystem$/) do |size_MiB, fs|
pattern = "wipe_didnt_work\n"
pattern_nb = (convert_to_bytes(size_MiB.to_i, 'MiB') / pattern.size).floor
if fs == 'root'
dest_file = "/" + random_alpha_string(10)
elsif fs == 'persistent'
dest_file = "/home/amnesia/Persistent/" + random_alpha_string(10)
else
raise "This should not happen"
end
$vm.execute_successfully(
"for i in $(seq 1 #{pattern_nb}) ; do " +
" echo wipe_didnt_work >> '#{dest_file}' ; " +
"done"
)
end
When(/^I drop all kernel caches$/) do
$vm.execute_successfully("echo 3 > /proc/sys/vm/drop_caches")
end
When(/^I trigger shutdown$/) do
$vm.spawn("halt")
end
......@@ -174,6 +174,10 @@ class VM
end
def eject_cdrom
execute_successfully('/usr/bin/eject -m')
end
def remove_cdrom_image
domain_rexml = REXML::Document.new(@domain.xml_desc)
cdrom_el = domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
if cdrom_el.nil?
......@@ -195,7 +199,7 @@ class VM
if image.nil? or image == ''
raise "Can't set cdrom image to an empty string"
end
eject_cdrom
remove_cdrom_image
domain_rexml = REXML::Document.new(@domain.xml_desc)
cdrom_el = domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
cdrom_el.add_element('source', { 'file' => image })
......@@ -374,23 +378,6 @@ class VM
return list
end
def set_ram_size(size, unit = "KiB")
raise "System memory can only be added to inactive vms" if is_running?
domain_xml = REXML::Document.new(@domain.xml_desc)
domain_xml.elements['domain/memory'].text = size
domain_xml.elements['domain/memory'].attributes['unit'] = unit
domain_xml.elements['domain/currentMemory'].text = size
domain_xml.elements['domain/currentMemory'].attributes['unit'] = unit
update(domain_xml.to_s)
end
def get_ram_size_in_bytes
domain_xml = REXML::Document.new(@domain.xml_desc)
unit = domain_xml.elements['domain/memory'].attribute('unit').to_s
size = domain_xml.elements['domain/memory'].text.to_i
return convert_to_bytes(size, unit)
end
def set_os_loader(type)
if is_running?
raise "boot settings can only be set for inactive vms"
......
......@@ -21,6 +21,7 @@ AfterConfiguration do |config|
'features/time_syncing.feature',
'features/tor_bridges.feature',
# Features using large amounts of scratch space for other reasons:
'features/emergency_shutdown.feature',