Package: release.debian.org
Severity: normal
Tags: trixie
User: [email protected]
Usertags: pu
X-Debbugs-Cc: [email protected], [email protected]
Control: affects -1 + src:incus

[ Reason ]
Last week's release of Incus 7.4 included fixes for two moderate
severity issues, CVE-2026-81500 and CVE-2026-81501. After discussion
with the Security Team, these vulnerabilities won't receive their own
DSA, but will be addressed via the upcoming point release.

[ Impact ]
Incus in trixie is currently vulnerable to CVE-2026-81500 and CVE-2026-
81501.

[ Tests ]
None -- both security issues are somewhat obscure edge cases, but the
fixes have been in the stable release for a week now and no regressions
have been reported upstream.

[ Risks ]
Minor/none -- two targeted fixes cherry-picked from the upstream git
repo.

[ Checklist ]
  [*] *all* changes are documented in the d/changelog
  [*] I reviewed all changes and I approve them
  [*] attach debdiff against the package in (old)stable
  [*] the issue is verified as fixed in unstable

[ Changes ]
Two security fixes as outlined above. Also updated d/changelog with
missing CVEs that hadn't been assigned by GitHub when the previous
release was uploaded.

[ Other info ]
The source debdiff is attached.
diff --git a/debian/changelog b/debian/changelog
index 438c618e46..8cc80a7c1f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+incus (6.0.4-2+deb13u10) trixie; urgency=medium
+
+  * Cherry-pick fixes for the following security issues
+    - CVE-2026-81500 / GHSA-9pqw-c7m4-xvg7
+    - CVE-2026-81501 / GHSA-c6wx-8679-hpr9
+
+ -- Mathias Gibbens <[email protected]>  Wed, 02 Sep 2026 16:39:29 +0000
+
 incus (6.0.4-2+deb13u9) trixie-security; urgency=high
 
   * Cherry-pick upstream fix for large nft ruleset performance
@@ -8,11 +16,11 @@ incus (6.0.4-2+deb13u9) trixie-security; urgency=high
     - CVE-2026-62941 / GHSA-mq9x-prm8-3vpw
     - CVE-2026-63125 / GHSA-6rqx-22hc-qm36
     - CVE-2026-63343 / GHSA-fmjx-5j3g-997p
-    - GHSA-26gp-p5fw-3r2h
-    - GHSA-4qxq-p5hm-3q3p
-    - GHSA-67qw-68v3-36h6
-    - GHSA-m3j6-p3v3-qmjv
-    - GHSA-p2v3-6wvc-cv3p
+    - CVE-2026-81493 / GHSA-p2v3-6wvc-cv3p
+    - CVE-2026-81495 / GHSA-67qw-68v3-36h6
+    - CVE-2026-81496 / GHSA-26gp-p5fw-3r2h
+    - CVE-2026-81497 / GHSA-4qxq-p5hm-3q3p
+    - CVE-2026-81498 / GHSA-m3j6-p3v3-qmjv
   * Cherry-pick four additional security fixes not assigned CVEs
 
  -- Mathias Gibbens <[email protected]>  Thu, 30 Jul 2026 22:57:51 +0000
diff --git a/debian/patches/147-CVE-2026-81500.patch b/debian/patches/147-CVE-2026-81500.patch
new file mode 100644
index 0000000000..217f0de519
--- /dev/null
+++ b/debian/patches/147-CVE-2026-81500.patch
@@ -0,0 +1,91 @@
+From d4d7badf6597274320b78575e77aec5720163c9c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?St=C3=A9phane=20Graber?= <[email protected]>
+Date: Sun, 23 Aug 2026 17:26:26 -0400
+Subject: [PATCH] client/images: Prevent path traversal in downloaded image
+ name
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The local filename for an exported image came from server-controlled
+data (Content-Disposition for unified images, the simplestreams index
+path) and was joined with the target directory. Basename it.
+
+This addresses GHSA-9pqw-c7m4-xvg7 (CVE pending)
+
+Signed-off-by: Stéphane Graber <[email protected]>
+Rebased-by: Mathias Gibbens <[email protected]>
+---
+ client/incus_images.go         |  4 +++-
+ client/simplestreams_images.go | 13 +++++++------
+ 2 files changed, 10 insertions(+), 7 deletions(-)
+
+diff --git a/client/incus_images.go b/client/incus_images.go
+index ed48d08be..3a0afccb2 100644
+--- a/client/incus_images.go
++++ b/client/incus_images.go
+@@ -9,6 +9,7 @@ import (
+ 	"net/http"
+ 	"net/url"
+ 	"os"
++	"path/filepath"
+ 	"slices"
+ 	"strings"
+ 	"time"
+@@ -325,7 +326,8 @@ func incusDownloadImage(fingerprint string, uri string, userAgent string, do fun
+ 	}
+ 
+ 	resp.MetaSize = size
+-	resp.MetaName = filename
++	// Basename the server-provided name to prevent path traversal.
++	resp.MetaName = filepath.Base(filename)
+ 
+ 	// Check the hash
+ 	hash := fmt.Sprintf("%x", sha256.Sum(nil))
+diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
+index 00cc35409..6bbfbe9d4 100644
+--- a/client/simplestreams_images.go
++++ b/client/simplestreams_images.go
+@@ -10,6 +10,7 @@ import (
+ 	"net/url"
+ 	"os"
+ 	"os/exec"
++	"path/filepath"
+ 	"strings"
+ 	"time"
+ 
+@@ -143,8 +144,8 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
+ 			return nil, err
+ 		}
+ 
+-		parts := strings.Split(meta.Path, "/")
+-		resp.MetaName = parts[len(parts)-1]
++		// Basename the server-provided name to prevent path traversal.
++		resp.MetaName = filepath.Base(meta.Path)
+ 		resp.MetaSize = size
+ 	}
+ 
+@@ -205,8 +206,8 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
+ 					return nil, err
+ 				}
+ 
+-				parts := strings.Split(rootfs.Path, "/")
+-				resp.RootfsName = parts[len(parts)-1]
++				// Basename the server-provided name to prevent path traversal.
++				resp.RootfsName = filepath.Base(rootfs.Path)
+ 				resp.RootfsSize = size
+ 				downloaded = true
+ 			}
+@@ -219,8 +220,8 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
+ 				return nil, err
+ 			}
+ 
+-			parts := strings.Split(rootfs.Path, "/")
+-			resp.RootfsName = parts[len(parts)-1]
++			// Basename the server-provided name to prevent path traversal.
++			resp.RootfsName = filepath.Base(rootfs.Path)
+ 			resp.RootfsSize = size
+ 		}
+ 	}
+-- 
+2.47.3
diff --git a/debian/patches/148-CVE-2026-81501.patch b/debian/patches/148-CVE-2026-81501.patch
new file mode 100644
index 0000000000..f05a56fb8b
--- /dev/null
+++ b/debian/patches/148-CVE-2026-81501.patch
@@ -0,0 +1,115 @@
+From a04abf23169d0597a544c6d96044a0f7aa9f19bf Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?St=C3=A9phane=20Graber?= <[email protected]>
+Date: Sun, 23 Aug 2026 17:26:26 -0400
+Subject: [PATCH] incusd/images: Check access before reusing cross-project
+ image
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+imageDownload reused an image from another project without checking the
+caller could view it, letting a client that knew a private fingerprint
+import it. Only reuse it directly when public or viewable, otherwise
+download it (proving access) and dedupe against the on-disk copy.
+
+This addresses GHSA-c6wx-8679-hpr9 (CVE pending)
+
+Signed-off-by: Stéphane Graber <[email protected]>
+Rebased-by: Mathias Gibbens <[email protected]>
+---
+ cmd/incusd/daemon_images.go  | 38 +++++++++++++++++++++++++++++++-----
+ internal/server/db/images.go |  1 +
+ 2 files changed, 34 insertions(+), 5 deletions(-)
+
+diff --git a/cmd/incusd/daemon_images.go b/cmd/incusd/daemon_images.go
+index 8eafb6de4..c26e1540e 100644
+--- a/cmd/incusd/daemon_images.go
++++ b/cmd/incusd/daemon_images.go
+@@ -14,6 +14,7 @@ import (
+ 
+ 	incus "github.com/lxc/incus/v6/client"
+ 	internalIO "github.com/lxc/incus/v6/internal/io"
++	"github.com/lxc/incus/v6/internal/server/auth"
+ 	"github.com/lxc/incus/v6/internal/server/db"
+ 	"github.com/lxc/incus/v6/internal/server/db/cluster"
+ 	"github.com/lxc/incus/v6/internal/server/locking"
+@@ -195,13 +196,37 @@ func ImageDownload(ctx context.Context, r *http.Request, s *state.State, op *ope
+ 			}
+ 		}
+ 	} else if response.IsNotFoundError(err) {
++		var otherImg *api.Image
+ 		err = s.DB.Cluster.Transaction(ctx, func(ctx context.Context, tx *db.ClusterTx) error {
+ 			// Check if the image already exists in some other project.
+-			_, imgInfo, err = tx.GetImageFromAnyProject(ctx, fp)
++			_, otherImg, err = tx.GetImageFromAnyProject(ctx, fp)
+ 
+ 			return err
+ 		})
+ 		if err == nil {
++			// Only reuse another project's image when the caller may see it,
++			// otherwise download it (proving access) and dedupe on disk.
++			reuse := otherImg.Public || r == nil
++			if !reuse {
++				err = s.Authorizer.CheckPermission(ctx, r, auth.ObjectImage(otherImg.Project, otherImg.Fingerprint), auth.EntitlementCanView)
++				if err == nil {
++					reuse = true
++				} else if !api.StatusErrorCheck(err, http.StatusForbidden) {
++					return nil, false, err
++				}
++
++				err = nil
++			}
++
++			if reuse {
++				imgInfo = otherImg
++			} else if args.Server == "" {
++				// No source to prove access against.
++				return nil, false, api.StatusErrorf(http.StatusNotFound, "Image not found")
++			}
++		}
++
++		if err == nil && imgInfo != nil {
+ 			var nodeAddress string
+ 
+ 			err = s.DB.Cluster.Transaction(ctx, func(ctx context.Context, tx *db.ClusterTx) error {
+@@ -317,9 +342,9 @@ func ImageDownload(ctx context.Context, r *http.Request, s *state.State, op *ope
+ 		return nil, false, fmt.Errorf("Invalid image fingerprint")
+ 	}
+ 
+-	// Cleanup any leftover from a past attempt
++	// Download to a temporary name so an existing on-disk copy isn't overwritten.
+ 	destDir := internalUtil.VarPath("images")
+-	destName := filepath.Join(destDir, fp)
++	destName := filepath.Join(destDir, fp+".download")
+ 
+ 	failure := true
+ 	cleanup := func() {
+@@ -576,9 +601,12 @@ func ImageDownload(ctx context.Context, r *http.Request, s *state.State, op *ope
+ 		return nil, false, fmt.Errorf("Invalid image fingerprint")
+ 	}
+ 
+-	// Check if the image path changed (private images)
++	// Reuse an existing on-disk copy if present, otherwise move ours into place.
+ 	newDestName := filepath.Join(destDir, fp)
+-	if newDestName != destName {
++	if util.PathExists(newDestName) {
++		_ = os.Remove(destName)
++		_ = os.Remove(destName + ".rootfs")
++	} else {
+ 		err = internalUtil.FileMove(destName, newDestName)
+ 		if err != nil {
+ 			return nil, false, err
+diff --git a/internal/server/db/images.go b/internal/server/db/images.go
+index 2a730e596..b3719becf 100644
+--- a/internal/server/db/images.go
++++ b/internal/server/db/images.go
+@@ -436,6 +436,7 @@ func (c *ClusterTx) GetImageFromAnyProject(ctx context.Context, fingerprint stri
+ 
+ 	object = images[0]
+ 
++	image.Project = object.Project
+ 	image.Fingerprint = object.Fingerprint
+ 	image.Filename = object.Filename
+ 	image.Size = object.Size
+-- 
+2.47.3
diff --git a/debian/patches/series b/debian/patches/series
index 45d2cab03b..f04af2fc4f 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -54,3 +54,5 @@
 144-GHSA-m3j6-p3v3-qmjv.patch
 145-GHSA-p2v3-6wvc-cv3p.patch
 146-incus-7.3-fixes.patch
+147-CVE-2026-81500.patch
+148-CVE-2026-81501.patch

Attachment: signature.asc
Description: This is a digitally signed message part

Reply via email to