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 <qiujian@yunionyun.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jian Qiu
2026-09-10 11:55:18 +08:00
committed by GitHub
parent cb3e3cbb08
commit c24383dd63
3 changed files with 54 additions and 3 deletions

View File

@@ -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"

View File

@@ -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)

View File

@@ -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")
}
}