Version in base suite: 6.0.4-2+deb13u7 Base version: incus_6.0.4-2+deb13u7 Target version: incus_6.0.4-2+deb13u8 Base file: /srv/ftp-master.debian.org/ftp/pool/main/i/incus/incus_6.0.4-2+deb13u7.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/i/incus/incus_6.0.4-2+deb13u8.dsc changelog | 15 +++ patches/125-CVE-2026-48756.patch | 36 +++++++ patches/126-CVE-2026-48749.patch | 65 +++++++++++++ patches/127-CVE-2026-48750.patch | 35 +++++++ patches/128-CVE-2026-48751.patch | 48 ++++++++++ patches/129-CVE-2026-48752.patch | 187 +++++++++++++++++++++++++++++++++++++++ patches/130-CVE-2026-48755.patch | 38 +++++++ patches/131-CVE-2026-48769.patch | 69 ++++++++++++++ patches/132-CVE-2026-55621.patch | 49 ++++++++++ patches/133-CVE-2026-55622.patch | 52 ++++++++++ patches/series | 9 + 11 files changed, 603 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpwrd7h_tu/incus_6.0.4-2+deb13u7.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpwrd7h_tu/incus_6.0.4-2+deb13u8.dsc: no acceptable signature found diff -Nru incus-6.0.4/debian/changelog incus-6.0.4/debian/changelog --- incus-6.0.4/debian/changelog 2026-05-01 01:13:25.000000000 +0000 +++ incus-6.0.4/debian/changelog 2026-06-25 00:53:11.000000000 +0000 @@ -1,3 +1,18 @@ +incus (6.0.4-2+deb13u8) trixie-security; urgency=high + + * Cherry-pick fixes for the following security issues: + - CVE-2026-48749 / GHSA-2q3f-q5pq-g8wv + - CVE-2026-48750 / GHSA-73hr-m85f-64v9 + - CVE-2026-48751 / GHSA-48q5-w887-33wv + - CVE-2026-48752 / GHSA-vxp5-584q-c479 + - CVE-2026-48755 / GHSA-v6mj-8pf4-hhw4 + - CVE-2026-48756 / GHSA-xhqx-mgh3-3h7q + - CVE-2026-48769 / GHSA-f6m5-xw2g-xc4x + - CVE-2026-55621 / GHSA-64f3-v33m-w89f + - CVE-2026-55622 / GHSA-c9f5-j9c3-mhrg + + -- Mathias Gibbens Thu, 25 Jun 2026 00:53:11 +0000 + incus (6.0.4-2+deb13u7) trixie-security; urgency=high * Cherry-pick fixes for the following security issues: diff -Nru incus-6.0.4/debian/patches/125-CVE-2026-48756.patch incus-6.0.4/debian/patches/125-CVE-2026-48756.patch --- incus-6.0.4/debian/patches/125-CVE-2026-48756.patch 1970-01-01 00:00:00.000000000 +0000 +++ incus-6.0.4/debian/patches/125-CVE-2026-48756.patch 2026-06-25 00:53:11.000000000 +0000 @@ -0,0 +1,36 @@ +From ab6b7dff0c770044875d9d26a6254a7075b4d00b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?St=C3=A9phane=20Graber?= +Date: Thu, 28 May 2026 17:37:33 -0400 +Subject: [PATCH] incusd/storage: Guard nil ExpiresAt in + CreateCustomVolumeFromBackup +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This addresses CVE-2026-48756 + +Signed-off-by: Stéphane Graber +--- + internal/server/storage/backend.go | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/internal/server/storage/backend.go b/internal/server/storage/backend.go +index cfe2c3e2b37..d917ffd23de 100644 +--- a/internal/server/storage/backend.go ++++ b/internal/server/storage/backend.go +@@ -7837,9 +7837,14 @@ func (b *backend) CreateCustomVolumeFromBackup(srcBackup backup.Info, srcData io + snapVolStorageName := project.StorageVolume(srcBackup.Project, fullSnapName) + snapVol := b.GetVolume(drivers.VolumeTypeCustom, drivers.ContentType(srcBackup.Config.Volume.ContentType), snapVolStorageName, snapshot.Config) + ++ var snapExpiryDate time.Time ++ if snapshot.ExpiresAt != nil { ++ snapExpiryDate = *snapshot.ExpiresAt ++ } ++ + // Validate config and create database entry for new storage volume. + // Strip unsupported config keys (in case the export was made from a different type of storage pool). +- err = VolumeDBCreate(b, srcBackup.Project, fullSnapName, snapshot.Description, snapVol.Type(), true, snapVol.Config(), snapshot.CreatedAt, *snapshot.ExpiresAt, snapVol.ContentType(), true, true) ++ err = VolumeDBCreate(b, srcBackup.Project, fullSnapName, snapshot.Description, snapVol.Type(), true, snapVol.Config(), snapshot.CreatedAt, snapExpiryDate, snapVol.ContentType(), true, true) + if err != nil { + return err + } diff -Nru incus-6.0.4/debian/patches/126-CVE-2026-48749.patch incus-6.0.4/debian/patches/126-CVE-2026-48749.patch --- incus-6.0.4/debian/patches/126-CVE-2026-48749.patch 1970-01-01 00:00:00.000000000 +0000 +++ incus-6.0.4/debian/patches/126-CVE-2026-48749.patch 2026-06-25 00:53:11.000000000 +0000 @@ -0,0 +1,65 @@ +From 873c8892cefdccf629f911253cfed3099ba76bb6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?St=C3=A9phane=20Graber?= +Date: Mon, 22 Jun 2026 14:12:43 -0400 +Subject: [PATCH 1/8] incusd: Reject rootfs symlink for instances +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This addresses CVE-2026-48749 + +Signed-off-by: Stéphane Graber +--- + internal/server/storage/backend.go | 8 ++++++++ + internal/server/storage/utils.go | 9 ++++++++- + 2 files changed, 16 insertions(+), 1 deletion(-) + +diff --git a/internal/server/storage/backend.go b/internal/server/storage/backend.go +index 55b32f985..1b402b56a 100644 +--- a/internal/server/storage/backend.go ++++ b/internal/server/storage/backend.go +@@ -835,6 +835,14 @@ func (b *backend) CreateInstanceFromBackup(srcBackup backup.Info, srcData io.Rea + + // Update information in the backup.yaml file. + err = vol.MountTask(func(mountPath string, op *operations.Operation) error { ++ // Reject a rootfs symlink which could redirect access to the host filesystem. ++ if volType == drivers.VolumeTypeContainer { ++ rootfsInfo, err := os.Lstat(filepath.Join(mountPath, "rootfs")) ++ if err == nil && !rootfsInfo.IsDir() { ++ return errors.New("Backup rootfs isn't a regular directory") ++ } ++ } ++ + return backup.UpdateInstanceConfig(b.state.DB.Cluster, srcBackup, mountPath) + }, op) + if err != nil { +diff --git a/internal/server/storage/utils.go b/internal/server/storage/utils.go +index 861e6e889..e75d971eb 100644 +--- a/internal/server/storage/utils.go ++++ b/internal/server/storage/utils.go +@@ -609,6 +609,12 @@ func ImageUnpack(imageFile string, vol drivers.Volume, destBlockFile string, sys + return -1, err + } + ++ // Reject a rootfs symlink which could redirect writes to the host filesystem. ++ rootfsInfo, err := os.Lstat(rootfsPath) ++ if err == nil && !rootfsInfo.IsDir() { ++ return -1, fmt.Errorf("Image rootfs isn't a regular directory: %s", imageFile) ++ } ++ + // Check for separate root file. + if util.PathExists(imageRootfsFile) { + err = os.MkdirAll(rootfsPath, 0o755) +@@ -623,7 +629,8 @@ func ImageUnpack(imageFile string, vol drivers.Volume, destBlockFile string, sys + } + + // Check that the container image unpack has resulted in a rootfs dir. +- if !util.PathExists(rootfsPath) { ++ rootfsInfo, err = os.Lstat(rootfsPath) ++ if err != nil || !rootfsInfo.IsDir() { + return -1, fmt.Errorf("Image is missing a rootfs: %s", imageFile) + } + +-- +2.47.3 + diff -Nru incus-6.0.4/debian/patches/127-CVE-2026-48750.patch incus-6.0.4/debian/patches/127-CVE-2026-48750.patch --- incus-6.0.4/debian/patches/127-CVE-2026-48750.patch 1970-01-01 00:00:00.000000000 +0000 +++ incus-6.0.4/debian/patches/127-CVE-2026-48750.patch 2026-06-25 00:53:11.000000000 +0000 @@ -0,0 +1,35 @@ +From dd38c2364b72ca55a7afd9692a7d2002d210596a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?St=C3=A9phane=20Graber?= +Date: Mon, 22 Jun 2026 14:12:43 -0400 +Subject: [PATCH 2/8] incusd/exec: Reject exec-output symlink +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This addresses CVE-2026-48750 + +Signed-off-by: Stéphane Graber +--- + cmd/incusd/instance_exec.go | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/cmd/incusd/instance_exec.go b/cmd/incusd/instance_exec.go +index 86d3bcbbf..a5126791a 100644 +--- a/cmd/incusd/instance_exec.go ++++ b/cmd/incusd/instance_exec.go +@@ -748,6 +748,12 @@ func instanceExecPost(d *Daemon, r *http.Request) response.Response { + return err + } + ++ // Reject a symlink which could redirect writes to the host filesystem. ++ execOutputInfo, err := os.Lstat(execOutputDir) ++ if err != nil || !execOutputInfo.IsDir() { ++ return errors.New("Exec output directory isn't a regular directory") ++ } ++ + // Prepare stdout and stderr recording. + stdout, err = os.OpenFile(filepath.Join(execOutputDir, fmt.Sprintf("exec_%s.stdout", op.ID())), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666) + if err != nil { +-- +2.47.3 + diff -Nru incus-6.0.4/debian/patches/128-CVE-2026-48751.patch incus-6.0.4/debian/patches/128-CVE-2026-48751.patch --- incus-6.0.4/debian/patches/128-CVE-2026-48751.patch 1970-01-01 00:00:00.000000000 +0000 +++ incus-6.0.4/debian/patches/128-CVE-2026-48751.patch 2026-06-25 00:53:11.000000000 +0000 @@ -0,0 +1,48 @@ +From a6c444cc113192a8263502a34844c383c68e2145 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?St=C3=A9phane=20Graber?= +Date: Mon, 22 Jun 2026 14:12:43 -0400 +Subject: [PATCH 3/8] incusd/instance: Enforce project restrictions on snapshot + restore +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This addresses CVE-2026-48751 + +Signed-off-by: Stéphane Graber +Rebased-by: Mathias Gibbens +--- + cmd/incusd/instance_put.go | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/cmd/incusd/instance_put.go b/cmd/incusd/instance_put.go +index 6c59e8cf0..1ff1ba4b5 100644 +--- a/cmd/incusd/instance_put.go ++++ b/cmd/incusd/instance_put.go +@@ -233,6 +233,23 @@ func instanceSnapRestore(s *state.State, projectName string, name string, snap s + } + source.SetOperation(op) + ++ // Ensure restoring the snapshot's config doesn't violate project restrictions. ++ profiles := make([]string, 0, len(source.Profiles())) ++ for _, profile := range source.Profiles() { ++ profiles = append(profiles, profile.Name) ++ } ++ ++ err = s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error { ++ return projecthelpers.AllowInstanceUpdate(tx, projectName, name, api.InstancePut{ ++ Config: source.LocalConfig(), ++ Devices: source.LocalDevices().CloneNative(), ++ Profiles: profiles, ++ }, inst.LocalConfig()) ++ }) ++ if err != nil { ++ return err ++ } ++ + // Generate a new `volatile.uuid.generation` to differentiate this instance restored from a snapshot from the original instance. + source.LocalConfig()["volatile.uuid.generation"] = uuid.New().String() + +-- +2.47.3 + diff -Nru incus-6.0.4/debian/patches/129-CVE-2026-48752.patch incus-6.0.4/debian/patches/129-CVE-2026-48752.patch --- incus-6.0.4/debian/patches/129-CVE-2026-48752.patch 1970-01-01 00:00:00.000000000 +0000 +++ incus-6.0.4/debian/patches/129-CVE-2026-48752.patch 2026-06-25 00:53:11.000000000 +0000 @@ -0,0 +1,187 @@ +From 5fb6ff4758083f4de3ccd69e7e08155a01d6ac72 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?St=C3=A9phane=20Graber?= +Date: Mon, 22 Jun 2026 14:12:43 -0400 +Subject: [PATCH 4/8] incusd/instance: Confine template access to instance root +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This addresses CVE-2026-48752 + +Signed-off-by: Stéphane Graber +Rebased-by: Mathias Gibbens +--- + cmd/incusd/instance_metadata.go | 77 +++++++++++++++++++++------------ + 1 file changed, 50 insertions(+), 27 deletions(-) + +diff --git a/cmd/incusd/instance_metadata.go b/cmd/incusd/instance_metadata.go +index 9a71a0a6b..f65d9fbcf 100644 +--- a/cmd/incusd/instance_metadata.go ++++ b/cmd/incusd/instance_metadata.go +@@ -2,8 +2,10 @@ package main + + import ( + "encoding/json" ++ "errors" + "fmt" + "io" ++ "io/fs" + "net/http" + "net/url" + "os" +@@ -469,18 +471,26 @@ func instanceMetadataTemplatesGet(d *Daemon, r *http.Request) response.Response + + defer func() { _ = storagePools.InstanceUnmount(pool, c, nil) }() + ++ // Confine all template access to the instance directory. ++ root, err := os.OpenRoot(c.Path()) ++ if err != nil { ++ return response.SmartError(err) ++ } ++ ++ defer func() { _ = root.Close() }() ++ + // Look at the request + templateName := r.FormValue("path") + if templateName == "" { + templates := []string{} +- if !util.PathExists(filepath.Join(c.Path(), "templates")) { +- return response.SyncResponse(true, templates) +- } + + // List templates +- templatesPath := filepath.Join(c.Path(), "templates") +- entries, err := os.ReadDir(templatesPath) ++ entries, err := fs.ReadDir(root.FS(), "templates") + if err != nil { ++ if errors.Is(err, fs.ErrNotExist) { ++ return response.SyncResponse(true, templates) ++ } ++ + return response.InternalError(err) + } + +@@ -494,19 +504,19 @@ func instanceMetadataTemplatesGet(d *Daemon, r *http.Request) response.Response + } + + // Check if the template exists +- templatePath, err := getContainerTemplatePath(c, templateName) ++ templatePath, err := getContainerTemplatePath(templateName) + if err != nil { + return response.SmartError(err) + } + +- if !util.PathExists(templatePath) { +- return response.NotFound(fmt.Errorf("Template %q not found", templateName)) +- } +- + // Create a temporary file with the template content (since the container + // storage might not be available when the file is read from FileResponse) +- template, err := os.Open(templatePath) ++ template, err := root.Open(templatePath) + if err != nil { ++ if errors.Is(err, fs.ErrNotExist) { ++ return response.NotFound(fmt.Errorf("Template %q not found", templateName)) ++ } ++ + return response.SmartError(err) + } + +@@ -621,27 +631,33 @@ func instanceMetadataTemplatesPost(d *Daemon, r *http.Request) response.Response + + defer func() { _ = storagePools.InstanceUnmount(pool, c, nil) }() + ++ // Confine all template access to the instance directory. ++ root, err := os.OpenRoot(c.Path()) ++ if err != nil { ++ return response.SmartError(err) ++ } ++ ++ defer func() { _ = root.Close() }() ++ + // Look at the request + templateName := r.FormValue("path") + if templateName == "" { + return response.BadRequest(fmt.Errorf("missing path argument")) + } + +- if !util.PathExists(filepath.Join(c.Path(), "templates")) { +- err := os.MkdirAll(filepath.Join(c.Path(), "templates"), 0o711) +- if err != nil { +- return response.SmartError(err) +- } ++ err = root.Mkdir("templates", 0o711) ++ if err != nil && !errors.Is(err, fs.ErrExist) { ++ return response.SmartError(err) + } + + // Check if the template already exists +- templatePath, err := getContainerTemplatePath(c, templateName) ++ templatePath, err := getContainerTemplatePath(templateName) + if err != nil { + return response.SmartError(err) + } + + // Write the new template +- template, err := os.OpenFile(templatePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644) ++ template, err := root.OpenFile(templatePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644) + if err != nil { + return response.SmartError(err) + } +@@ -740,24 +756,32 @@ func instanceMetadataTemplatesDelete(d *Daemon, r *http.Request) response.Respon + + defer func() { _ = storagePools.InstanceUnmount(pool, c, nil) }() + ++ // Confine all template access to the instance directory. ++ root, err := os.OpenRoot(c.Path()) ++ if err != nil { ++ return response.SmartError(err) ++ } ++ ++ defer func() { _ = root.Close() }() ++ + // Look at the request + templateName := r.FormValue("path") + if templateName == "" { + return response.BadRequest(fmt.Errorf("missing path argument")) + } + +- templatePath, err := getContainerTemplatePath(c, templateName) ++ templatePath, err := getContainerTemplatePath(templateName) + if err != nil { + return response.SmartError(err) + } + +- if !util.PathExists(templatePath) { +- return response.NotFound(fmt.Errorf("Template %q not found", templateName)) +- } +- + // Delete the template +- err = os.Remove(templatePath) ++ err = root.Remove(templatePath) + if err != nil { ++ if errors.Is(err, fs.ErrNotExist) { ++ return response.NotFound(fmt.Errorf("Template %q not found", templateName)) ++ } ++ + return response.InternalError(err) + } + +@@ -766,11 +790,11 @@ func instanceMetadataTemplatesDelete(d *Daemon, r *http.Request) response.Respon + return response.EmptySyncResponse + } + +-// Return the full path of a container template. +-func getContainerTemplatePath(c instance.Instance, filename string) (string, error) { ++// Return the template path relative to the instance root. ++func getContainerTemplatePath(filename string) (string, error) { + if strings.Contains(filename, "/") { + return "", fmt.Errorf("Invalid template filename") + } + +- return filepath.Join(c.Path(), "templates", filename), nil ++ return filepath.Join("templates", filename), nil + } +-- +2.47.3 + diff -Nru incus-6.0.4/debian/patches/130-CVE-2026-48755.patch incus-6.0.4/debian/patches/130-CVE-2026-48755.patch --- incus-6.0.4/debian/patches/130-CVE-2026-48755.patch 1970-01-01 00:00:00.000000000 +0000 +++ incus-6.0.4/debian/patches/130-CVE-2026-48755.patch 2026-06-25 00:53:11.000000000 +0000 @@ -0,0 +1,38 @@ +From 0b7b8c94a7c460649601ca97ad6520c757b2fc36 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?St=C3=A9phane=20Graber?= +Date: Mon, 22 Jun 2026 14:12:43 -0400 +Subject: [PATCH 5/8] shared/validate: Reject compression algorithm arguments +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This addresses CVE-2026-48755 + +Signed-off-by: Stéphane Graber +Rebased-by: Mathias Gibbens +--- + shared/validate/validate.go | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/shared/validate/validate.go b/shared/validate/validate.go +index 15dde0912..83e8f5e64 100644 +--- a/shared/validate/validate.go ++++ b/shared/validate/validate.go +@@ -590,6 +590,14 @@ func IsCompressionAlgorithm(value string) error { + return fmt.Errorf("Invalid compressor provided") + } + ++ // Only allow known-safe arguments (compression levels) to avoid argument injection. ++ allowedArgs := []string{"-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "-9", "--rsyncable"} ++ for _, arg := range fields[1:] { ++ if !slices.Contains(allowedArgs, arg) { ++ return fmt.Errorf("Compression algorithm argument %q isn't allowed", arg) ++ } ++ } ++ + // Check that we're dealing with a supported option. + if !slices.Contains([]string{ + "bzip2", +-- +2.47.3 + diff -Nru incus-6.0.4/debian/patches/131-CVE-2026-48769.patch incus-6.0.4/debian/patches/131-CVE-2026-48769.patch --- incus-6.0.4/debian/patches/131-CVE-2026-48769.patch 1970-01-01 00:00:00.000000000 +0000 +++ incus-6.0.4/debian/patches/131-CVE-2026-48769.patch 2026-06-25 00:53:11.000000000 +0000 @@ -0,0 +1,69 @@ +From 73641782901264148c46e8efe540f75fe0a17495 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?St=C3=A9phane=20Graber?= +Date: Mon, 22 Jun 2026 14:12:43 -0400 +Subject: [PATCH 6/8] incusd/images: Validate fingerprint on direct download +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This addresses CVE-2026-48769 + +Signed-off-by: Stéphane Graber +Rebased-by: Mathias Gibbens +--- + cmd/incusd/daemon_images.go | 10 ++++++++++ + shared/validate/validate.go | 10 ++++++++++ + 2 files changed, 20 insertions(+) + +diff --git a/cmd/incusd/daemon_images.go b/cmd/incusd/daemon_images.go +index 12d100485..9e9a84963 100644 +--- a/cmd/incusd/daemon_images.go ++++ b/cmd/incusd/daemon_images.go +@@ -28,6 +28,7 @@ import ( + "github.com/lxc/incus/v6/shared/logger" + "github.com/lxc/incus/v6/shared/units" + "github.com/lxc/incus/v6/shared/util" ++ "github.com/lxc/incus/v6/shared/validate" + ) + + // ImageDownloadArgs used with ImageDownload. +@@ -307,6 +308,15 @@ func ImageDownload(ctx context.Context, r *http.Request, s *state.State, op *ope + + logger.Info("Downloading image", ctxMap) + ++ // For the direct protocol the fingerprint is caller-controlled, so validate ++ // it to avoid path traversal when used as a file name. ++ if protocol == "direct" { ++ err = validate.IsSHA256(fp) ++ if err != nil { ++ return nil, false, fmt.Errorf("Invalid image fingerprint") ++ } ++ } ++ + // Cleanup any leftover from a past attempt + destDir := internalUtil.VarPath("images") + destName := filepath.Join(destDir, fp) +diff --git a/shared/validate/validate.go b/shared/validate/validate.go +index 63f4fbfe9..d2df3d7f2 100644 +--- a/shared/validate/validate.go ++++ b/shared/validate/validate.go +@@ -558,6 +558,16 @@ func IsUUID(value string) error { + return nil + } + ++// IsSHA256 validates whether a value is a SHA-256 hash in hex form. ++func IsSHA256(value string) error { ++ match, _ := regexp.MatchString(`^[0-9a-f]{64}$`, value) ++ if !match { ++ return fmt.Errorf("Invalid SHA-256 hash") ++ } ++ ++ return nil ++} ++ + // IsPCIAddress validates whether a value is a PCI address. + func IsPCIAddress(value string) error { + match, _ := regexp.MatchString(`^(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$`, value) +-- +2.47.3 + diff -Nru incus-6.0.4/debian/patches/132-CVE-2026-55621.patch incus-6.0.4/debian/patches/132-CVE-2026-55621.patch --- incus-6.0.4/debian/patches/132-CVE-2026-55621.patch 1970-01-01 00:00:00.000000000 +0000 +++ incus-6.0.4/debian/patches/132-CVE-2026-55621.patch 2026-06-25 00:53:11.000000000 +0000 @@ -0,0 +1,49 @@ +From 8cc0de3f77c61c9f13aa5b4f3f0ae1befc7cdbef Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?St=C3=A9phane=20Graber?= +Date: Mon, 22 Jun 2026 14:12:43 -0400 +Subject: [PATCH 7/8] incusd/storage: Check source volume access on copy +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This addresses CVE-2026-55621 + +Signed-off-by: Stéphane Graber +Rebased-by: Mathias Gibbens +--- + cmd/incusd/storage_volumes.go | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +diff --git a/cmd/incusd/storage_volumes.go b/cmd/incusd/storage_volumes.go +index 54c2e5254..9d6e0b3d3 100644 +--- a/cmd/incusd/storage_volumes.go ++++ b/cmd/incusd/storage_volumes.go +@@ -752,6 +752,25 @@ func storagePoolVolumesPost(d *Daemon, r *http.Request) response.Response { + case "": + return doVolumeCreateOrCopy(s, r, request.ProjectParam(r), projectName, poolName, &req) + case "copy": ++ // Check that the caller is allowed to view the source volume. ++ srcProjectName := projectName ++ if req.Source.Project != "" { ++ srcProjectName, err = project.StorageVolumeProject(s.DB.Cluster, req.Source.Project, db.StoragePoolVolumeTypeCustom) ++ if err != nil { ++ return response.SmartError(err) ++ } ++ } ++ ++ srcPoolName := req.Source.Pool ++ if srcPoolName == "" { ++ srcPoolName = poolName ++ } ++ ++ err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectStorageVolume(srcProjectName, srcPoolName, db.StoragePoolVolumeTypeNameCustom, req.Source.Name, req.Source.Location), auth.EntitlementCanView) ++ if err != nil { ++ return response.SmartError(err) ++ } ++ + if dbVolume != nil { + return doCustomVolumeRefresh(s, r, request.ProjectParam(r), projectName, poolName, &req) + } +-- +2.47.3 + diff -Nru incus-6.0.4/debian/patches/133-CVE-2026-55622.patch incus-6.0.4/debian/patches/133-CVE-2026-55622.patch --- incus-6.0.4/debian/patches/133-CVE-2026-55622.patch 1970-01-01 00:00:00.000000000 +0000 +++ incus-6.0.4/debian/patches/133-CVE-2026-55622.patch 2026-06-25 00:53:11.000000000 +0000 @@ -0,0 +1,52 @@ +From c9bc4330b41e252c7add3efbbe4d29548439f170 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?St=C3=A9phane=20Graber?= +Date: Mon, 22 Jun 2026 14:12:43 -0400 +Subject: [PATCH 8/8] incusd/instances: Check source instance access on copy +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This addresses CVE-2026-55622 + +Signed-off-by: Stéphane Graber +Rebased-by: Mathias Gibbens +--- + cmd/incusd/instances_post.go | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/cmd/incusd/instances_post.go b/cmd/incusd/instances_post.go +index 611078727..72a1e5b88 100644 +--- a/cmd/incusd/instances_post.go ++++ b/cmd/incusd/instances_post.go +@@ -16,6 +16,7 @@ import ( + + internalInstance "github.com/lxc/incus/v6/internal/instance" + internalIO "github.com/lxc/incus/v6/internal/io" ++ "github.com/lxc/incus/v6/internal/server/auth" + "github.com/lxc/incus/v6/internal/server/backup" + "github.com/lxc/incus/v6/internal/server/cluster" + "github.com/lxc/incus/v6/internal/server/db" +@@ -1040,6 +1041,20 @@ func instancesPost(d *Daemon, r *http.Request) response.Response { + return response.BadRequest(fmt.Errorf("Target only allowed when clustered")) + } + ++ // For a copy, check that the caller is allowed to view the source instance before any of its details are loaded. ++ if req.Source.Type == "copy" && req.Source.Source != "" { ++ sourceProject := req.Source.Project ++ if sourceProject == "" { ++ sourceProject = targetProjectName ++ } ++ ++ sourceName, _, _ := api.GetParentAndSnapshotName(req.Source.Source) ++ err = s.Authorizer.CheckPermission(r.Context(), r, auth.ObjectInstance(sourceProject, sourceName), auth.EntitlementCanView) ++ if err != nil { ++ return response.SmartError(err) ++ } ++ } ++ + err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error { + dbProject, err := dbCluster.GetProject(ctx, tx.Tx(), targetProjectName) + if err != nil { +-- +2.47.3 + diff -Nru incus-6.0.4/debian/patches/series incus-6.0.4/debian/patches/series --- incus-6.0.4/debian/patches/series 2026-05-01 01:13:25.000000000 +0000 +++ incus-6.0.4/debian/patches/series 2026-06-25 00:53:11.000000000 +0000 @@ -31,3 +31,12 @@ 122-CVE-2026-41648.patch 123-CVE-2026-41684.patch 124-CVE-2026-41685.patch +125-CVE-2026-48756.patch +126-CVE-2026-48749.patch +127-CVE-2026-48750.patch +128-CVE-2026-48751.patch +129-CVE-2026-48752.patch +130-CVE-2026-48755.patch +131-CVE-2026-48769.patch +132-CVE-2026-55621.patch +133-CVE-2026-55622.patch