123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- def udev_watchdog_monitored_device
- ps_output = $vm.execute_successfully('ps -wweo cmd').stdout
- udev_watchdog_cmd = '/usr/local/sbin/udev-watchdog'
- # The regex below looks for a line like the following:
- # /usr/local/sbin/udev-watchdog /devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/block/sr0 cd
- # We're only interested in the device itself, not in the type
- ps_output_scan = ps_output.scan(/^#{Regexp.escape(udev_watchdog_cmd)}\s(\S+)\s(?:cd|disk)$/)
- assert_equal(ps_output_scan.count, 1, "There should be one udev-watchdog running.")
- monitored_out = ps_output_scan.flatten[0]
- assert(!monitored_out.nil?)
- monitored_device_id = $vm.file_content('/sys' + monitored_out + '/dev').chomp
- monitored_device =
- $vm.execute_successfully(
- "readlink -f /dev/block/'#{monitored_device_id}'").stdout.chomp
- return monitored_device
- end
- Given /^udev-watchdog is monitoring the correct device$/ do
- assert_equal(udev_watchdog_monitored_device, boot_device)
- end
- Given /^the computer is a modern 64-bit system$/ do
- $vm.set_arch("x86_64")
- $vm.drop_hypervisor_feature("nonpae")
- $vm.add_hypervisor_feature("pae")
- end
- Given /^the computer is an old pentium without the PAE extension$/ do
- $vm.set_arch("i686")
- $vm.drop_hypervisor_feature("pae")
- # libvirt claim the following feature doesn't exit even though
- # it's listed in the hvm i686 capabilities...
- # $vm.add_hypervisor_feature("nonpae")
- # ... so we use a workaround until we can figure this one out.
- $vm.disable_pae_workaround
- end
- def which_kernel
- kernel_path = $vm.execute_successfully("tails-get-bootinfo kernel").stdout.chomp
- return File.basename(kernel_path)
- end
- Given /^the PAE kernel is running$/ do
- kernel = which_kernel
- assert_equal("vmlinuz2", kernel)
- end
- Given /^the non-PAE kernel is running$/ do
- kernel = which_kernel
- assert_equal("vmlinuz", kernel)
- end
- def used_ram_in_MiB
- return $vm.execute_successfully("free -m | awk '/^Mem:/ { print $3 }'").stdout.chomp.to_i
- end
- def detected_ram_in_MiB
- return $vm.execute_successfully("free -m | awk '/^Mem:/ { print $2 }'").stdout.chomp.to_i
- end
- Given /^at least (\d+) ([[:alpha:]]+) of RAM was detected$/ do |min_ram, unit|
- @detected_ram_m = detected_ram_in_MiB
- puts "Detected #{@detected_ram_m} MiB of RAM"
- min_ram_m = convert_to_MiB(min_ram.to_i, unit)
- # All RAM will not be reported by `free`, so we allow a 196 MB gap
- gap = convert_to_MiB(256, "MiB")
- assert(@detected_ram_m + gap >= min_ram_m, "Didn't detect enough RAM")
- end
- def pattern_coverage_in_guest_ram
- assert_not_nil(
- @free_mem_before_fill_b,
- "@free_mem_before_fill_b is not set, probably the required 'I fill the " +
- "guest's memory ...' step was not run")
- free_mem_before_fill_m = convert_to_MiB(@free_mem_before_fill_b, 'b')
- dump = "#{$config["TMPDIR"]}/memdump"
- # Workaround: when dumping the guest's memory via core_dump(), libvirt
- # will create files that only root can read. We therefore pre-create
- # them with more permissible permissions, which libvirt will preserve
- # (although it will change ownership) so that the user running the
- # script can grep the dump for the fillram pattern, and delete it.
- if File.exist?(dump)
- File.delete(dump)
- end
- FileUtils.touch(dump)
- FileUtils.chmod(0666, dump)
- $vm.domain.core_dump(dump)
- patterns = IO.popen(['grep', '--text', '-c', 'wipe_didnt_work', dump]).gets.to_i
- File.delete dump
- # Pattern is 16 bytes long
- patterns_b = patterns*16
- patterns_m = convert_to_MiB(patterns_b, 'b')
- coverage = patterns_b.to_f/@free_mem_before_fill_b
- puts "Pattern coverage: #{"%.3f" % (coverage*100)}% (#{patterns_m} MiB " +
- "out of #{free_mem_before_fill_m} MiB initial free memory)"
- return coverage
- end
- Given /^I fill the guest's memory with a known pattern(| without verifying)$/ do |dont_verify|
- verify = dont_verify.empty?
- # Free some more memory by dropping the caches etc.
- $vm.execute_successfully("echo 3 > /proc/sys/vm/drop_caches")
- # The (guest) kernel may freeze when approaching full memory without
- # adjusting the OOM killer and memory overcommitment limitations.
- kernel_mem_reserved_k = 64*1024 # Duplicated in /usr/share/initramfs-tools/scripts/init-premount/memory_wipe
- kernel_mem_reserved_m = convert_to_MiB(kernel_mem_reserved_k, 'k')
- admin_mem_reserved_k = 128*1024 # Duplicated in /usr/share/initramfs-tools/scripts/init-premount/memory_wipe
- admin_mem_reserved_m = convert_to_MiB(admin_mem_reserved_k, 'k')
- kernel_mem_settings = [
- # Let's avoid killing other random processes, and instead focus on
- # the hoggers, which will be our fillram instances.
- ["vm.oom_kill_allocating_task", 0],
- # Let's not print stuff to the terminal.
- ["vm.oom_dump_tasks", 0],
- # From tests the 'guess' heuristic seems to allow us to safely
- # (i.e. no kernel freezes) fill the maximum amount of RAM.
- ["vm.overcommit_memory", 0],
- # Make sure the kernel doesn't starve...
- ["vm.min_free_kbytes", kernel_mem_reserved_k],
- # ... and also some core privileged processes, e.g. the remote
- # shell.
- ["vm.admin_reserve_kbytes", admin_mem_reserved_k],
- ]
- kernel_mem_settings.each do |key, val|
- $vm.execute_successfully("sysctl #{key}=#{val}")
- end
- # We exclude the memory we reserve for the kernel and admin
- # processes above from the free memory since fillram will be run by
- # an unprivileged user in user-space.
- used_mem_before_fill_m = used_ram_in_MiB
- 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')
- # To be sure that we fill all memory we run one fillram instance for
- # each GiB of detected memory, rounded up. To maintain stability we
- # prioritize the fillram instances to be OOM killed. We also kill
- # all instances after the first one has finished, i.e. when the
- # memory is full, since the others otherwise may continue re-filling
- # the same memory unnecessarily. Note that we leave the `killall`
- # call outside of the OOM adjusted shell so it will not be OOM
- # killed too.
- nr_instances = (@detected_ram_m.to_f/(2**10)).ceil
- nr_instances.times do
- oom_adjusted_fillram_cmd =
- "echo 1000 > /proc/$$/oom_score_adj && exec /usr/local/sbin/fillram"
- $vm.spawn("sh -c '#{oom_adjusted_fillram_cmd}'; killall fillram",
- :user => LIVE_USER)
- end
- # We make sure that all fillram processes have started...
- try_for(10, :msg => "all fillram processes didn't start", :delay => 0.1) do
- nr_fillram_procs = $vm.pidof("fillram").size
- nr_instances == nr_fillram_procs
- end
- prev_used_ram_ratio = -1
- # ... and that it finishes
- try_for(nr_instances*2*60, { :msg => "fillram didn't complete, probably the VM crashed" }) do
- used_ram_ratio = (used_ram_in_MiB.to_f/@detected_ram_m)*100
- # Round down to closest multiple of 10 to limit the logging a bit.
- used_ram_ratio = (used_ram_ratio/10).round*10
- if used_ram_ratio - prev_used_ram_ratio >= 10
- debug_log("Memory fill progress: %3d%%" % used_ram_ratio)
- prev_used_ram_ratio = used_ram_ratio
- end
- ! $vm.has_process?("fillram")
- end
- debug_log("Memory fill progress: finished")
- if verify
- coverage = pattern_coverage_in_guest_ram()
- min_coverage = 0.90
- assert(coverage > min_coverage,
- "#{"%.3f" % (coverage*100)}% of the free memory was filled with " +
- "the pattern, but more than #{"%.3f" % (min_coverage*100)}% was " +
- "expected")
- end
- end
- Then /^I find very few patterns in the guest's memory$/ do
- coverage = pattern_coverage_in_guest_ram()
- max_coverage = 0.008
- assert(coverage < max_coverage,
- "#{"%.3f" % (coverage*100)}% of the free memory still has the " +
- "pattern, but less than #{"%.3f" % (max_coverage*100)}% was expected")
- end
- Then /^I find many patterns in the guest's memory$/ do
- coverage = pattern_coverage_in_guest_ram()
- min_coverage = 0.9
- assert(coverage > min_coverage,
- "#{"%.3f" % (coverage*100)}% of the free memory still has the " +
- "pattern, but more than #{"%.3f" % (min_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")
- 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(" ")
- @screen.find('MemoryWipeCompleted.png')
- end
- end
|