[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1120520: trixie-pu: package lxd/5.0.2+git20231211.1364ae4-9+deb13u2



Package: release.debian.org
Severity: normal
Tags: trixie
User: release.debian.org@packages.debian.org
Usertags: pu
X-Debbugs-Cc: gibmat@debian.org, team@security.debian.org
Control: affects -1 + src:lxd

[ Reason ]
While investigating CVE-2025-64507, I discovered that filesystem ID
mapping is broken for LXD in trixie. This renders the attack
unexploitable (yay!) but is a regression for anyone who wants/needs to
setup filesystem ID mapping.

After discussion with the Security Team, I have prepared this updated
version of LXD that fixes both the ID mapping and relevant CVE. Please
note that is NOT being treated as a security update for trixie and it's
fine to be included in the 13.3 point release.

[ Impact ]
Users cannot currently configure filesystem ID mapping for their
containers/VMs.

[ Tests ]
I have manually tested creating containers with an ID-shifted storage
volume and verified it works as expected and that the mitigation for
CVE-2025-64507 prevents the attack.

[ Risks ]
Minor/none -- the regression fix has been in the stable-5.0 branch for
a year and a half, and the CVE has been applied on all upstream
supported LXD/Incus branches.

[ 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
      NOTE: lxd was RM'ed from unstable, so there is no fix to
            apply there

[ Changes ]
Cherry-pick relevant fixes Canonical's stable-5.0 branch.

[ Other info ]
The source debdiff is attached.
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	2025-10-02 16:23:38.000000000 +0000
+++ lxd-5.0.2+git20231211.1364ae4/debian/changelog	2025-11-11 15:25:08.000000000 +0000
@@ -1,3 +1,10 @@
+lxd (5.0.2+git20231211.1364ae4-9+deb13u2) trixie; urgency=medium
+
+  * Cherry-pick upstream fix for broken idmapping with kernel 6.9+
+  * Cherry-pick upstream fix for CVE-2025-64507 / GHSA-56mx-8g9f-5crf
+
+ -- Mathias Gibbens <gibmat@debian.org>  Tue, 11 Nov 2025 15:25:08 +0000
+
 lxd (5.0.2+git20231211.1364ae4-9+deb13u1) trixie-security; urgency=high
 
   * Backport fixes for the following security issues that are unfixed by
diff -Nru lxd-5.0.2+git20231211.1364ae4/debian/patches/013-cherry-pick-fix-idmapping.patch lxd-5.0.2+git20231211.1364ae4/debian/patches/013-cherry-pick-fix-idmapping.patch
--- lxd-5.0.2+git20231211.1364ae4/debian/patches/013-cherry-pick-fix-idmapping.patch	1970-01-01 00:00:00.000000000 +0000
+++ lxd-5.0.2+git20231211.1364ae4/debian/patches/013-cherry-pick-fix-idmapping.patch	2025-11-11 15:25:08.000000000 +0000
@@ -0,0 +1,111 @@
+From 891b129ab6dde6c63eb7cdadbbd746419d2f6d26 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber@stgraber.org>
+Date: Wed, 22 May 2024 13:29:18 -0400
+Subject: [PATCH] shared/idmap: Make get_userns_fd configure the userns
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Stéphane Graber <stgraber@stgraber.org>
+(cherry picked from commit ec223f75e7056f271fb84be56980243cd68e67b3)
+Signed-off-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@canonical.com>
+License: Apache-2.0
+---
+ lxd/idmap/shift_linux.go | 68 +++++++++++++++++++++++++++++++++++++---
+ 1 file changed, 64 insertions(+), 4 deletions(-)
+
+diff --git a/shared/idmap/shift_linux.go b/shared/idmap/shift_linux.go
+index 024c55fa819b..d8fbff31101e 100644
+--- a/shared/idmap/shift_linux.go
++++ b/shared/idmap/shift_linux.go
+@@ -279,19 +279,78 @@ static int get_userns_fd_cb(void *data)
+ 
+ static int get_userns_fd(void)
+ {
+-	int ret;
++	int userns_fd = -EBADF;
++	int file_fd = -EBADF;
+ 	pid_t pid;
+ 	char path[256];
+ 
++	// Create the namespace.
+ 	pid = do_clone(get_userns_fd_cb, NULL, CLONE_NEWUSER);
+ 	if (pid < 0)
+-		return -errno;
++		goto err;
+ 
++	// Fetch a reference.
+ 	snprintf(path, sizeof(path), "/proc/%d/ns/user", pid);
+-	ret = open(path, O_RDONLY | O_CLOEXEC);
++	userns_fd = open(path, O_RDONLY | O_CLOEXEC);
++	if (userns_fd < 0)
++		goto err_process;
++
++	// Setup uid_map
++	snprintf(path, sizeof(path), "/proc/%d/uid_map", pid);
++	file_fd = openat(AT_FDCWD, path, O_WRONLY);
++	if (file_fd < 0)
++		goto err_process;
++
++	if (write(file_fd, "0 0 1", 5) != 5)
++		goto err_process;
++
++	if (close(file_fd) < 0) {
++		file_fd = -EBADF;
++		goto err_process;
++	}
++
++	// Setup setgroups
++	snprintf(path, sizeof(path), "/proc/%d/setgroups", pid);
++	file_fd = openat(AT_FDCWD, path, O_WRONLY);
++	if (file_fd < 0)
++		goto err_process;
++
++	if (write(file_fd, "deny", 4) != 4)
++		goto err_process;
++
++	if (close(file_fd) < 0) {
++		file_fd = -EBADF;
++		goto err_process;
++	}
++
++	// Setup gid_map
++	snprintf(path, sizeof(path), "/proc/%d/gid_map", pid);
++	file_fd = openat(AT_FDCWD, path, O_WRONLY);
++	if (file_fd < 0)
++		goto err_process;
++
++	if (write(file_fd, "0 0 1", 5) != 5)
++		goto err_process;
++
++	if (close(file_fd) < 0) {
++		file_fd = -EBADF;
++		goto err_process;
++	}
++
++	// Kill the temporary process.
++	kill(pid, SIGKILL);
++	wait_for_pid(pid);
++
++	return userns_fd;
++
++err_process:
+ 	kill(pid, SIGKILL);
+ 	wait_for_pid(pid);
+-	return ret;
++
++err:
++	close(userns_fd);
++	close(file_fd);
++	return -1;
+ }
+ 
+ static int create_detached_idmapped_mount(const char *path, const char *fstype)
+@@ -335,6 +394,7 @@ static int create_detached_idmapped_mount(const char *path, const char *fstype)
+ 	if (ret < 0)
+ 		return -errno;
+ 
++	close(fd_userns);
+ 	return 0;
+ }
+ */
diff -Nru lxd-5.0.2+git20231211.1364ae4/debian/patches/104-GHSA-56mx-8g9f-5crf.patch lxd-5.0.2+git20231211.1364ae4/debian/patches/104-GHSA-56mx-8g9f-5crf.patch
--- lxd-5.0.2+git20231211.1364ae4/debian/patches/104-GHSA-56mx-8g9f-5crf.patch	1970-01-01 00:00:00.000000000 +0000
+++ lxd-5.0.2+git20231211.1364ae4/debian/patches/104-GHSA-56mx-8g9f-5crf.patch	2025-11-11 15:25:08.000000000 +0000
@@ -0,0 +1,245 @@
+From 2236fb7001bec8a4dd6a9070eb59d2cc818f833c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber@stgraber.org>
+Date: Sun, 9 Nov 2025 18:41:24 -0500
+Subject: [PATCH 1/5] lxd/storage: Tighten storage pool volume permissions
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Related to https://github.com/lxc/incus/issues/2641
+
+Signed-off-by: Stéphane Graber <stgraber@stgraber.org>
+(cherry picked from commit b0c6c0bac42c6ac27d536984cc043a6ec02b9e7c)
+Signed-off-by: Thomas Parrott <thomas.parrott@canonical.com>
+License: Apache-2.0
+(cherry picked from commit 7598d5ab710e05829c7bc4a6e30106a022f376c1)
+(cherry picked from commit 049d86def7c26e8736bb991e4223ec89dab0b05e)
+---
+ lxd/storage/backend_lxd.go              |  4 ++--
+ lxd/storage/drivers/driver_btrfs.go     |  2 +-
+ lxd/storage/drivers/driver_zfs_utils.go |  3 +--
+ lxd/storage/drivers/generic_vfs.go      |  4 ++--
+ lxd/storage/drivers/volume.go           | 17 +++++++++++------
+ 5 files changed, 17 insertions(+), 13 deletions(-)
+
+diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
+index 76be2843343b..7da6aaf631df 100644
+--- a/lxd/storage/backend_lxd.go
++++ b/lxd/storage/backend_lxd.go
+@@ -5116,9 +5116,9 @@ func (b *lxdBackend) RestoreCustomVolume(projectName, volName string, snapshotNa
+ 
+ func (b *lxdBackend) createStorageStructure(path string) error {
+ 	for _, volType := range b.driver.Info().VolumeTypes {
+-		for _, name := range drivers.BaseDirectories[volType] {
++		for _, name := range drivers.BaseDirectories[volType].Paths {
+ 			path := filepath.Join(path, name)
+-			err := os.MkdirAll(path, 0711)
++			err := os.MkdirAll(path, drivers.BaseDirectories[volType].Mode)
+ 			if err != nil && !os.IsExist(err) {
+ 				return fmt.Errorf("Failed to create directory %q: %w", path, err)
+ 			}
+diff --git a/lxd/storage/drivers/driver_btrfs.go b/lxd/storage/drivers/driver_btrfs.go
+index 89310350d3bb..1e8577043cc5 100644
+--- a/lxd/storage/drivers/driver_btrfs.go
++++ b/lxd/storage/drivers/driver_btrfs.go
+@@ -283,7 +283,7 @@ func (d *btrfs) Delete(op *operations.Operation) error {
+ 
+ 	// Delete potential intermediate btrfs subvolumes.
+ 	for _, volType := range d.Info().VolumeTypes {
+-		for _, dir := range BaseDirectories[volType] {
++		for _, dir := range BaseDirectories[volType].Paths {
+ 			path := filepath.Join(GetPoolMountPath(d.name), dir)
+ 			if !shared.PathExists(path) {
+ 				continue
+diff --git a/lxd/storage/drivers/driver_zfs_utils.go b/lxd/storage/drivers/driver_zfs_utils.go
+index 0381c98be034..d72f5add2527 100644
+--- a/lxd/storage/drivers/driver_zfs_utils.go
++++ b/lxd/storage/drivers/driver_zfs_utils.go
+@@ -286,8 +286,7 @@ func (d *zfs) initialDatasets() []string {
+ 
+ 	// Iterate over the listed supported volume types.
+ 	for _, volType := range d.Info().VolumeTypes {
+-		entries = append(entries, BaseDirectories[volType][0])
+-		entries = append(entries, filepath.Join("deleted", BaseDirectories[volType][0]))
++		entries = append(entries, BaseDirectories[volType].Paths[0], "deleted/"+BaseDirectories[volType].Paths[0])
+ 	}
+ 
+ 	return entries
+diff --git a/lxd/storage/drivers/generic_vfs.go b/lxd/storage/drivers/generic_vfs.go
+index 03e729b4092f..7327a5b43487 100644
+--- a/lxd/storage/drivers/generic_vfs.go
++++ b/lxd/storage/drivers/generic_vfs.go
+@@ -1153,11 +1153,11 @@ func genericVFSListVolumes(d Driver) ([]Volume, error) {
+ 	poolMountPath := GetPoolMountPath(poolName)
+ 
+ 	for _, volType := range d.Info().VolumeTypes {
+-		if len(BaseDirectories[volType]) < 1 {
++		if len(BaseDirectories[volType].Paths) < 1 {
+ 			return nil, fmt.Errorf("Cannot get base directory name for volume type %q", volType)
+ 		}
+ 
+-		volTypePath := filepath.Join(poolMountPath, BaseDirectories[volType][0])
++		volTypePath := filepath.Join(poolMountPath, BaseDirectories[volType].Paths[0])
+ 		ents, err := os.ReadDir(volTypePath)
+ 		if err != nil {
+ 			return nil, fmt.Errorf("Failed to list directory %q for volume type %q: %w", volTypePath, volType, err)
+diff --git a/lxd/storage/drivers/volume.go b/lxd/storage/drivers/volume.go
+index 96ee6ffcfa11..c2188b826004 100644
+--- a/lxd/storage/drivers/volume.go
++++ b/lxd/storage/drivers/volume.go
+@@ -76,13 +76,18 @@ const ContentTypeISO = ContentType("iso")
+ // VolumePostHook function returned from a storage action that should be run later to complete the action.
+ type VolumePostHook func(vol Volume) error
+ 
++type baseDirectory struct {
++	Paths []string
++	Mode  os.FileMode
++}
++
+ // BaseDirectories maps volume types to the expected directories.
+-var BaseDirectories = map[VolumeType][]string{
+-	VolumeTypeBucket:    {"buckets"},
+-	VolumeTypeContainer: {"containers", "containers-snapshots"},
+-	VolumeTypeCustom:    {"custom", "custom-snapshots"},
+-	VolumeTypeImage:     {"images"},
+-	VolumeTypeVM:        {"virtual-machines", "virtual-machines-snapshots"},
++var BaseDirectories = map[VolumeType]baseDirectory{
++	VolumeTypeBucket:    {Paths: []string{"buckets"}, Mode: 0o711},
++	VolumeTypeContainer: {Paths: []string{"containers", "containers-snapshots"}, Mode: 0o711},
++	VolumeTypeCustom:    {Paths: []string{"custom", "custom-snapshots"}, Mode: 0o700},
++	VolumeTypeImage:     {Paths: []string{"images"}, Mode: 0o700},
++	VolumeTypeVM:        {Paths: []string{"virtual-machines", "virtual-machines-snapshots"}, Mode: 0o700},
+ }
+ 
+ // Volume represents a storage volume, and provides functions to mount and unmount it.
+
+From 54eca5752e47bd9443f43850ca8241074de49609 Mon Sep 17 00:00:00 2001
+From: Thomas Parrott <thomas.parrott@canonical.com>
+Date: Mon, 10 Nov 2025 10:49:26 +0000
+Subject: [PATCH 2/5] lxd/storage/drivers/volume: Add comments explaining
+ differences in BaseDirectories permissions
+
+Signed-off-by: Thomas Parrott <thomas.parrott@canonical.com>
+(cherry picked from commit 52a551c8f0452eda858ac7ba27dc250d84ce72bf)
+(cherry picked from commit 10ad9d7419b4127cd127d2850e79aee3e1dc07e1)
+---
+ lxd/storage/drivers/volume.go | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/lxd/storage/drivers/volume.go b/lxd/storage/drivers/volume.go
+index c2188b826004..3ef75f98c671 100644
+--- a/lxd/storage/drivers/volume.go
++++ b/lxd/storage/drivers/volume.go
+@@ -83,8 +83,8 @@ type baseDirectory struct {
+ 
+ // BaseDirectories maps volume types to the expected directories.
+ var BaseDirectories = map[VolumeType]baseDirectory{
+-	VolumeTypeBucket:    {Paths: []string{"buckets"}, Mode: 0o711},
+-	VolumeTypeContainer: {Paths: []string{"containers", "containers-snapshots"}, Mode: 0o711},
++	VolumeTypeBucket:    {Paths: []string{"buckets"}, Mode: 0o711},                            // MinIO is run as non-root, so 0700 won't work, however as S3 interface doesn't allow creation of setuid binaries this is OK.
++	VolumeTypeContainer: {Paths: []string{"containers", "containers-snapshots"}, Mode: 0o711}, // Containers may be run as non-root, so 0700 won't work, however as containers have their own sub-directory with correct ownership that is 0100 this is OK.
+ 	VolumeTypeCustom:    {Paths: []string{"custom", "custom-snapshots"}, Mode: 0o700},
+ 	VolumeTypeImage:     {Paths: []string{"images"}, Mode: 0o700},
+ 	VolumeTypeVM:        {Paths: []string{"virtual-machines", "virtual-machines-snapshots"}, Mode: 0o700},
+
+From 693c65f114b7ab94f896e38f3deaa3b53a48c6e1 Mon Sep 17 00:00:00 2001
+From: Thomas Parrott <thomas.parrott@canonical.com>
+Date: Mon, 10 Nov 2025 09:35:28 +0000
+Subject: [PATCH 3/5] lxd/storage/backend/lxd: Replace deprecated os.IsExist
+
+Signed-off-by: Thomas Parrott <thomas.parrott@canonical.com>
+(cherry picked from commit 89c0906e5107fa059263490d020b99dfa1c9c26b)
+(cherry picked from commit 97219271d4c67e388e584944349a87f86647fcdb)
+---
+ lxd/storage/backend_lxd.go | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
+index 7da6aaf631df..f76847380c6e 100644
+--- a/lxd/storage/backend_lxd.go
++++ b/lxd/storage/backend_lxd.go
+@@ -5119,7 +5119,7 @@ func (b *lxdBackend) createStorageStructure(path string) error {
+ 		for _, name := range drivers.BaseDirectories[volType].Paths {
+ 			path := filepath.Join(path, name)
+ 			err := os.MkdirAll(path, drivers.BaseDirectories[volType].Mode)
+-			if err != nil && !os.IsExist(err) {
++			if err != nil && !errors.Is(err, fs.ErrExist) {
+ 				return fmt.Errorf("Failed to create directory %q: %w", path, err)
+ 			}
+ 		}
+
+From 849ed20a5fbb5229efec24c98ca3caee608fd87d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber@stgraber.org>
+Date: Sun, 9 Nov 2025 18:41:39 -0500
+Subject: [PATCH 4/5] lxd/patches: Re-apply storage permissions on update
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Related to https://github.com/lxc/incus/issues/2641
+
+Signed-off-by: Stéphane Graber <stgraber@stgraber.org>
+(cherry picked from commit 3abdc12cf6a8dce391d28d340a32c137125357dd)
+Signed-off-by: Thomas Parrott <thomas.parrott@canonical.com>
+License: Apache-2.0
+(cherry picked from commit 87a34bbe6bd3f918081db431ac1a6ee22346f172)
+(cherry picked from commit b2d38e82bda982e337a3cbdc173ec4b372ab8b24)
+---
+ lxd/patches.go | 32 ++++++++++++++++++++++++++++++++
+ 1 file changed, 32 insertions(+)
+
+diff --git a/lxd/patches.go b/lxd/patches.go
+index 01ad6926aed1..b4559b13fde0 100644
+--- a/lxd/patches.go
++++ b/lxd/patches.go
+@@ -4,6 +4,7 @@ import (
+ 	"context"
+ 	"errors"
+ 	"fmt"
++	"io/fs"
+ 	"net/http"
+ 	"os"
+ 	"path/filepath"
+@@ -83,6 +84,7 @@ var patches = []patch{
+ 	{name: "zfs_set_content_type_user_property", stage: patchPostDaemonStorage, run: patchZfsSetContentTypeUserProperty},
+ 	{name: "storage_zfs_unset_invalid_block_settings", stage: patchPostDaemonStorage, run: patchStorageZfsUnsetInvalidBlockSettings},
+ 	{name: "storage_zfs_unset_invalid_block_settings_v2", stage: patchPostDaemonStorage, run: patchStorageZfsUnsetInvalidBlockSettingsV2},
++	{name: "pool_fix_default_permissions", stage: patchPostDaemonStorage, run: patchDefaultStoragePermissions},
+ }
+ 
+ type patch struct {
+@@ -1148,4 +1149,34 @@ func patchStorageZfsUnsetInvalidBlockSettingsV2(_ string, d *Daemon) error {
+ 	return nil
+ }
+ 
++// patchDefaultStoragePermissions re-applies the default modes to all storage pools.
++func patchDefaultStoragePermissions(_ string, d *Daemon) error {
++	s := d.State()
++
++	pools, err := s.DB.Cluster.GetStoragePoolNames()
++	if err != nil {
++		// Skip the rest of the patch if no storage pools were found.
++		if api.StatusErrorCheck(err, http.StatusNotFound) {
++			return nil
++		}
++
++		return fmt.Errorf("Failed getting storage pool names: %w", err)
++	}
++
++	for _, pool := range pools {
++		for _, volEntry := range storageDrivers.BaseDirectories {
++			for _, volDir := range volEntry.Paths {
++				path := storageDrivers.GetPoolMountPath(pool) + "/" + volDir
++
++				err := os.Chmod(path, volEntry.Mode)
++				if err != nil && !errors.Is(err, fs.ErrNotExist) {
++					return fmt.Errorf("Failed to set directory mode %q: %w", path, err)
++				}
++			}
++		}
++	}
++
++	return nil
++}
++
+ // Patches end here
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	2025-10-02 16:23:38.000000000 +0000
+++ lxd-5.0.2+git20231211.1364ae4/debian/patches/series	2025-11-11 15:25:08.000000000 +0000
@@ -9,8 +9,10 @@
 010-cherry-pick-update-test-cert.patch
 011-newer-qemu-fixes.patch
 012-fix-issues-with-old-nvram.patch
+013-cherry-pick-fix-idmapping.patch
 100-CVE-2025-54293.patch
 101-CVE-2025-54287.patch
 102-CVE-2025-54288.patch
 103a-CVE-2025-54286.patch
 103b-CVE-2025-54286.patch
+104-GHSA-56mx-8g9f-5crf.patch

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


Reply to: