diff --git a/images/virt-artifact/patches/028-hotplug-container-disk.patch b/images/virt-artifact/patches/028-hotplug-container-disk.patch index ac0cb3ae5d..9d27217744 100644 --- a/images/virt-artifact/patches/028-hotplug-container-disk.patch +++ b/images/virt-artifact/patches/028-hotplug-container-disk.patch @@ -45,7 +45,7 @@ index 0000000000..e4f734a516 + Name string `json:"name"` +} diff --git a/cmd/virt-chroot/main.go b/cmd/virt-chroot/main.go -index e28daa07c7..bf15bc0b6e 100644 +index e28daa07c7..7a69b7451b 100644 --- a/cmd/virt-chroot/main.go +++ b/cmd/virt-chroot/main.go @@ -20,6 +20,7 @@ var ( @@ -78,6 +78,69 @@ index e28daa07c7..bf15bc0b6e 100644 execCmd := &cobra.Command{ Use: "exec", +@@ -136,16 +143,39 @@ func main() { + Args: cobra.MinimumNArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + var mntOpts uint = 0 ++ var dataOpts []string + + fsType := cmd.Flag("type").Value.String() + mntOptions := cmd.Flag("options").Value.String() ++ var ( ++ uid = -1 ++ gid = -1 ++ ) + for _, opt := range strings.Split(mntOptions, ",") { + opt = strings.TrimSpace(opt) +- switch opt { +- case "ro": ++ switch { ++ case opt == "ro": + mntOpts = mntOpts | syscall.MS_RDONLY +- case "bind": ++ case opt == "bind": + mntOpts = mntOpts | syscall.MS_BIND ++ case opt == "remount": ++ mntOpts = mntOpts | syscall.MS_REMOUNT ++ case strings.HasPrefix(opt, "uid="): ++ uidS := strings.TrimPrefix(opt, "uid=") ++ uidI, err := strconv.Atoi(uidS) ++ if err != nil { ++ return fmt.Errorf("failed to parse uid: %w", err) ++ } ++ uid = uidI ++ dataOpts = append(dataOpts, opt) ++ case strings.HasPrefix(opt, "gid="): ++ gidS := strings.TrimPrefix(opt, "gid=") ++ gidI, err := strconv.Atoi(gidS) ++ if err != nil { ++ return fmt.Errorf("failed to parse gid: %w", err) ++ } ++ gid = gidI ++ dataOpts = append(dataOpts, opt) + default: + return fmt.Errorf("mount option %s is not supported", opt) + } +@@ -168,8 +198,17 @@ func main() { + return fmt.Errorf("mount target invalid: %v", err) + } + defer targetFile.Close() +- +- return syscall.Mount(sourceFile.SafePath(), targetFile.SafePath(), fsType, uintptr(mntOpts), "") ++ if uid >= 0 && gid >= 0 { ++ err = os.Chown(targetFile.SafePath(), uid, gid) ++ if err != nil { ++ return fmt.Errorf("chown target failed: %w", err) ++ } ++ } ++ var data string ++ if len(dataOpts) > 0 { ++ data = strings.Join(dataOpts, ",") ++ } ++ return syscall.Mount(sourceFile.SafePath(), targetFile.SafePath(), fsType, uintptr(mntOpts), data) + }, + } + mntCmd.Flags().StringP("options", "o", "", "comma separated list of mount options") diff --git a/manifests/generated/kv-resource.yaml b/manifests/generated/kv-resource.yaml index 66d1b01dbf..43e36b7195 100644 --- a/manifests/generated/kv-resource.yaml @@ -533,10 +596,10 @@ index 0c4bfca389..142f4400a6 100644 pvcName := storagetypes.PVCNameFromVirtVolume(&volume) diff --git a/pkg/virt-handler/container-disk/hotplug.go b/pkg/virt-handler/container-disk/hotplug.go new file mode 100644 -index 0000000000..b146792688 +index 0000000000..2785919ef0 --- /dev/null +++ b/pkg/virt-handler/container-disk/hotplug.go -@@ -0,0 +1,486 @@ +@@ -0,0 +1,487 @@ +package container_disk + +import ( @@ -549,8 +612,6 @@ index 0000000000..b146792688 + "sync" + "time" + -+ "k8s.io/utils/ptr" -+ + hotplugdisk "kubevirt.io/kubevirt/pkg/hotplug-disk" + "kubevirt.io/kubevirt/pkg/unsafepath" + @@ -821,9 +882,12 @@ index 0000000000..b146792688 + } + + log.DefaultLogger().Object(vmi).Infof("Bind mounting container disk at %s to %s", sourceFile, target) -+ out, err := virt_chroot.MountChrootWithOptions(sourceFile, target, true, ptr.To[int](107)).CombinedOutput() ++ opts := []string{ ++ "bind", "ro", "uid=107", "gid=107", ++ } ++ err = virt_chroot.MountChrootWithOptions(sourceFile, target, opts...) + if err != nil { -+ return nil, fmt.Errorf("failed to bindmount containerDisk %v: %v : %v", volume.Name, string(out), err) ++ return nil, fmt.Errorf("failed to bindmount containerDisk %v. err: %w", volume.Name, err) + } + } + // qemu-img: Could not open '/var/run/kubevirt/hotplug-disks/alpine.img': Could not open '/var/run/kubevirt/hotplug-disks/alpine.img': Permission denied @@ -1147,36 +1211,65 @@ index f83f96ead4..5e38c6cedd 100644 ufile, err := sock.(*net.UnixConn).File() if err != nil { diff --git a/pkg/virt-handler/virt-chroot/virt-chroot.go b/pkg/virt-handler/virt-chroot/virt-chroot.go -index 4160212b7b..a4f24bafa5 100644 +index 4160212b7b..580b788acc 100644 --- a/pkg/virt-handler/virt-chroot/virt-chroot.go +++ b/pkg/virt-handler/virt-chroot/virt-chroot.go -@@ -21,6 +21,7 @@ package virt_chroot +@@ -20,7 +20,10 @@ + package virt_chroot import ( ++ "bytes" ++ "fmt" "os/exec" -+ "strconv" ++ "slices" "strings" "kubevirt.io/kubevirt/pkg/safepath" -@@ -48,6 +49,23 @@ func MountChroot(sourcePath, targetPath *safepath.Path, ro bool) *exec.Cmd { +@@ -48,6 +51,49 @@ func MountChroot(sourcePath, targetPath *safepath.Path, ro bool) *exec.Cmd { return UnsafeMountChroot(trimProcPrefix(sourcePath), trimProcPrefix(targetPath), ro) } -+func MountChrootWithOptions(sourcePath, targetPath *safepath.Path, ro bool, switchUserID *int) *exec.Cmd { -+ args := append(getBaseArgs(), "mount", "-o") -+ -+ mountOptions := "bind" -+ if ro { -+ mountOptions = "ro," + mountOptions ++func MountChrootWithOptions(sourcePath, targetPath *safepath.Path, mountOptions ...string) error { ++ args := append(getBaseArgs(), "mount") ++ remountArgs := slices.Clone(args) ++ ++ mountOptions = slices.DeleteFunc(mountOptions, func(s string) bool { ++ return s == "remount" ++ }) ++ if len(mountOptions) > 0 { ++ opts := strings.Join(mountOptions, ",") ++ remountOpts := "remount," + opts ++ args = append(args, "-o", opts) ++ remountArgs = append(remountArgs, "-o", remountOpts) + } -+ args = append(args, mountOptions) + -+ if switchUserID != nil { -+ args = append(args, "--user", strconv.Itoa(*switchUserID)) ++ sp := trimProcPrefix(sourcePath) ++ tp := trimProcPrefix(targetPath) ++ args = append(args, sp, tp) ++ remountArgs = append(remountArgs, sp, tp) ++ ++ stdout := new(bytes.Buffer) ++ stderr := new(bytes.Buffer) ++ ++ cmd := exec.Command(binaryPath, args...) ++ cmd.Stdout = stdout ++ cmd.Stderr = stderr ++ err := cmd.Run() ++ if err != nil { ++ return fmt.Errorf("mount failed: %w, stdout: %s, stderr: %s", err, stdout.String(), stderr.String()) + } + -+ args = append(args, trimProcPrefix(sourcePath), trimProcPrefix(targetPath)) -+ return exec.Command(binaryPath, args...) ++ stdout = new(bytes.Buffer) ++ stderr = new(bytes.Buffer) ++ ++ remountCmd := exec.Command(binaryPath, remountArgs...) ++ cmd.Stdout = stdout ++ cmd.Stderr = stderr ++ err = remountCmd.Run() ++ if err != nil { ++ return fmt.Errorf("mount failed: %w, stdout: %s, stderr: %s", err, stdout.String(), stderr.String()) ++ } ++ return nil +} + // Deprecated: UnsafeMountChroot is used to connect to code which needs to be refactored