Device passthrough and root checks

This commit is contained in:
AlexSSD7 2023-09-01 11:44:49 +01:00
commit 64d3891c48
5 changed files with 151 additions and 101 deletions

View file

@ -1,47 +0,0 @@
//go:build !windows
package vm
import (
"os/exec"
"path/filepath"
"strings"
"syscall"
"github.com/pkg/errors"
)
func prepareVMCmd(cmd *exec.Cmd) {
// This is to prevent Ctrl+C propagating to the child process.
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
}
func terminateProcess(pid int) error {
return syscall.Kill(-pid, syscall.SIGTERM)
}
// This is never used except for a band-aid that would check
// that there are no double-mounts.
func checkDeviceSeemsMounted(devPathPrefix string) (bool, error) {
// Quite a bit hacky implementation, but it's to be used as a failsafe band-aid anyway.
absDevPathPrefix, err := filepath.Abs(devPathPrefix)
if err != nil {
return false, errors.Wrap(err, "get abs path")
}
mounts, err := exec.Command("mount").Output()
if err != nil {
return false, errors.Wrap(err, "run mount command")
}
for _, line := range strings.Split(string(mounts), "\n") {
// I know, I know, this is a rare band-aid.
if strings.HasPrefix(line, devPathPrefix) || strings.HasPrefix(line, absDevPathPrefix) {
return true, nil
}
}
return false, nil
}

View file

@ -1,49 +0,0 @@
// go:build windows
package vm
import (
"fmt"
"os/exec"
"regexp"
"strings"
"syscall"
"github.com/pkg/errors"
)
func prepareVMCmd(cmd *exec.Cmd) {
// This is to prevent Ctrl+C propagating to the child process.
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
}
}
func terminateProcess(pid int) error {
return exec.Command("TASKKILL", "/T", "/F", "/PID", fmt.Sprint(pid)).Run()
}
var physicalDriveRegexp = regexp.MustCompile(`PhysicalDrive(\d+)`)
// This is never used except for a band-aid that would check
// that there are no double-mounts.
func checkDeviceSeemsMounted(path string) (bool, error) {
// Quite a bit hacky implementation, but it's to be used as a failsafe band-aid anyway.
matches := physicalDriveRegexp.FindAllStringSubmatch(path, 1)
if len(matches) == 0 {
return false, fmt.Errorf("bad device path '%v'", path)
}
match := matches[0]
if want, have := 2, len(match); want != have {
return false, fmt.Errorf("bad match items length: want %v, have %v (%v)", want, have, match)
}
out, err := exec.Command("wmic", "path", "Win32_LogicalDiskToPartition", "get", "Antecedent").Output()
if err != nil {
return false, errors.Wrap(err, "exec wmic cmd")
}
return strings.Contains(string(out), fmt.Sprintf("Disk #%v", match[1])), nil
}

View file

@ -19,6 +19,7 @@ import (
"log/slog"
"github.com/AlexSSD7/linsk/nettap"
"github.com/AlexSSD7/linsk/osspecifics"
"github.com/AlexSSD7/linsk/sshutil"
"github.com/AlexSSD7/linsk/utils"
"github.com/alessio/shellescape"
@ -115,9 +116,7 @@ func NewVM(logger *slog.Logger, cfg VMConfig) (*VM, error) {
var accel string
switch runtime.GOOS {
case "windows":
// TODO: whpx accel is broken in Windows. Long term solution looks to be use Hyper-V.
// For Windows, we need to install QEMU using an installer and add it to PATH.
// TODO: To document: For Windows, we need to install QEMU using an installer and add it to PATH.
// Then, we should enable Windows Hypervisor Platform in "Turn Windows features on or off".
// IMPORTANT: We should also install libusbK drivers for USB devices we want to pass through.
// This can be easily done with a program called Zadiag by Akeo.
@ -216,14 +215,14 @@ func NewVM(logger *slog.Logger, cfg VMConfig) (*VM, error) {
}
if len(cfg.PassthroughConfig.Block) != 0 {
logger.Warn("Detected raw block device passthrough. Please note that it's YOUR responsibility to ensure that no device is mounted in your OS and the VM at the same time. Otherwise, you run serious risks. No further warnings will be issued.")
logger.Warn("Using raw block device passthrough. Please note that it's YOUR responsibility to ensure that no device is mounted in your OS and the VM at the same time. Otherwise, you run serious risks. No further warnings will be issued.")
}
for _, dev := range cfg.PassthroughConfig.Block {
// It's always a user's responsibility to ensure that no drives are mounted
// in both host and guest system. This should serve as the last resort.
{
seemsMounted, err := checkDeviceSeemsMounted(dev.Path)
seemsMounted, err := osspecifics.CheckDeviceSeemsMounted(dev.Path)
if err != nil {
return nil, errors.Wrapf(err, "check whether device seems to be mounted (path '%v')", dev.Path)
}
@ -275,7 +274,7 @@ func NewVM(logger *slog.Logger, cfg VMConfig) (*VM, error) {
cmd.Stderr = stderrBuf
// This function is OS-specific.
prepareVMCmd(cmd)
osspecifics.SetNewProcessGroupCmd(cmd)
userReader := bufio.NewReader(userRead)
@ -456,7 +455,7 @@ func (vm *VM) Cancel() error {
if vm.cmd.Process == nil {
interruptErr = fmt.Errorf("process is not started")
} else {
interruptErr = terminateProcess(vm.cmd.Process.Pid)
interruptErr = osspecifics.TerminateProcess(vm.cmd.Process.Pid)
}
}
@ -582,7 +581,7 @@ func (vm *VM) runPeriodicHostMountChecker() {
return
case <-time.After(time.Second):
for _, dev := range vm.originalCfg.PassthroughConfig.Block {
seemsMounted, err := checkDeviceSeemsMounted(dev.Path)
seemsMounted, err := osspecifics.CheckDeviceSeemsMounted(dev.Path)
if err != nil {
vm.logger.Warn("Failed to check if a passed device seems to be mounted", "dev-path", dev.Path)
continue