From c24383dd637ff7ad02c01a39a3f9183f901dc850 Mon Sep 17 00:00:00 2001 From: Jian Qiu Date: Thu, 10 Sep 2026 11:55:18 +0800 Subject: [PATCH] fix(image): reject images that declare a backing file (#25630) Treat a non-empty backing filename as unsupported on upload, convert, probe, and ceph cache. Co-authored-by: Qiu Jian Co-authored-by: Cursor --- pkg/image/models/images.go | 30 +++++++++++++++++++++++++++++- pkg/util/qemuimg/qemuimg.go | 16 ++++++++++++++-- pkg/util/qemuimg/qemuimg_test.go | 11 +++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/pkg/image/models/images.go b/pkg/image/models/images.go index 26ac7955da..a7f1368885 100644 --- a/pkg/image/models/images.go +++ b/pkg/image/models/images.go @@ -569,6 +569,9 @@ func (self *SImage) SaveImageFromStream(reader io.Reader, totalSize int64, calCh if err != nil { return errors.Wrapf(err, "NewQemuImage %s", localPath) } + if err := img.CheckNoBackingFile(); err != nil { + return err + } format = string(img.String2ImageFormat()) virtualSizeBytes = img.SizeBytes @@ -1217,7 +1220,14 @@ func (self *SImage) GetNewLocation(newLocalPath string) string { } func (self *SImage) getQemuImage() (*qemuimg.SQemuImage, error) { - return qemuimg.NewQemuImageWithIOLevel(self.GetLocalLocation(), qemuimg.IONiceIdle) + img, err := qemuimg.NewQemuImageWithIOLevel(self.GetLocalLocation(), qemuimg.IONiceIdle) + if err != nil { + return nil, err + } + if err := img.CheckNoBackingFile(); err != nil { + return nil, err + } + return img, nil } func (self *SImage) StopTorrents() { @@ -1775,6 +1785,13 @@ func (image *SImage) doProbeImageInfo(ctx context.Context, userCred mcclient.Tok if len(diskPath) == 0 { return false, errors.Wrap(httperrors.ErrNotFound, "disk file not found") } + qimg, err := qemuimg.NewQemuImage(diskPath) + if err != nil { + return false, errors.Wrap(err, "NewQemuImage") + } + if err := qimg.CheckNoBackingFile(); err != nil { + return false, err + } if deployclient.GetDeployClient() == nil { return false, fmt.Errorf("deploy client not init") } @@ -2231,6 +2248,17 @@ func (img *SImage) cacheToCephStorages(ctx context.Context) { } if cachedRbdimgStorageId == "" { // do cache img to ceph storage + if fileutils2.Exists(localPath) { + qimg, err := qemuimg.NewQemuImage(localPath) + if err != nil { + log.Errorf("skip cache img %s: NewQemuImage %s", img.Id, err) + return + } + if err := qimg.CheckNoBackingFile(); err != nil { + log.Errorf("skip cache img %s: %s", img.Id, err) + return + } + } for storageId := range storageCachedImages { storageConf := cephStorages.StorageIdConf[storageId] imgTmpName := "image_cache_" + img.Id + ".tmp" diff --git a/pkg/util/qemuimg/qemuimg.go b/pkg/util/qemuimg/qemuimg.go index dba5fc74bf..9562497e55 100644 --- a/pkg/util/qemuimg/qemuimg.go +++ b/pkg/util/qemuimg/qemuimg.go @@ -37,7 +37,8 @@ import ( ) var ( - ErrUnsupportedFormat = errors.Error("unsupported format") + ErrUnsupportedFormat = errors.Error("unsupported format") + ErrBackingFileNotAllowed = errors.Error("image backing file is not allowed") convertWorkInOrder = false convertCoroutines = 16 @@ -230,7 +231,11 @@ func (img *SQemuImage) parse() error { img.ClusterSize = info.ClusterSize img.Compat = info.FormatSpecific.Data.Compat img.Encrypted = info.Encrypted - img.BackFilePath, err = ParseQemuFilepath(info.FullBackingFilename) + backing := info.FullBackingFilename + if backing == "" { + backing = info.BackingFilename + } + img.BackFilePath, err = ParseQemuFilepath(backing) if err != nil { return errors.Wrap(err, "ParseQemuFilepath") } @@ -267,6 +272,13 @@ func (img *SQemuImage) IsChained() bool { return len(img.BackFilePath) > 0 } +func (img *SQemuImage) CheckNoBackingFile() error { + if img.IsChained() { + return ErrBackingFileNotAllowed + } + return nil +} + func (img *SQemuImage) GetBackingChain() ([]string, error) { if len(img.BackFilePath) > 0 { backImg, err := NewQemuImage(img.BackFilePath) diff --git a/pkg/util/qemuimg/qemuimg_test.go b/pkg/util/qemuimg/qemuimg_test.go index a72829b7cb..da14afd335 100644 --- a/pkg/util/qemuimg/qemuimg_test.go +++ b/pkg/util/qemuimg/qemuimg_test.go @@ -184,3 +184,14 @@ func TestParseBackingFile(t *testing.T) { t.Errorf("want: %s got: %s", want, path) } } + +func TestCheckNoBackingFile(t *testing.T) { + img := &SQemuImage{} + if err := img.CheckNoBackingFile(); err != nil { + t.Fatalf("standalone image: %v", err) + } + img.BackFilePath = "nbd://127.0.0.1:10809/disk" + if err := img.CheckNoBackingFile(); err == nil { + t.Fatal("expected error when backing file is set") + } +}