Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kubevirt1.8 for openSUSE:Factory checked in at 2026-10-22 15:50:54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kubevirt1.8 (Old) and /work/SRC/openSUSE:Factory/.kubevirt1.8.new.2252680 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kubevirt1.8" Thu Oct 22 15:50:54 2026 rev:8 rq:1386988 version:1.8.4 Changes: -------- --- /work/SRC/openSUSE:Factory/kubevirt1.8/kubevirt1.8.changes 2026-08-26 19:52:44.097647059 +0200 +++ /work/SRC/openSUSE:Factory/.kubevirt1.8.new.2252680/kubevirt1.8.changes 2026-10-22 15:54:47.230398203 +0200 @@ -11,0 +12,16 @@ +Mon Aug 3 23:14:03 UTC 2026 - Victor Carvalho <[email protected]> + +- Add 0003-use-safepath-when-dialing-the-migration-target-unix-sockets.patch: + CVE-2026-13622 (bsc#1272840) — + virt-handler dials the migration target unix sockets through + /proc/<pid>/root of the virt-launcher pod; resolve the socket path + with safepath and dial via ExecuteNoFollow so a symlink planted + inside the launcher filesystem cannot redirect the connection. + Unsupported target protocols are now rejected instead of dialed. +- Add 0004-tests-dial-migration-target-sockets-through-proc-root-layout.patch: + the migration-proxy unit tests dialed target sockets straight out of a + temporary directory, which is not below a /proc/<pid>/root and is + therefore refused by the resolution above; place those sockets under a + "root" directory so they keep the shape they have in production. + +------------------------------------------------------------------- New: ---- 0003-use-safepath-when-dialing-the-migration-target-unix-sockets.patch 0004-tests-dial-migration-target-sockets-through-proc-root-layout.patch ----------(New B)---------- New: - Add 0003-use-safepath-when-dialing-the-migration-target-unix-sockets.patch: CVE-2026-13622 (bsc#1272840) — New: Unsupported target protocols are now rejected instead of dialed. - Add 0004-tests-dial-migration-target-sockets-through-proc-root-layout.patch: the migration-proxy unit tests dialed target sockets straight out of a ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kubevirt1.8.spec ++++++ --- /var/tmp/diff_new_pack.8bpnaz/_old 2026-10-22 15:54:48.135436139 +0200 +++ /var/tmp/diff_new_pack.8bpnaz/_new 2026-10-22 15:54:48.136436181 +0200 @@ -32,6 +32,8 @@ Source3: %{url}/releases/download/v%{version}/disks-images-provider.yaml Patch0001: 0001-safepath-make-sure-whole-path-has-no-symlink.patch Patch0002: 0002-console-use-JoinNoFollow-for-console-socket-path.patch +Patch0003: 0003-use-safepath-when-dialing-the-migration-target-unix-sockets.patch +Patch0004: 0004-tests-dial-migration-target-sockets-through-proc-root-layout.patch Source100: %{name}-rpmlintrc BuildRequires: glibc-devel-static BuildRequires: golang-packaging ++++++ 0003-use-safepath-when-dialing-the-migration-target-unix-sockets.patch ++++++ >From ee94588903e1a6aa21af28d4e7ef1e8085ab916c Mon Sep 17 00:00:00 2001 From: Igor Bezukh <[email protected]> Date: Thu, 16 Jul 2026 13:10:51 +0300 Subject: [PATCH 1/2] use safepath when dialing the migration target unix sockets since the target socket file resides on virt-launcher, the handler constructs the path using /proc/<pid>/root. using safe path will keep the dial process protected from symlink attacks. Signed-off-by: Igor Bezukh <[email protected]> --- .../migration-proxy/migration-proxy.go | 76 ++++++++++++++++--- 1 file changed, 64 insertions(+), 12 deletions(-) diff --git a/pkg/virt-handler/migration-proxy/migration-proxy.go b/pkg/virt-handler/migration-proxy/migration-proxy.go index e7ab78a..6a4a42a 100644 --- a/pkg/virt-handler/migration-proxy/migration-proxy.go +++ b/pkg/virt-handler/migration-proxy/migration-proxy.go @@ -33,6 +33,7 @@ import ( "kubevirt.io/client-go/log" diskutils "kubevirt.io/kubevirt/pkg/ephemeral-disk-utils" + "kubevirt.io/kubevirt/pkg/safepath" "kubevirt.io/kubevirt/pkg/util" "kubevirt.io/kubevirt/pkg/util/net/ip" virtconfig "kubevirt.io/kubevirt/pkg/virt-config" @@ -443,22 +444,28 @@ func (m *migrationProxy) handleConnection(fd net.Conn) { inBoundErr := make(chan error, 1) var conn net.Conn - var err error + var dialErr error if m.targetProtocol == "tcp" && m.clientTLSConfig != nil { - conn, err = tls.Dial(m.targetProtocol, m.targetAddress, m.migrationTLSConfig) + conn, dialErr = tls.Dial(m.targetProtocol, m.targetAddress, m.migrationTLSConfig) // Check for specific error (CN missmatch), fallback to old client TLS - if err != nil { - m.logger.Reason(err).Info("fallback to old tls config") - conn, err = tls.Dial(m.targetProtocol, m.targetAddress, m.clientTLSConfig) + if dialErr != nil { + m.logger.Reason(dialErr).Info("fallback to old tls config") + conn, dialErr = tls.Dial(m.targetProtocol, m.targetAddress, m.clientTLSConfig) } else if tlsErr := conn.(*tls.Conn).Handshake(); tlsErr != nil { - m.logger.Reason(err).Info("handshake failed, fallback to old tls config") - conn, err = tls.Dial(m.targetProtocol, m.targetAddress, m.clientTLSConfig) + m.logger.Reason(tlsErr).Info("handshake failed, fallback to old tls config") + conn, dialErr = tls.Dial(m.targetProtocol, m.targetAddress, m.clientTLSConfig) } + } else if m.targetProtocol == "tcp" { + conn, dialErr = net.Dial(m.targetProtocol, m.targetAddress) + } else if m.targetProtocol == "unix" { + conn, dialErr = dialUnixNoFollow(m.targetAddress) } else { - conn, err = net.Dial(m.targetProtocol, m.targetAddress) + m.logger.Errorf("unsupported target protocol: %s", m.targetProtocol) + return } - if err != nil { - m.logger.Reason(err).Error("unable to create outbound leg of proxy to host") + + if dialErr != nil { + m.logger.Reason(dialErr).Errorf("unable to create outbound leg of proxy to host") return } @@ -476,11 +483,11 @@ func (m *migrationProxy) handleConnection(fd net.Conn) { }() select { - case err = <-outBoundErr: + case err := <-outBoundErr: if err != nil { m.logger.Reason(err).Errorf("error encountered copying data to outbound connection") } - case err = <-inBoundErr: + case err := <-inBoundErr: if err != nil { m.logger.Reason(err).Errorf("error encountered copying data into inbound connection") } @@ -489,6 +496,51 @@ func (m *migrationProxy) handleConnection(fd net.Conn) { } } +const procRootMarker = "/root/" + +type procRootPath struct { + rootBase string + elems []string +} + +func splitProcRootPath(path string) (*procRootPath, error) { + idx := strings.Index(path, procRootMarker) + if idx == -1 { + return nil, fmt.Errorf("path %q does not contain %q marker", path, procRootMarker) + } + rootBase := path[:idx+len("/root")] + relativePath := path[idx+len(procRootMarker):] + if relativePath == "" { + return nil, fmt.Errorf("path %q has empty relative path after %q marker", path, procRootMarker) + } + return &procRootPath{ + rootBase: rootBase, + elems: strings.Split(relativePath, "/"), + }, nil +} + +func dialUnixNoFollow(path string) (net.Conn, error) { + procPath, err := splitProcRootPath(path) + if err != nil { + return nil, err + } + safePath, err := safepath.JoinAndResolveWithRelativeRoot(procPath.rootBase, procPath.elems...) + if err != nil { + return nil, err + } + + var conn net.Conn + err = safePath.ExecuteNoFollow(func(safePath string) error { + var dialErr error + conn, dialErr = net.Dial("unix", safePath) + return dialErr + }) + if err != nil { + return nil, err + } + return conn, nil +} + func (m *migrationProxy) Start() error { if m.unixSocketPath != "" { ++++++ 0004-tests-dial-migration-target-sockets-through-proc-root-layout.patch ++++++ >From 6b82d6c38d82a74eea76161024b7af0cf33fdfa0 Mon Sep 17 00:00:00 2001 From: Victor Carvalho <[email protected]> Date: Tue, 4 Aug 2026 07:33:06 +0200 Subject: [PATCH 2/2] tests: dial migration target sockets through a /proc/<pid>/root layout The migration proxy resolves the target unix socket relative to the /proc/<pid>/root of the virt-launcher it belongs to, and rejects a path that is not below such a root. The unit tests dialed sockets straight out of a temporary directory, so the target proxy refused to connect and the tests blocked on a listener that never accepted. Place the sockets that act as proxy targets under a "root" directory inside the temporary directory. The paths then have the same shape as in production and exercise the same resolution. The source socket keeps its place: the source proxy listens on it and dials TCP, so it never reaches that code path. --- .../migration-proxy/migration-proxy_test.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pkg/virt-handler/migration-proxy/migration-proxy_test.go b/pkg/virt-handler/migration-proxy/migration-proxy_test.go index 50c5e70..56d929c 100644 --- a/pkg/virt-handler/migration-proxy/migration-proxy_test.go +++ b/pkg/virt-handler/migration-proxy/migration-proxy_test.go @@ -39,12 +39,18 @@ import ( var _ = Describe("MigrationProxy", func() { var tlsConfig *tls.Config var tmpDir string + var launcherRoot string BeforeEach(func() { var err error tmpDir, err = os.MkdirTemp("", "migrationproxytest") Expect(err).ToNot(HaveOccurred()) os.MkdirAll(tmpDir, 0755) + // virt-handler reaches the target sockets inside the virt-launcher + // filesystem through /proc/<pid>/root. Mirror that layout so the + // proxy resolves them exactly as it does in production. + launcherRoot = filepath.Join(tmpDir, "root") + Expect(os.MkdirAll(launcherRoot, 0755)).To(Succeed()) store, err := certificates.GenerateSelfSignedCert(tmpDir, "test", "test") ephemeraldiskutils.MockDefaultOwnershipManager() @@ -107,7 +113,7 @@ var _ = Describe("MigrationProxy", func() { It("by creating both ends and sending a message", func() { sourceSock := filepath.Join(tmpDir, "source-sock") - virtqemudSock := filepath.Join(tmpDir, "virtqemud-sock") + virtqemudSock := filepath.Join(launcherRoot, "virtqemud-sock") virtqemudListener, err := net.Listen("unix", virtqemudSock) Expect(err).ShouldNot(HaveOccurred()) @@ -151,10 +157,10 @@ var _ = Describe("MigrationProxy", func() { DescribeTable("by creating both ends with a manager and sending a message", func(migrationConfig *v1.MigrationConfiguration) { directMigrationPort := "49152" - virtqemudSock := filepath.Join(tmpDir, "virtqemud-sock") + virtqemudSock := filepath.Join(launcherRoot, "virtqemud-sock") virtqemudListener, err := net.Listen("unix", virtqemudSock) Expect(err).ShouldNot(HaveOccurred()) - directSock := filepath.Join(tmpDir, "mykey-"+directMigrationPort) + directSock := filepath.Join(launcherRoot, "mykey-"+directMigrationPort) directListener, err := net.Listen("unix", directSock) Expect(err).ShouldNot(HaveOccurred()) @@ -218,12 +224,12 @@ var _ = Describe("MigrationProxy", func() { key2 := "key2" directMigrationPort := "49152" - virtqemudSock := filepath.Join(tmpDir, "virtqemud-sock") + virtqemudSock := filepath.Join(launcherRoot, "virtqemud-sock") virtqemudListener, err := net.Listen("unix", virtqemudSock) Expect(err).ShouldNot(HaveOccurred()) defer virtqemudListener.Close() - directSock := filepath.Join(tmpDir, key1+"-"+directMigrationPort) + directSock := filepath.Join(launcherRoot, key1+"-"+directMigrationPort) directListener, err := net.Listen("unix", directSock) Expect(err).ShouldNot(HaveOccurred()) defer directListener.Close()
