Whonix live mode / amnesia / amnesic / non-persistent / anti-forensics

And this is exactly the case! The mount command always succeeds, if it can’t be mounted rw then it will be automatically mounted ro. One of the earlier versions of the script actually checked the exit code of the mount command but since it always succeeded it did not work. For my tests I just used an invalid disk identifier. In this case it really fails since there is obviously nothing to mount.

2 Likes

I am very cautious about this since this has potential to break the boot process or kernel package upgrade (initramfs part). Could you please have a look at other initramfs users so we don’t overlook some common practice here?


Does the script need to be idempotent?

If yes: mkdirmkdir -p


if [ -z “$(dmesg | grep “BIOS VirtualBox”)” ]; then
    echo ‘live_disk=$(blkid /dev/vda1 -o value -s UUID)’ >> /scripts/local
else
    echo ‘live_disk=$(blkid /dev/sda1 -o value -s UUID)’ >> /scripts/local
fi

Could you please add an elif for KVM and then the else if not detection succeeded, the script should continue with some sane default… Which would be what? Not affecting boot process and continuing normally?

Please echo which virtualizer was being detected to ease future debugging if required.


dmesg seems the wrong tool for that.

systemd-detect-virt is probably unavailable at that point in boot?

In that case let’s use virt-what / /usr/sbin/virt-what for that? initramfs has some API / config file to have such tools to be copied into initramfs. How to do so can probably be learned at looking at other initramfs users.


mount -t ext4 -n -o rw $ROOT /livetest

Is there a better way to test for read/write vs read-only other than using mkdir and mount Couldn’t we inquiry the kernel about it?


Is this a common way to check?

Might be better to parse /proc/mounts or something even better?

Anything useful in linux - Determine if filesystem or partition is mounted RO or RW via Bash Script? - Server Fault or related searches?


I would like to have this bulletproof even in case the output/format of the mount command or /proc/mounts or virt-what changes in future.

Sure if it somehow breaks then it breaks early in the boot process. But imho this not much different than the current version using grub. The script is based on other initramfs scripts. Since live mode is optional in 14 I’d keep it as already described on the Whonix live wiki page: make a backup of /boot or even better a VM snapshot. Potential errors can only be found if people actually test it.

I don’t see a reason why it should.

I can add the elif but there won’t be an else except maybe dropping to a shell. The part of the script you quoted only takes effect if the disk could not be mounted rw. This either means the user made it ro on the host on purpose or there is something very wrong with his disk. Therefore it also can’t continue to boot normally since with an ro disk you can’t boot normally.

Ok.

True.

Took a short look at it and it mostly seems to use dmiodecode or stuff in /proc. I can try to put it in the initrd and see how it behaves.

I think it doesn’t get any better than /proc/mounts or just mount. The output of both is more or less the same.

1 Like

Actually, nothing indicates that systemd-detect-virt depends on systemd being running. So it might also run independently.

Well, there are various initramfs scripts. Those used to create the initramfs and those running during initramfs are separated?

In case of false-positive ro detection boot could continue normally.

Some differences.

grub-live adds a new boot menu entry. The existing one is preserved. If that one isn’t used during boot, then the boot is just normal.

initramfs-live code runs every time for everyone. And in case of a bug there is only the debug shell to boot the system?

Documentation on how to boot from debug shell / how to manually boot from grub boot menu using alternative (working initramfs) would be useful.

I can also give it a try in addition to virt-what. Which one would you prefer?

I don’t think I get your point. Scripts for generation as well as those running at boot are scattered accross /etc/initramfs-tools, /usr/share/initramfs-tools, /usr/sbin, /lib/live/boot in case live-tools etc are installed, maybe in other places too.

How do you know if it is a false positive and can you come up with any scenario how a false positive could happen? If you know it is a false positive then you can also prevent the detection of false positives. As described in your link it breaks down to grepping the mount command or /proc/mount which is a fairly simple process. However, as we all know there is no 100% security.

Either this or the backup /boot. I can add instructions for that to the wiki. Of course you can also use somthing like chrooting into the system from another system and regenerating the initramfs but this is probably more complicated.

Algernon:

I can also give it a try in addition to virt-what. Which one would you prefer?

systemd-detect-virt preferred. More updated. Can look at code from
here:
https://github.com/Whonix/whonixcheck/blob/master/usr/lib/whonixcheck/check_virtualizer.bsh

I don’t think I get your point. Scripts for generation as well as those running at boot are scattered accross /etc/initramfs-tools, /usr/share/initramfs-tools, /usr/sbin, /lib/live/boot in case live-tools etc are installed, maybe in other places too.

Ok.

How do you know if it is a false positive and can you come up with any scenario how a false positive could happen?

If the format of /proc/mount changes after kernel upgrade.

If you know it is a false positive then you can also prevent the detection of false positives. As described in your link it breaks down to grepping the mount command or /proc/mount which is a fairly simple process. However, as we all know there is no 100% security.

Either this or the backup /boot. I can add instructions for that to the wiki. Of course you can also use somthing like chrooting into the system from another system and regenerating the initramfs but this is probably more complicated.

Sounds good!

(Sub page /Troubleshooting most likely.)

New version:

#!/bin/sh

set -e

case "${1}" in
        prereqs)
                exit 0
                ;;
esac

echo "Testing for live boot. "
mkdir /livetest
mount -t ext4 -n -o rw $ROOT /livetest
if [ -n "$(mount | grep "(ro,")" ]; then
echo "Mounting root read-write failed. Assuming live-mode. "
umount /livetest
	
	if [ !-x /usr/bin/systemd-detect-virt ]; then
	echo "systemd-detect-virt is missing. Dropping to a shell. " && /bin/sh
	fi
	
	if [ -n "$( systemd-detect-virt | grep "qemu")" ]; then
	echo 'live_disk=$(blkid /dev/vda1 -o value -s UUID)' >> /scripts/local
	echo "Detected KVM. "
	elif [ -n "$( systemd-detect-virt | grep "oracle")" ]; then
	echo 'live_disk=$(blkid /dev/sda1 -o value -s UUID)' >> /scripts/local
	echo "Detected VirtualBox. "
	else
	echo "Neither VirtualBox nor KVM detected. Live-boot failed. Dropping to a shell. " && /bin/sh
	fi

echo "BOOT=live" >> /scripts/local
echo 'LIVE_BOOT_CMDLINE="root=/dev/disk/by-uuid/$live_disk boot=live ip=frommedia plainroot union=overlay"' >> /scripts/local
else
echo "Filesystem can be mounted read-write. Proceeding normal boot. "
umount /livetest
fi
if [ -n "$(mount | grep "/livetest")" ]; then
echo "Unmounting the root disk during the live-test failed. Dropping to a shell ... " && /bin/sh
fi

exit 0

We also now require:

/etc/initramfs-tools/hooks/systemd-detect-virt with chmod +x

#!/bin/sh

set -e

case "${1}" in
        prereqs)
                exit 0
                ;;
esac

. /usr/share/initramfs-tools/hook-functions
copy_exec /usr/bin/systemd-detect-virt /usr/bin/systemd-detect-virt

The you still don’t know if the disk is rw or ro. If upstream suddenly changes something there is no way to predict the outcome. The only solution would be to track changes and adapt the script accordingly.

What happens when the only contents of the script would be this?

#!/bin/sh
set -e
exit 1

Does normal boot continue or stop boot?


  • mkdir -> mkdir -p - easily made idempotent, why not
  • missing space: !-x -> ! -x (fixed)
  • leading space: _" - Is this required? (removed for now)
  • grep -> grep -i for detect virt - ok?
  • if [ ! -x /usr/bin/systemd-detect-virt ]; then - probably not going to work because at that stage the root disk / isn’t mounted so /usr/bin/systemd-detect-virt can’t exist. Needs some initramfs hook to copy it into initramfs, I think.
#!/bin/sh

set -e

case "${1}" in
        prereqs)
                exit 0
                ;;
esac

output() {
   echo "live boot test: $@"
}

live_boot_test() {
   output "Testing for live boot."

   if [ ! -x /usr/bin/systemd-detect-virt ]; then
      output "systemd-detect-virt is missing."
      read_write=error
      return 0
   fi

   mkdir -p /livetest
   mount -t ext4 -n -o rw $ROOT /livetest

   if [ -n "$(mount | grep "(ro,")" ]; then
      output "Mounting root read-only detected. Assuming live mode."
      read_write=false
   elif [ -n "$(mount | grep "(rw,")" ]; then
      output "Mounting root read-write. Assuming persistent mode."
      read_write=true
   else
      output "Write mode detection failed."
      read_write=error
   fi

   if ! umount /livetest ; then
      output "Unmounting the root disk failed."
      read_write=error
      ## TODO: Should we exit 0 here or is non-zero more appropriate?
      exit 0
   fi

   if [ -n "$(mount | grep "/livetest")" ]; then
      output "Root disk still mounted after umount."
      read_write=error
      ## TODO: Should we exit 0 here or is non-zero more appropriate?
      exit 0
   fi
}

live_boot() {
   if [ -n "$( systemd-detect-virt | grep -i "qemu")" ]; then
      output "Detected KVM."
      echo 'live_disk=$(blkid /dev/vda1 -o value -s UUID)' >> /scripts/local

   elif [ -n "$( systemd-detect-virt | grep -i "oracle")" ]; then
      output "Detected VirtualBox."
      echo 'live_disk=$(blkid /dev/sda1 -o value -s UUID)' >> /scripts/local
   else
      output "Neither VirtualBox nor KVM detected."
      ## Don't live boot.
      return 0
   fi

   echo "BOOT=live" >> /scripts/local
   echo 'LIVE_BOOT_CMDLINE="root=/dev/disk/by-uuid/$live_disk boot=live ip=frommedia plainroot union=overlay"' >> /scripts/local

   exit 0
}

live_boot_test

if [ "$read_write" = "false" ]; then
   live_boot
fi

output "Proceeding persistent boot."

exit 0

Pure theory. What do you think about these changes? If ok, could you please test? If still ok, could you please add license header and add to git?

Some more theoretic enhancements. The /proc/mounts parser is tested on my running Debian system but not during actual initramfs. Enhanced the /proc/mounts parser to make sure if any other (user or distribution) initramfs script mounts anything, so we don’t accidentally match ro.

#!/bin/sh

set -e

case "${1}" in
        prereqs)
                exit 0
                ;;
esac

output() {
   echo "live boot test: $@"
}

## based on Stéphane Chazelas answer
## https://unix.stackexchange.com/a/417188
parse_mount_options() {
   set -o noglob # disable glob part
   IFS=","         # split on colon
   set -- $1     # split+glob
   first="$1"
   if [ "$first" = "rw" ]; then
      output "Read-write disk detected. Assuming persistent mode."
      read_write=true
   elif [ "$first" = "ro" ]; then
      output "Read-only disk detected. Assuming live mode."
      read_write=false
   else
      output "Write mode detection failed."
      read_write=error
   fi
}

## Taken from os-prober /usr/share/os-prober/common.sh and modified.
parse_proc_mounts () {
	while read -r line; do
		set -f
		set -- $line
		set +f
		true "line: $line"
		path="$1"
		mount_point="$2"
		file_system="$3"
		mount_options="$4"
		if [ "$path" = "/livetest" ]; then
         parse_mount_options "$mount_options"
         return 0
      fi
	done
}

live_boot_test() {
   output "Testing for live boot."

   if [ ! -x /usr/bin/systemd-detect-virt ]; then
      output "systemd-detect-virt is missing."
      read_write=error
      return 0
   fi

   mkdir -p /livetest
   mount -t ext4 -n -o rw $ROOT /livetest

   cat /proc/mounts | parse_proc_mounts

   if umount /livetest ; then
      rmdir --ignore-fail-on-non-empty /livetest
   else
      output "Unmounting the root disk failed."
      read_write=error
   fi

   if [ -n "$(cat /proc/mounts | grep "/livetest")" ]; then
      output "Root disk still mounted after umount."
      read_write=error
   fi
}

live_boot() {
   if [ -n "$( systemd-detect-virt | grep -i "qemu")" ]; then
      output "Detected KVM."
      echo 'live_disk=$(blkid /dev/vda1 -o value -s UUID)' >> /scripts/local

   elif [ -n "$( systemd-detect-virt | grep -i "oracle")" ]; then
      output "Detected VirtualBox."
      echo 'live_disk=$(blkid /dev/sda1 -o value -s UUID)' >> /scripts/local
   else
      output "Neither VirtualBox nor KVM detected."
      ## Don't live boot.
      return 0
   fi

   echo "BOOT=live" >> /scripts/local
   echo 'LIVE_BOOT_CMDLINE="root=/dev/disk/by-uuid/$live_disk boot=live ip=frommedia plainroot union=overlay"' >> /scripts/local

   exit 0
}

live_boot_test

if [ "$read_write" = "error" ]; then
   output "Contents of /proc/mounts:"
   cat /proc/mounts
   ## TODO: Should we exit 0 here or is non-zero more appropriate?
   exit 0
fi

if [ "$read_write" = "false" ]; then
   live_boot
fi

output "Proceeding persistent boot."

exit 0

Boots as always.

Not really. Looks imho sometimes better when reading stuff from the commandline.

Yes.

Yes. See previous post.

Your last script does not work for me. It works for normal boot but fails when the disk is set ro. Either “/livetest” needs to be set to “/dev/vda1” or $mount_point needs to be used instead of $path.
When changed accordingly the proc parser generally works but the read_write variable somehow is not set in the live_boot_test function. Not sure why it gets lost. In this case live boot does not work with a ro disk.
While theoretically failsafe the rmdir is not really required. I’d just leave the directory there since it is just in the initramfs.
We don’t drop to a shell in case of errors. I have to think about if is easier for debugging to do that. Alternative would be to set “debug” and “break” on the kernel command line in case something goes wrong.

Algernon:

Not really. Looks imho sometimes better when reading stuff from the commandline.

Well, if it looks better then it’s “required”.

Your last script does not work for me. It works for normal boot but fails when the disk is set ro. Either “/livetest” needs to be set to “/dev/vda1” or $mount_point needs to be used instead of $path.

Fixed, set to mount_point. Please check.

When changed accordingly the proc parser generally works but the read_write variable somehow is not set in the live_boot_test function. Not sure why it gets lost. In this case live boot does not work with a ro disk.

I’ll post a version with debugging enabled. Maybe that helps to idenitfy
the error. Please post the debug output here if not.

While theoretically failsafe the rmdir is not really required. I’d just leave the directory there since it is just in the initramfs.

Ok.

We don’t drop to a shell in case of errors. I have to think about if is easier for debugging to do that.

Alternative would be to set “debug” and “break” on the kernel command
line in case something goes wrong.

Yes, perhaps is livedebug=1 (or so) is set as kernel command line, drop
to shell from initramfs-live?

#!/bin/sh

set -x

set -e

case "${1}" in
        prereqs)
                exit 0
                ;;
esac

output() {
   echo "live boot test: $@"
}

## based on Stéphane Chazelas answer
## https://unix.stackexchange.com/a/417188
parse_mount_options() {
   set -o noglob # disable glob part
   IFS=","       # split on colon
   set -- $1     # split+glob
   first="$1"
   if [ "$first" = "rw" ]; then
      output "Read-write disk detected. Assuming persistent mode."
      read_write=true
   elif [ "$first" = "ro" ]; then
      output "Read-only disk detected. Assuming live mode."
      read_write=false
   else
      output "Write mode detection failed."
      read_write=error
   fi
}

## Taken from os-prober /usr/share/os-prober/common.sh and modified.
parse_proc_mounts () {
	while read -r line; do
		set -f
		set -- $line
		set +f
		true "line: $line"
		path="$1"
		mount_point="$2"
		file_system="$3"
		mount_options="$4"
		if [ "$mount_point" = "/livetest" ]; then
         parse_mount_options "$mount_options"
         return 0
      fi
	done
}

live_boot_test() {
   output "Testing for live boot."

   if [ ! -x /usr/bin/systemd-detect-virt ]; then
      output "systemd-detect-virt is missing."
      read_write=error
      return 0
   fi

   mkdir -p /livetest
   mount -t ext4 -n -o rw $ROOT /livetest

   cat /proc/mounts | parse_proc_mounts

   if ! umount /livetest ; then
      output "Unmounting the root disk failed."
      read_write=error
   fi

   if [ -n "$(cat /proc/mounts | grep "/livetest")" ]; then
      output "Root disk still mounted after umount."
      read_write=error
   fi
}

live_boot() {
   if [ -n "$(systemd-detect-virt | grep -i "qemu")" ]; then
      output "Detected KVM."
      echo 'live_disk=$(blkid /dev/vda1 -o value -s UUID)' >> /scripts/local
   elif [ -n "$(systemd-detect-virt | grep -i "oracle")" ]; then
      output "Detected VirtualBox."
      echo 'live_disk=$(blkid /dev/sda1 -o value -s UUID)' >> /scripts/local
   else
      output "Neither VirtualBox nor KVM detected."
      ## Don't live boot.
      return 0
   fi

   echo "BOOT=live" >> /scripts/local
   echo 'LIVE_BOOT_CMDLINE="root=/dev/disk/by-uuid/$live_disk boot=live ip=frommedia plainroot union=overlay"' >> /scripts/local

   exit 0
}

true "Setting fallback read_write=error."
read_write=error

live_boot_test

if [ "$read_write" = "error" ]; then
   output "Contents of /proc/mounts:"
   cat /proc/mounts
   ## TODO: Should we exit 0 here or is non-zero more appropriate?
   exit 0
fi

if [ "$read_write" = "false" ]; then
   live_boot
fi

output "Proceeding persistent boot."

exit 0
1 Like

Ok the issue was the pipe see here: http://www.linuxprogrammingblog.com/pipe-in-bash-can-be-a-trap
Below script now works. If there are no further issues I will try to build the deb package and see how it behaves. If all goes well I’ll update the package on github and the wiki page.

#!/bin/sh

set -e

case "${1}" in
        prereqs)
                exit 0
                ;;
esac

output() {
   echo "live boot test: $@"
}

## based on Stéphane Chazelas answer
## https://unix.stackexchange.com/a/417188
parse_mount_options() {
   set -o noglob # disable glob part
   IFS=","       # split on colon
   set -- $1     # split+glob
   first="$1"
   if [ "$first" = "rw" ]; then
      output "Read-write disk detected. Assuming persistent mode."
      read_write=true
   elif [ "$first" = "ro" ]; then
      output "Read-only disk detected. Assuming live mode."
      read_write=false
   else
      output "Write mode detection failed."
      read_write=error
   fi
}

## Taken from os-prober /usr/share/os-prober/common.sh and modified.
parse_proc_mounts () {
	while read -r line; do
		set -f
		set -- $line
		set +f
		true "line: $line"
		path="$1"
		mount_point="$2"
		file_system="$3"
		mount_options="$4"
		if [ "$mount_point" = "/livetest" ]; then
         parse_mount_options "$mount_options"
         return 0
      fi
	done < /proc/mounts
}

live_boot_test() {
   output "Testing for live boot."

   if [ ! -x /usr/bin/systemd-detect-virt ]; then
      output "systemd-detect-virt is missing."
      read_write=error
      return 0
   fi

   mkdir -p /livetest
   mount -t ext4 -n -o rw $ROOT /livetest

   parse_proc_mounts

   if ! umount /livetest ; then
      output "Unmounting the root disk failed."
      read_write=error
   fi

   if [ -n "$(cat /proc/mounts | grep "/livetest")" ]; then
      output "Root disk still mounted after umount."
      read_write=error
   fi
}

live_boot() {
   if [ -n "$(systemd-detect-virt | grep -i "qemu")" ]; then
      output "Detected KVM."
      echo 'live_disk=$(blkid /dev/vda1 -o value -s UUID)' >> /scripts/local
   elif [ -n "$(systemd-detect-virt | grep -i "oracle")" ]; then
      output "Detected VirtualBox."
      echo 'live_disk=$(blkid /dev/sda1 -o value -s UUID)' >> /scripts/local
   else
      output "Neither VirtualBox nor KVM detected."
      ## Don't live boot.
      return 0
   fi

   echo "BOOT=live" >> /scripts/local
   echo 'LIVE_BOOT_CMDLINE="root=/dev/disk/by-uuid/$live_disk boot=live ip=frommedia plainroot union=overlay"' >> /scripts/local

   exit 0
}

true "Setting fallback read_write=error."
read_write=error

live_boot_test

if [ "$read_write" = "error" ]; then
   output "Contents of /proc/mounts:"
   cat /proc/mounts
   ## TODO: Should we exit 0 here or is non-zero more appropriate?
   exit 0
fi

if [ "$read_write" = "false" ]; then
   live_boot
fi

output "Proceeding persistent boot."

exit 0
1 Like

Is it possible to use bash inside initramfs? I would very much like to add an error handling trap.

Perhaps leave GitHub - Kicksecure/grub-live: optional grub live boot menu entry as second option https://www.kicksecure.com/wiki/Grub-live as is? I think it’s a really cool implementation.

The project name grub-live no longer applies since it’s now initramfs based. Perhaps a new package? How to call it…? initramfs-live? Or better perhaps we can find a generic name that is independent from the implementation tails grub vs initramfs based? This is only useful inside virtual machines and doesn’t look like it will be useful on bare metal? That may influence naming. I guess this could be interesting for bare metal for disks which have a physical (or BIOS / firmware) read-only on/off switch.

1 Like

A few ideas…

  • read-only-live-boot
  • live-boot-auto-detection
  • live-boot-auto-detector
  • live-boot-detector

Could you please discuss this with upstream / try to upstream this?

What would be a good place to upstream this? A few ideas:

ephemeral-mode?

Actually since you want a more descript name, why not ro-mode-init?

Certainly not impossible, but would at least require an initramfs hook. However, I’d stick to what it is in there by default. Every other script in there also uses those programs.

I can do that. We need another name anyway. ro-mode-init would give a nice abbreviation, romi.

You could maybe use a fallback option for systemd-detect-virt in case it does not detect a hypervisor to automatically assume bare metal. Or you get rid of it at all. You could always parse the underlying stuff directly from /proc or /sys which more or less each virtualization detection tool does and virt-what even suggests in the man page. More recent versions of the kernel can see very early in the boot process if the disk is write protected which could maye also be used instead. It is under /sys/block/vda/ro or similar path depending on the disk driver.
I can try to contact upstream, though in its current form it is using some rather hacky workarounds to actually not need to change the upstream packages :wink: and is basically an end-user friendly version of adding “plainroot …” to the boot options. It makes some assumptions which are rather Whonix-specific and always relies on user interaction on the host to work as intended.

2 Likes