Version in base suite: 5.0.2+git20231211.1364ae4-9+deb13u3 Version in overlay suite: 5.0.2+git20231211.1364ae4-9+deb13u4 Base version: lxd_5.0.2+git20231211.1364ae4-9+deb13u4 Target version: lxd_5.0.2+git20231211.1364ae4-9+deb13u5 Base file: /srv/ftp-master.debian.org/ftp/pool/main/l/lxd/lxd_5.0.2+git20231211.1364ae4-9+deb13u4.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/l/lxd/lxd_5.0.2+git20231211.1364ae4-9+deb13u5.dsc changelog | 9 patches/110-CVE-2026-34177.patch | 50 ++ patches/111-CVE-2026-34178.patch | 669 +++++++++++++++++++++++++++++++++++++++ patches/112-CVE-2026-34179.patch | 109 ++++++ patches/series | 3 5 files changed, 840 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpoqn1v7cv/lxd_5.0.2+git20231211.1364ae4-9+deb13u4.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpoqn1v7cv/lxd_5.0.2+git20231211.1364ae4-9+deb13u5.dsc: no acceptable signature found diff -Nru lxd-5.0.2+git20231211.1364ae4/debian/changelog lxd-5.0.2+git20231211.1364ae4/debian/changelog --- lxd-5.0.2+git20231211.1364ae4/debian/changelog 2026-03-24 23:41:46.000000000 +0000 +++ lxd-5.0.2+git20231211.1364ae4/debian/changelog 2026-04-14 18:30:26.000000000 +0000 @@ -1,3 +1,12 @@ +lxd (5.0.2+git20231211.1364ae4-9+deb13u5) trixie-security; urgency=high + + * Cherry-pick fixes for the following security issues: + - CVE-2026-34177 / GHSA-fm2x-c5qw-4h6f + - CVE-2026-34178 / GHSA-q96j-3fmm-7fv4 + - CVE-2026-34179 / GHSA-c3h3-89qf-jqm5 + + -- Mathias Gibbens Tue, 14 Apr 2026 18:30:26 +0000 + lxd (5.0.2+git20231211.1364ae4-9+deb13u4) trixie-security; urgency=high * Cherry-pick fixes for the following security issues: diff -Nru lxd-5.0.2+git20231211.1364ae4/debian/patches/110-CVE-2026-34177.patch lxd-5.0.2+git20231211.1364ae4/debian/patches/110-CVE-2026-34177.patch --- lxd-5.0.2+git20231211.1364ae4/debian/patches/110-CVE-2026-34177.patch 1970-01-01 00:00:00.000000000 +0000 +++ lxd-5.0.2+git20231211.1364ae4/debian/patches/110-CVE-2026-34177.patch 2026-04-14 18:30:26.000000000 +0000 @@ -0,0 +1,50 @@ +From 0451314ab5b0ceb9d941831b0b578527eaac9094 Mon Sep 17 00:00:00 2001 +From: Din Music +Date: Thu, 19 Mar 2026 09:26:16 +0000 +Subject: [PATCH 1/2] lxd/project/limits: Add raw.apparmor and raw.qemu.conf to + the list of forbidden low level VM options + +Signed-off-by: Din Music +--- + lxd/project/permissions.go | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/lxd/project/permissions.go b/lxd/project/permissions.go +index a2024f0ccb0c..c2e400682d46 100644 +--- a/lxd/project/permissions.go ++++ b/lxd/project/permissions.go +@@ -847,8 +847,10 @@ func isVMLowLevelOptionForbidden(key string) bool { + return shared.StringInSlice(key, []string{ + "boot.host_shutdown_timeout", + "limits.memory.hugepages", ++ "raw.apparmor", + "raw.idmap", + "raw.qemu", ++ "raw.qemu.conf", + }) + } + + +From 607770a1bc1b919eeb9faa4a2dbe21d107b06be7 Mon Sep 17 00:00:00 2001 +From: Din Music +Date: Wed, 18 Mar 2026 09:16:08 +0000 +Subject: [PATCH 2/2] lxd/project/limits: Set instance type in + AllowInstanceCreation for consistency + +Signed-off-by: Din Music +--- + lxd/project/permissions.go | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/lxd/project/permissions.go b/lxd/project/permissions.go +index c2e400682d46..f3d92b8d3436 100644 +--- a/lxd/project/permissions.go ++++ b/lxd/project/permissions.go +@@ -61,6 +61,7 @@ func AllowInstanceCreation(tx *db.ClusterTx, projectName string, req api.Instanc + info.Instances = append(info.Instances, api.Instance{ + Name: req.Name, + Project: projectName, ++ Type: string(req.Type), + InstancePut: req.InstancePut, + }) + diff -Nru lxd-5.0.2+git20231211.1364ae4/debian/patches/111-CVE-2026-34178.patch lxd-5.0.2+git20231211.1364ae4/debian/patches/111-CVE-2026-34178.patch --- lxd-5.0.2+git20231211.1364ae4/debian/patches/111-CVE-2026-34178.patch 1970-01-01 00:00:00.000000000 +0000 +++ lxd-5.0.2+git20231211.1364ae4/debian/patches/111-CVE-2026-34178.patch 2026-04-14 18:30:26.000000000 +0000 @@ -0,0 +1,669 @@ +From e397e74206fe0de45fffb7246fd1ba8eb7597625 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Julian=20Peliz=C3=A4us?= +Date: Mon, 23 Mar 2026 10:12:10 +0100 +Subject: [PATCH 01/11] lxd: Pass backup index into internalImportFromBackup +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Julian Pelizäus +(cherry picked from commit f78210df3487c581abcf2a64aee3005e40153ae1) +--- + lxd/api_internal.go | 6 +++++- + lxd/instances_post.go | 2 +- + 2 files changed, 6 insertions(+), 2 deletions(-) + +diff --git a/lxd/api_internal.go b/lxd/api_internal.go +index 7843c7fc7f66..e9905ed6b03a 100644 +--- a/lxd/api_internal.go ++++ b/lxd/api_internal.go +@@ -582,7 +582,11 @@ func internalSQLExec(tx *sql.Tx, query string, result *internalSQLResult) error + + // internalImportFromBackup creates instance, storage pool and volume DB records from an instance's backup file. + // It expects the instance volume to be mounted so that the backup.yaml file is readable. +-func internalImportFromBackup(s *state.State, projectName string, instName string, allowNameOverride bool) error { ++// Also accepts an optional map of device overrides. ++func internalImportFromBackup(s *state.State, bInfo *backup.Info, allowNameOverride bool) error { ++ projectName := bInfo.Project ++ instName := bInfo.Name ++ + if instName == "" { + return fmt.Errorf("The name of the instance is required") + } +diff --git a/lxd/instances_post.go b/lxd/instances_post.go +index 746c73031358..1d320085c257 100644 +--- a/lxd/instances_post.go ++++ b/lxd/instances_post.go +@@ -721,7 +721,7 @@ func createFromBackup(s *state.State, r *http.Request, projectName string, data + + runRevert.Add(revertHook) + +- err = internalImportFromBackup(s, bInfo.Project, bInfo.Name, instanceName != "") ++ err = internalImportFromBackup(s, bInfo, instanceName != "") + if err != nil { + return fmt.Errorf("Failed importing backup: %w", err) + } + +From 06026e81e9231edd8ef089a8dd18bfae82474a01 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Julian=20Peliz=C3=A4us?= +Date: Mon, 23 Mar 2026 10:39:47 +0100 +Subject: [PATCH 02/11] lxd/api_internal: Don't use backup config from disk +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Instead use the already validated backup index which contains the same set of information. + +Signed-off-by: Julian Pelizäus +(cherry picked from commit 79dbba5fed73a08d0a8c0781c7ab2f9701e71f5a) +Rebased-by: Mathias Gibbens +--- + lxd/api_internal.go | 21 ++++----------------- + 1 file changed, 4 insertions(+), 17 deletions(-) + +diff --git a/lxd/api_internal.go b/lxd/api_internal.go +index e9905ed6b03a..edf579e29223 100644 +--- a/lxd/api_internal.go ++++ b/lxd/api_internal.go +@@ -641,23 +641,9 @@ func internalImportFromBackup(s *state.State, bInfo *backup.Info, allowNameOverr + return fmt.Errorf(`The instance %q does not seem to exist on any storage pool`, instName) + } + +- // User needs to make sure that we can access the directory where backup.yaml lives. +- instanceMountPoint := instanceMountPoints[0] +- isEmpty, err := shared.PathIsEmpty(instanceMountPoint) +- if err != nil { +- return err +- } +- +- if isEmpty { +- return fmt.Errorf(`The instance's directory %q appears to be empty. Please ensure that the instance's storage volume is mounted`, instanceMountPoint) +- } +- +- // Read in the backup.yaml file. +- backupYamlPath := filepath.Join(instanceMountPoint, "backup.yaml") +- backupConf, err := backup.ParseConfigYamlFile(backupYamlPath) +- if err != nil { +- return err +- } ++ // Use the information from the backup index. ++ // The backup config later gets persisted to disk too. ++ backupConf := bInfo.Config + + if allowNameOverride && instName != "" { + backupConf.Container.Name = instName +@@ -797,6 +783,7 @@ func internalImportFromBackup(s *state.State, bInfo *backup.Info, allowNameOverr + isPrivileged = true + } + ++ instanceMountPoint := instanceMountPoints[0] + err = storagePools.CreateContainerMountpoint(instanceMountPoint, instancePath, isPrivileged) + if err != nil { + return err + +From ea467bb7623c06a8253ace4b198859fd555bd3aa Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Julian=20Peliz=C3=A4us?= +Date: Mon, 23 Mar 2026 10:40:07 +0100 +Subject: [PATCH 03/11] lxd/instances_post: Add a note when the backup config + gets persisted +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Julian Pelizäus +(cherry picked from commit dc2c416651ba1844cedfd7a1786f73515a9d73c1) +--- + lxd/instances_post.go | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/lxd/instances_post.go b/lxd/instances_post.go +index 1d320085c257..6ed8cd7e35a2 100644 +--- a/lxd/instances_post.go ++++ b/lxd/instances_post.go +@@ -736,6 +736,7 @@ func createFromBackup(s *state.State, r *http.Request, projectName string, data + + // Run the storage post hook to perform any final actions now that the instance has been created + // in the database (this normally includes unmounting volumes that were mounted). ++ // This also writes the backup config to disk. + if postHook != nil { + err = postHook(inst) + if err != nil { + +From 721a5259f8361e8ac9c2ff4ac17d1435216ce033 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Julian=20Peliz=C3=A4us?= +Date: Mon, 23 Mar 2026 12:14:21 +0100 +Subject: [PATCH 04/11] lxd/backup: Don't anymore persist changes in + UpdateInstanceConfig +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Instead perform the changes in place. + +Signed-off-by: Julian Pelizäus +(cherry picked from commit 40a324e0cdcafdaf8a5df649ab1190f2d294a3e2) +Rebased-by: Mathias Gibbens +--- + lxd/backup/backup_config_utils.go | 60 +++++-------------------------- + 1 file changed, 8 insertions(+), 52 deletions(-) + +diff --git a/lxd/backup/backup_config_utils.go b/lxd/backup/backup_config_utils.go +index 46760fbb41b4..b32cd91e8344 100644 +--- a/lxd/backup/backup_config_utils.go ++++ b/lxd/backup/backup_config_utils.go +@@ -4,7 +4,6 @@ import ( + "context" + "fmt" + "os" +- "path/filepath" + + "gopkg.in/yaml.v2" + +@@ -105,28 +104,8 @@ func updateRootDevicePool(devices map[string]map[string]string, poolName string) + return false + } + +-// UpdateInstanceConfig updates the instance's backup.yaml configuration file. +-func UpdateInstanceConfig(c *db.Cluster, b Info, mountPath string) error { +- backupFilePath := filepath.Join(mountPath, "backup.yaml") +- +- // Read in the backup.yaml file. +- backup, err := ParseConfigYamlFile(backupFilePath) +- if err != nil { +- return err +- } +- +- // Update instance information in the backup.yaml. +- if backup.Container != nil { +- backup.Container.Name = b.Name +- backup.Container.Project = b.Project +- } +- +- // Update volume information in the backup.yaml. +- if backup.Volume != nil { +- backup.Volume.Name = b.Name +- backup.Volume.Project = b.Project +- } +- ++// UpdateInstanceConfigInPlace updates the instance's backup index in place. ++func UpdateInstanceConfigInPlace(c *db.Cluster, b *Info) error { + // Load the storage pool. + _, pool, _, err := c.GetStoragePool(b.Pool) + if err != nil { +@@ -138,18 +113,18 @@ func UpdateInstanceConfig(c *db.Cluster, b Info, mountPath string) error { + + rootDiskDeviceFound := false + +- // Change the pool in the backup.yaml. +- backup.Pool = pool ++ // Change the pool in case it doesn't match the one of the original instance. ++ b.Config.Pool = pool + +- if updateRootDevicePool(backup.Container.Devices, pool.Name) { ++ if updateRootDevicePool(b.Config.Container.Devices, pool.Name) { + rootDiskDeviceFound = true + } + +- if updateRootDevicePool(backup.Container.ExpandedDevices, pool.Name) { ++ if updateRootDevicePool(b.Config.Container.ExpandedDevices, pool.Name) { + rootDiskDeviceFound = true + } + +- for _, snapshot := range backup.Snapshots { ++ for _, snapshot := range b.Config.Snapshots { + updateRootDevicePool(snapshot.Devices, pool.Name) + updateRootDevicePool(snapshot.ExpandedDevices, pool.Name) + } +@@ -158,24 +133,5 @@ func UpdateInstanceConfig(c *db.Cluster, b Info, mountPath string) error { + return fmt.Errorf("No root device could be found") + } + +- // Write updated backup.yaml file. +- +- file, err := os.Create(backupFilePath) +- if err != nil { +- return err +- } +- +- defer func() { _ = file.Close() }() +- +- data, err := yaml.Marshal(&backup) +- if err != nil { +- return err +- } +- +- _, err = file.Write(data) +- if err != nil { +- return err +- } +- +- return file.Close() ++ return nil + } + +From e4a1dd06c2e0cf2b064e82c61cb87f367b117a5c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Julian=20Peliz=C3=A4us?= +Date: Mon, 23 Mar 2026 10:41:22 +0100 +Subject: [PATCH 05/11] lxd/instances_post: Don't update the instance's backup + config during import +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Instead the actual backup config gets written to disk at the end of the import (post hook) which doesn't require any updates. +Already existing backup configs will be overwritten. +Therefore only update the backup's index in memory to reflect the latest state. + +Signed-off-by: Julian Pelizäus +(cherry picked from commit db1d50c7bb544934d03be9d38e9dac16e9e81c0b) +--- + lxd/instances_post.go | 7 +++++++ + lxd/storage/backend_lxd.go | 8 -------- + 2 files changed, 7 insertions(+), 8 deletions(-) + +diff --git a/lxd/instances_post.go b/lxd/instances_post.go +index 6ed8cd7e35a2..e2959d64a21e 100644 +--- a/lxd/instances_post.go ++++ b/lxd/instances_post.go +@@ -692,6 +692,13 @@ func createFromBackup(s *state.State, r *http.Request, projectName string, data + return response.InternalError(err) + } + ++ // Ensure the backup's config included in the index reflects the current state. ++ // It is used later to create the actual backup's config. ++ err = backup.UpdateInstanceConfigInPlace(s.DB.Cluster, bInfo) ++ if err != nil { ++ return response.SmartError(fmt.Errorf("Failed updating backup index file in place: %w", err)) ++ } ++ + // Copy reverter so far so we can use it inside run after this function has finished. + runRevert := revert.Clone() + +diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go +index f76847380c6e..84bb93e74ae6 100644 +--- a/lxd/storage/backend_lxd.go ++++ b/lxd/storage/backend_lxd.go +@@ -788,14 +788,6 @@ func (b *lxdBackend) CreateInstanceFromBackup(srcBackup backup.Info, srcData io. + }) + } + +- // Update information in the backup.yaml file. +- err = vol.MountTask(func(mountPath string, op *operations.Operation) error { +- return backup.UpdateInstanceConfig(b.state.DB.Cluster, srcBackup, mountPath) +- }, op) +- if err != nil { +- return nil, nil, fmt.Errorf("Error updating backup file: %w", err) +- } +- + // Create a post hook function that will use the instance (that will be created) to setup a new volume + // containing the instance's root disk device's config so that the driver's post hook function can access + // that config to perform any post instance creation setup. + +From b06046ae64de6a4edaf870285fde73114c6f5b95 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Julian=20Peliz=C3=A4us?= +Date: Tue, 24 Mar 2026 10:46:20 +0100 +Subject: [PATCH 06/11] lxd/instances_post: Also update index' instance root + volume name +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +In case the instance gets imported under a differet name, not only set the new name but also update its corresponding root volume name. +This was based on a report from Copilot. +Currently this code path isn't used but it's added for consistency. + +See https://github.com/canonical/lxd/pull/17921#discussion_r2979867289. + +Signed-off-by: Julian Pelizäus +(cherry picked from commit 17e8b418485a74ebbe41511e6c1f88889275ced5) +--- + lxd/instances_post.go | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/lxd/instances_post.go b/lxd/instances_post.go +index e2959d64a21e..542a0cd9be70 100644 +--- a/lxd/instances_post.go ++++ b/lxd/instances_post.go +@@ -653,6 +653,7 @@ func createFromBackup(s *state.State, r *http.Request, projectName string, data + // Override instance name. + if instanceName != "" { + bInfo.Name = instanceName ++ bInfo.Config.Volume.Name = instanceName + } + + logger.Debug("Backup file info loaded", logger.Ctx{ + +From 8413f3c41db178f7bbf7e36ff42ee4ef1be8fd8a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Julian=20Peliz=C3=A4us?= +Date: Mon, 23 Mar 2026 11:05:37 +0100 +Subject: [PATCH 07/11] lxd/storage: Don't update backup config prior to export +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The backp config file gets removed during export anyway. + +Signed-off-by: Julian Pelizäus +(cherry picked from commit 6174842e03d34d3e245462a375e6d74485572e74) +Rebased-by: Mathias Gibbens +--- + lxd/storage/backend_lxd.go | 6 ------ + 1 file changed, 6 deletions(-) + +diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go +index 84bb93e74ae6..375a8a07e773 100644 +--- a/lxd/storage/backend_lxd.go ++++ b/lxd/storage/backend_lxd.go +@@ -2654,12 +2654,6 @@ func (b *lxdBackend) BackupInstance(inst instance.Instance, tarWriter *instancew + return err + } + +- // Ensure the backup file reflects current config. +- err = b.UpdateInstanceBackupFile(inst, snapshots, op) +- if err != nil { +- return err +- } +- + var snapNames []string + if snapshots { + // Get snapshots in age order, oldest first, and pass names to storage driver. + +From 5fe95048b0385de14cd5ed7d192a8ab2ff5018d4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Julian=20Peliz=C3=A4us?= +Date: Mon, 23 Mar 2026 18:17:26 +0100 +Subject: [PATCH 08/11] lxd/storage/drivers/generic_vfs: Always exclude backup + config from tarball +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Julian Pelizäus +(cherry picked from commit 2031d7aae588c3659aa690631b4a81d8da58a5e8) +Rebased-by: Mathias Gibbens +--- + lxd/storage/drivers/generic_vfs.go | 16 ++++++++++++++-- + 1 file changed, 14 insertions(+), 2 deletions(-) + +diff --git a/lxd/storage/drivers/generic_vfs.go b/lxd/storage/drivers/generic_vfs.go +index edd4f149a..44a479fe6 100644 +--- a/lxd/storage/drivers/generic_vfs.go ++++ b/lxd/storage/drivers/generic_vfs.go +@@ -486,6 +486,11 @@ func genericVFSBackupVolume(d Driver, vol Volume, tarWriter *instancewriter.Inst + // Reset hard link cache as we are copying a new volume (instance or snapshot). + tarWriter.ResetHardLinkMap() + ++ // Never include the backup config in the tarball. ++ alwaysExcludedPaths := []string{ ++ filepath.Join(mountPath, "backup.yaml"), ++ } ++ + if v.contentType == ContentTypeBlock { + blockPath, err := d.GetVolumeDiskPath(v) + if err != nil { +@@ -513,14 +518,16 @@ func genericVFSBackupVolume(d Driver, vol Volume, tarWriter *instancewriter.Inst + if v.IsVMBlock() { + logMsg := "Copying virtual machine config volume" + ++ combinedExcludedPaths := append(exclude, alwaysExcludedPaths...) ++ + d.Logger().Debug(logMsg, logger.Ctx{"sourcePath": mountPath, "prefix": prefix}) + err = filepath.Walk(mountPath, func(srcPath string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + +- // Skip any exluded files. +- if shared.StringHasPrefix(srcPath, exclude...) { ++ // Skip any excluded files. ++ if shared.StringHasPrefix(srcPath, combinedExcludedPaths...) { + return nil + } + +@@ -597,6 +604,11 @@ func genericVFSBackupVolume(d Driver, vol Volume, tarWriter *instancewriter.Inst + return fmt.Errorf("Error walking file during export: %q: %w", srcPath, err) + } + ++ // Skip any excluded files. ++ if shared.StringHasPrefix(srcPath, alwaysExcludedPaths...) { ++ return nil ++ } ++ + name := filepath.Join(prefix, strings.TrimPrefix(srcPath, mountPath)) + + // Write the file to the tarball with ignoreGrowth enabled so that if the + +From fd9fa4b5c9ad796bff227c544f00afc9440156b2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Julian=20Peliz=C3=A4us?= +Date: Thu, 19 Mar 2026 13:35:23 +0100 +Subject: [PATCH 09/11] test/suites/backup: Add test_backup_inconsistent_config +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Julian Pelizäus +(cherry picked from commit 0ba3066e81033e66df423a9fc6e3af787f3e37cb) +--- + test/main.sh | 1 + + test/suites/backup.sh | 139 ++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 140 insertions(+) + +diff --git a/test/main.sh b/test/main.sh +index b6025706b453..fff5a58be1f1 100755 +--- a/test/main.sh ++++ b/test/main.sh +@@ -378,6 +378,7 @@ if [ "${1:-"all"}" != "cluster" ]; then + run_test test_backup_rename "backup rename" + run_test test_backup_volume_export "backup volume export" + run_test test_backup_export_import_instance_only "backup export and import instance only" ++ run_test test_backup_inconsistent_config "backup config inconsistency checks" + run_test test_backup_volume_rename_delete "backup volume rename and delete" + run_test test_backup_different_instance_uuid "backup instance and check instance UUIDs" + run_test test_backup_volume_expiry "backup volume expiry" +diff --git a/test/suites/backup.sh b/test/suites/backup.sh +index 210a0bfb2e86..e7376a544dfb 100644 +--- a/test/suites/backup.sh ++++ b/test/suites/backup.sh +@@ -965,3 +965,142 @@ test_backup_export_import_instance_only() { + rm "${LXD_DIR}/c1.tar.gz" + lxc delete -f c1 + } ++ ++test_backup_inconsistent_config() { ++ local poolName ++ poolName="lxdtest-$(basename "${LXD_DIR}")" ++ ++ # Create a restricted project and switch to it. ++ lxc project create restricted \ ++ -c restricted=true ++ lxc profile device add default root disk path=/ pool="${poolName}" --project restricted ++ ++ # Switch to restricted project to test imports. ++ lxc project switch restricted ++ ++ tmpDir=$(mktemp -d -p "${TEST_DIR}" backups-XXX) ++ ++ # Create a new empty instance with a clean index and inconsistent backup config. ++ mkdir -p "${tmpDir}/backup/container" ++ cat > "${tmpDir}/backup/index.yaml" < "${tmpDir}/backup/container/backup.yaml" < temp.yaml && mv temp.yaml "${tmpDir}/backup/container/backup.yaml" ++ yq '.container.expanded_config = {}' < "${tmpDir}/backup/container/backup.yaml" > temp.yaml && mv temp.yaml "${tmpDir}/backup/container/backup.yaml" ++ ++ # Re-package the fixed archive. ++ tar -cf "${tmpDir}/fixed-backup.tar" -C "${tmpDir}" "backup/" ++ ++ # Importing the instance from tarball succeeds. ++ lxc import "${tmpDir}/fixed-backup.tar" ++ ++ lxc delete -f inconsistent-instance ++ ++ # Now make the index inconsistent. ++ yq '.config.container.config += {"security.privileged": "true"}' < "${tmpDir}/backup/index.yaml" > temp.yaml && mv temp.yaml "${tmpDir}/backup/index.yaml" ++ yq '.config.container.expanded_config += {"security.privileged": "true"}' < "${tmpDir}/backup/index.yaml" > temp.yaml && mv temp.yaml "${tmpDir}/backup/index.yaml" ++ ++ # Re-package the inconsistent archive. ++ tar -cf "${tmpDir}/inconsistent-backup.tar" -C "${tmpDir}" "backup/" ++ ++ # Importing the instance from tarball fails. ++ ! lxc import "${tmpDir}/inconsistent-backup.tar" || false ++ ++ # Fix the index. ++ yq '.config.container.config = {}' < "${tmpDir}/backup/index.yaml" > temp.yaml && mv temp.yaml "${tmpDir}/backup/index.yaml" ++ yq '.config.container.expanded_config = {}' < "${tmpDir}/backup/index.yaml" > temp.yaml && mv temp.yaml "${tmpDir}/backup/index.yaml" ++ ++ # Re-package the fixed archive. ++ tar -cf "${tmpDir}/fixed-backup.tar" -C "${tmpDir}" "backup/" ++ ++ # Importing the instance from tarball succeeds. ++ lxc import "${tmpDir}/fixed-backup.tar" ++ ++ lxc delete -f inconsistent-instance ++ lxc project delete restricted ++ rm -rf "${tmpDir}/backup" ++ ++ # Check an exported instance doesn't contain a backup config. ++ lxc init --empty c1 ++ lxc export c1 "${tmpDir}/c1.tar.gz" ++ tar -xzf "${tmpDir}/c1.tar.gz" -C "${tmpDir}" ++ [ ! -f "${tmpDir}/backup/container/backup.yaml" ] ++ ++ lxc delete -f c1 ++ rm -r "${tmpDir}" ++} + +From 7785837e7d37f4e58249b2c1cc92e2735c159e7e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Julian=20Peliz=C3=A4us?= +Date: Tue, 24 Mar 2026 09:52:48 +0100 +Subject: [PATCH 10/11] lxd/api_internal: Update comment on + internalImportFromBackup +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Julian Pelizäus +(cherry picked from commit 0fe9b1fdb4578c5c27e10b45bfc5ea4d23d0832c) +--- + lxd/api_internal.go | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lxd/api_internal.go b/lxd/api_internal.go +index edf579e29223..a4694f77392c 100644 +--- a/lxd/api_internal.go ++++ b/lxd/api_internal.go +@@ -581,7 +581,7 @@ func internalSQLExec(tx *sql.Tx, query string, result *internalSQLResult) error + } + + // internalImportFromBackup creates instance, storage pool and volume DB records from an instance's backup file. +-// It expects the instance volume to be mounted so that the backup.yaml file is readable. ++// It expects the backup's index file to determine the instance's config. + // Also accepts an optional map of device overrides. + func internalImportFromBackup(s *state.State, bInfo *backup.Info, allowNameOverride bool) error { + projectName := bInfo.Project + +From 8eabaed5887c0c648bd5d41dc0a36ff9fe0bf722 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Julian=20Peliz=C3=A4us?= +Date: Wed, 25 Mar 2026 09:25:42 +0100 +Subject: [PATCH 11/11] test/suites/backup: Check for specific error during + import +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Julian Pelizäus +(cherry picked from commit 041a147479a7d23c72d72feb262faab5d91b9494) +--- + test/suites/backup.sh | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/test/suites/backup.sh b/test/suites/backup.sh +index e7376a544dfb..3767d76bc953 100644 +--- a/test/suites/backup.sh ++++ b/test/suites/backup.sh +@@ -1079,7 +1079,10 @@ EOF + tar -cf "${tmpDir}/inconsistent-backup.tar" -C "${tmpDir}" "backup/" + + # Importing the instance from tarball fails. +- ! lxc import "${tmpDir}/inconsistent-backup.tar" || false ++ ! lxc import "${tmpDir}/inconsistent-backup.tar" >/dev/null 2>error || false ++ [ "$(tail -1 error)" = 'Error: Failed checking if instance creation allowed: Invalid value "true" for config "security.privileged" on container "inconsistent-instance" of project "restricted": Privileged containers are forbidden' ] ++ ++ rm error + + # Fix the index. + yq '.config.container.config = {}' < "${tmpDir}/backup/index.yaml" > temp.yaml && mv temp.yaml "${tmpDir}/backup/index.yaml" diff -Nru lxd-5.0.2+git20231211.1364ae4/debian/patches/112-CVE-2026-34179.patch lxd-5.0.2+git20231211.1364ae4/debian/patches/112-CVE-2026-34179.patch --- lxd-5.0.2+git20231211.1364ae4/debian/patches/112-CVE-2026-34179.patch 1970-01-01 00:00:00.000000000 +0000 +++ lxd-5.0.2+git20231211.1364ae4/debian/patches/112-CVE-2026-34179.patch 2026-04-14 18:30:26.000000000 +0000 @@ -0,0 +1,109 @@ +From 8955439529204a4404b7ba4730d91f75dfe97a14 Mon Sep 17 00:00:00 2001 +From: Mark Laing +Date: Mon, 23 Mar 2026 09:41:08 +0000 +Subject: [PATCH 1/2] lxd: Improve validation when editing certificates. + +Signed-off-by: Mark Laing +(cherry picked from commit 8c0c8dcc0f7b6ef59524bfeae198b6081248a88d) +--- + lxd/certificates.go | 15 ++++++++++----- + 1 file changed, 10 insertions(+), 5 deletions(-) + +diff --git a/lxd/certificates.go b/lxd/certificates.go +index a47194fd2036..375a903f6943 100644 +--- a/lxd/certificates.go ++++ b/lxd/certificates.go +@@ -7,6 +7,7 @@ import ( + "encoding/base64" + "encoding/json" + "encoding/pem" ++ "errors" + "fmt" + "net" + "net/http" +@@ -970,10 +971,14 @@ func certificatePatch(d *Daemon, r *http.Request) response.Response { + func doCertificateUpdate(d *Daemon, dbInfo api.Certificate, req api.CertificatePut, clientType clusterRequest.ClientType, r *http.Request) response.Response { + s := d.State() + ++ if dbInfo.Type != req.Type { ++ return response.Forbidden(errors.New("The certificate type cannot be changed")) ++ } ++ + if clientType == clusterRequest.ClientTypeNormal { +- reqDBType, err := certificate.FromAPIType(req.Type) ++ dbInfoType, err := certificate.FromAPIType(dbInfo.Type) + if err != nil { +- return response.BadRequest(err) ++ return response.SmartError(fmt.Errorf("Invalid existing certificate type: %w", err)) + } + + // Convert to the database type. +@@ -982,7 +987,7 @@ func doCertificateUpdate(d *Daemon, dbInfo api.Certificate, req api.CertificateP + Fingerprint: dbInfo.Fingerprint, + Restricted: req.Restricted, + Name: req.Name, +- Type: reqDBType, ++ Type: dbInfoType, + } + + // Non-admins are able to change their own certificate but no other fields. +@@ -995,7 +1000,7 @@ func doCertificateUpdate(d *Daemon, dbInfo api.Certificate, req api.CertificateP + } + + // Ensure the user in not trying to change fields other than the certificate. +- if dbInfo.Restricted != req.Restricted || dbInfo.Name != req.Name || len(dbInfo.Projects) != len(req.Projects) { ++ if dbInfo.Restricted != req.Restricted || dbInfo.Name != req.Name || len(dbInfo.Projects) != len(req.Projects) || dbInfo.Type != req.Type { + return response.Forbidden(fmt.Errorf("Only the certificate can be changed")) + } + +@@ -1011,7 +1016,7 @@ func doCertificateUpdate(d *Daemon, dbInfo api.Certificate, req api.CertificateP + Fingerprint: dbInfo.Fingerprint, + Restricted: dbInfo.Restricted, + Name: dbInfo.Name, +- Type: reqDBType, ++ Type: dbInfoType, + } + + certProjects = dbInfo.Projects + +From 8d020089fae6d13b4904392e416fd9e6598534ae Mon Sep 17 00:00:00 2001 +From: Mark Laing +Date: Mon, 23 Mar 2026 09:55:36 +0000 +Subject: [PATCH 2/2] test/suites: Test improved validation. + +Signed-off-by: Mark Laing +(cherry picked from commit 5f4b8e20afa321e0be436a908a8a92b8b8700557) +--- + test/suites/tls_restrictions.sh | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/test/suites/tls_restrictions.sh b/test/suites/tls_restrictions.sh +index ab72b2c35ce5..c10bb61c1b85 100644 +--- a/test/suites/tls_restrictions.sh ++++ b/test/suites/tls_restrictions.sh +@@ -24,9 +24,25 @@ test_tls_restrictions() { + lxc_remote project list localhost: | grep -q default + lxc_remote project list localhost: | grep -q blah + ++ # Confirm certificates cannot be edited to have "server" type. ++ ! lxc_remote config trust show "localhost:${FINGERPRINT}" | sed -e "s/type: client/type: server/" | lxc_remote config trust edit "localhost:${FINGERPRINT}" || false ++ + # Apply restrictions + lxc config trust show "${FINGERPRINT}" | sed -e "s/restricted: false/restricted: true/" | lxc config trust edit "${FINGERPRINT}" + ++ # Confirm restricted client cannot edit certificate type, name, restrictions, or projects. ++ cert_name="$(lxc query "localhost:/1.0/certificates/${FINGERPRINT}" | jq -r '.name')" ++ ! lxc_remote config trust show "localhost:${FINGERPRINT}" | sed -e "s/type: client/type: server/" | lxc_remote config trust edit "localhost:${FINGERPRINT}" || false ++ ! lxc_remote config trust show "localhost:${FINGERPRINT}" | sed -e "s/type: client/type: metrics/" | lxc_remote config trust edit "localhost:${FINGERPRINT}" || false ++ ! lxc_remote config trust show "localhost:${FINGERPRINT}" | sed -e "s/name: ${cert_name}/name: bar/" | lxc_remote config trust edit "localhost:${FINGERPRINT}" || false ++ ! lxc_remote config trust show "localhost:${FINGERPRINT}" | sed -e "s/restricted: true/restricted: false/" | lxc_remote config trust edit "localhost:${FINGERPRINT}" || false ++ ! lxc_remote config trust show "localhost:${FINGERPRINT}" | sed -e "s/projects: \[\]/projects: ['default']/" | lxc_remote config trust edit "localhost:${FINGERPRINT}" || false ++ ! lxc_remote query -X PATCH "localhost:/1.0/certificates/${FINGERPRINT}" -d '{"type": "server"}' || false ++ ! lxc_remote query -X PATCH "localhost:/1.0/certificates/${FINGERPRINT}" -d '{"type": "metrics"}' || false ++ ! lxc_remote query -X PATCH "localhost:/1.0/certificates/${FINGERPRINT}" -d '{"restricted": false, "projects": []}' || false ++ ! lxc_remote query -X PATCH "localhost:/1.0/certificates/${FINGERPRINT}" -d '{"projects": ["default"]}' || false ++ ! lxc_remote query -X PATCH "localhost:/1.0/certificates/${FINGERPRINT}" -d '{"name": "bar"}' || false ++ + # Confirm no project visible when none listed + [ "$(lxc_remote project list localhost: --format csv | wc -l)" = 0 ] + diff -Nru lxd-5.0.2+git20231211.1364ae4/debian/patches/series lxd-5.0.2+git20231211.1364ae4/debian/patches/series --- lxd-5.0.2+git20231211.1364ae4/debian/patches/series 2026-03-24 23:41:46.000000000 +0000 +++ lxd-5.0.2+git20231211.1364ae4/debian/patches/series 2026-04-14 18:30:26.000000000 +0000 @@ -21,3 +21,6 @@ 107-CVE-2026-28384.patch 108-CVE-2026-33542.patch 109-CVE-2026-33897.patch +110-CVE-2026-34177.patch +111-CVE-2026-34178.patch +112-CVE-2026-34179.patch