Skip to content

Commit 76c9c52

Browse files
authored
Tighten storage pool permissions (from Incus) (stable-5.0) (#16923)
Pulls in fix from lxc/incus#2642 (from #16904 via #16922) Related to lxc/incus#2641 Fix for [GHSA-56mx-8g9f-5crf](GHSA-56mx-8g9f-5crf) CVE-2025-64507
2 parents c9e7ce3 + dd7fe60 commit 76c9c52

7 files changed

Lines changed: 79 additions & 14 deletions

File tree

lxd/patches.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"io/fs"
78
"net/http"
89
"os"
910
"path/filepath"
@@ -83,6 +84,7 @@ var patches = []patch{
8384
{name: "storage_unset_invalid_block_settings", stage: patchPostDaemonStorage, run: patchStorageUnsetInvalidBlockSettings},
8485
{name: "storage_move_custom_iso_block_volumes_v2", stage: patchPostDaemonStorage, run: patchStorageRenameCustomISOBlockVolumesV2},
8586
{name: "storage_unset_invalid_block_settings_v2", stage: patchPostDaemonStorage, run: patchStorageUnsetInvalidBlockSettingsV2},
87+
{name: "pool_fix_default_permissions", stage: patchPostDaemonStorage, run: patchDefaultStoragePermissions},
8688
}
8789

8890
type patch struct {
@@ -1249,4 +1251,34 @@ DELETE FROM storage_volumes_config
12491251
return err
12501252
}
12511253

1254+
// patchDefaultStoragePermissions re-applies the default modes to all storage pools.
1255+
func patchDefaultStoragePermissions(_ string, d *Daemon) error {
1256+
s := d.State()
1257+
1258+
pools, err := s.DB.Cluster.GetStoragePoolNames()
1259+
if err != nil {
1260+
// Skip the rest of the patch if no storage pools were found.
1261+
if api.StatusErrorCheck(err, http.StatusNotFound) {
1262+
return nil
1263+
}
1264+
1265+
return fmt.Errorf("Failed getting storage pool names: %w", err)
1266+
}
1267+
1268+
for _, pool := range pools {
1269+
for _, volEntry := range storageDrivers.BaseDirectories {
1270+
for _, volDir := range volEntry.Paths {
1271+
path := storageDrivers.GetPoolMountPath(pool) + "/" + volDir
1272+
1273+
err := os.Chmod(path, volEntry.Mode)
1274+
if err != nil && !errors.Is(err, fs.ErrNotExist) {
1275+
return fmt.Errorf("Failed to set directory mode %q: %w", path, err)
1276+
}
1277+
}
1278+
}
1279+
}
1280+
1281+
return nil
1282+
}
1283+
12521284
// Patches end here

lxd/storage/backend_lxd.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5116,10 +5116,10 @@ func (b *lxdBackend) RestoreCustomVolume(projectName, volName string, snapshotNa
51165116

51175117
func (b *lxdBackend) createStorageStructure(path string) error {
51185118
for _, volType := range b.driver.Info().VolumeTypes {
5119-
for _, name := range drivers.BaseDirectories[volType] {
5119+
for _, name := range drivers.BaseDirectories[volType].Paths {
51205120
path := filepath.Join(path, name)
5121-
err := os.MkdirAll(path, 0711)
5122-
if err != nil && !os.IsExist(err) {
5121+
err := os.MkdirAll(path, drivers.BaseDirectories[volType].Mode)
5122+
if err != nil && !errors.Is(err, fs.ErrExist) {
51235123
return fmt.Errorf("Failed to create directory %q: %w", path, err)
51245124
}
51255125
}

lxd/storage/drivers/driver_btrfs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ func (d *btrfs) Delete(op *operations.Operation) error {
283283

284284
// Delete potential intermediate btrfs subvolumes.
285285
for _, volType := range d.Info().VolumeTypes {
286-
for _, dir := range BaseDirectories[volType] {
286+
for _, dir := range BaseDirectories[volType].Paths {
287287
path := filepath.Join(GetPoolMountPath(d.name), dir)
288288
if !shared.PathExists(path) {
289289
continue

lxd/storage/drivers/driver_zfs_utils.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,7 @@ func (d *zfs) initialDatasets() []string {
286286

287287
// Iterate over the listed supported volume types.
288288
for _, volType := range d.Info().VolumeTypes {
289-
entries = append(entries, BaseDirectories[volType][0])
290-
entries = append(entries, filepath.Join("deleted", BaseDirectories[volType][0]))
289+
entries = append(entries, BaseDirectories[volType].Paths[0], "deleted/"+BaseDirectories[volType].Paths[0])
291290
}
292291

293292
return entries

lxd/storage/drivers/generic_vfs.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,11 +1153,11 @@ func genericVFSListVolumes(d Driver) ([]Volume, error) {
11531153
poolMountPath := GetPoolMountPath(poolName)
11541154

11551155
for _, volType := range d.Info().VolumeTypes {
1156-
if len(BaseDirectories[volType]) < 1 {
1156+
if len(BaseDirectories[volType].Paths) < 1 {
11571157
return nil, fmt.Errorf("Cannot get base directory name for volume type %q", volType)
11581158
}
11591159

1160-
volTypePath := filepath.Join(poolMountPath, BaseDirectories[volType][0])
1160+
volTypePath := filepath.Join(poolMountPath, BaseDirectories[volType].Paths[0])
11611161
ents, err := os.ReadDir(volTypePath)
11621162
if err != nil {
11631163
return nil, fmt.Errorf("Failed to list directory %q for volume type %q: %w", volTypePath, volType, err)

lxd/storage/drivers/volume.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,18 @@ const ContentTypeISO = ContentType("iso")
7676
// VolumePostHook function returned from a storage action that should be run later to complete the action.
7777
type VolumePostHook func(vol Volume) error
7878

79+
type baseDirectory struct {
80+
Paths []string
81+
Mode os.FileMode
82+
}
83+
7984
// BaseDirectories maps volume types to the expected directories.
80-
var BaseDirectories = map[VolumeType][]string{
81-
VolumeTypeBucket: {"buckets"},
82-
VolumeTypeContainer: {"containers", "containers-snapshots"},
83-
VolumeTypeCustom: {"custom", "custom-snapshots"},
84-
VolumeTypeImage: {"images"},
85-
VolumeTypeVM: {"virtual-machines", "virtual-machines-snapshots"},
85+
var BaseDirectories = map[VolumeType]baseDirectory{
86+
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.
87+
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.
88+
VolumeTypeCustom: {Paths: []string{"custom", "custom-snapshots"}, Mode: 0o700},
89+
VolumeTypeImage: {Paths: []string{"images"}, Mode: 0o700},
90+
VolumeTypeVM: {Paths: []string{"virtual-machines", "virtual-machines-snapshots"}, Mode: 0o700},
8691
}
8792

8893
// Volume represents a storage volume, and provides functions to mount and unmount it.

test/suites/storage.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,35 @@ test_storage() {
2020

2121
lxc storage volume create "$storage_pool" "$storage_volume"
2222

23+
# Test storage directory permissions
24+
25+
# Verify storage pool directory permissions match BaseDirectories expectations.
26+
# We expect:
27+
# - containers, containers-snapshots -> 0711
28+
# - buckets -> 0711
29+
# - custom, custom-snapshots -> 0700
30+
# - images -> 0700
31+
# - virtual-machines, virtual-machines-snapshots -> 0700
32+
pool_path="${LXD_DIR}/storage-pools/${storage_pool}"
33+
34+
declare -A expected_modes
35+
expected_modes[containers]=711
36+
expected_modes[containers-snapshots]=711
37+
if [ "${lxd_backend}" != "ceph" ]; then
38+
expected_modes[buckets]=711 # Buckets are not supported on ceph backend.
39+
fi
40+
expected_modes[custom]=700
41+
expected_modes[custom-snapshots]=700
42+
expected_modes[images]=700
43+
expected_modes[virtual-machines]=700
44+
expected_modes[virtual-machines-snapshots]=700
45+
46+
for dir in "${!expected_modes[@]}"; do
47+
want="${expected_modes[$dir]}"
48+
mode=$(stat -c %a "${pool_path}/${dir}")
49+
[ "${mode}" = "${want}" ]
50+
done
51+
2352
# Test setting description on a storage volume
2453
lxc storage volume show "$storage_pool" "$storage_volume" | sed 's/^description:.*/description: bar/' | lxc storage volume edit "$storage_pool" "$storage_volume"
2554
lxc storage volume show "$storage_pool" "$storage_volume" | grep -q 'description: bar'

0 commit comments

Comments
 (0)