mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-21 14:46:07 +00:00
Add cap for backup data extraction (#10260)
* add cap for backup data extraction Signed-off-by: Lyndon-Li <lyonghui@vmware.com> * set default extraction size Signed-off-by: Lyndon-Li <lyonghui@vmware.com> * control total size only Signed-off-by: Lyndon-Li <lyonghui@vmware.com> * add doc for max-backup-extraction-size Signed-off-by: Lyndon-Li <lyonghui@vmware.com> --------- Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
This commit is contained in:
@@ -32,14 +32,27 @@ import (
|
||||
// Extractor unzips/extracts a backup tarball to a local
|
||||
// temp directory.
|
||||
type Extractor struct {
|
||||
log logrus.FieldLogger
|
||||
fs filesystem.Interface
|
||||
log logrus.FieldLogger
|
||||
fs filesystem.Interface
|
||||
maxExtractionSize int64
|
||||
totalExtractedSize int64
|
||||
}
|
||||
|
||||
var maxExtractionSize = int64(16) << 30
|
||||
|
||||
// SetMaxExtractionSize sets the maximum extraction size. It is normally called at server startup.
|
||||
func SetMaxExtractionSize(size int64) {
|
||||
if size > 0 {
|
||||
maxExtractionSize = size
|
||||
}
|
||||
}
|
||||
|
||||
func NewExtractor(log logrus.FieldLogger, fs filesystem.Interface) *Extractor {
|
||||
return &Extractor{
|
||||
log: log,
|
||||
fs: fs,
|
||||
log: log,
|
||||
fs: fs,
|
||||
maxExtractionSize: maxExtractionSize,
|
||||
totalExtractedSize: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +109,15 @@ func (e *Extractor) readBackup(tarRdr *tar.Reader) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Enforce maximum extraction size to prevent memory/storage exhaustion and zip bombs.
|
||||
maxSize := e.maxExtractionSize
|
||||
e.totalExtractedSize += header.Size
|
||||
if e.totalExtractedSize > maxSize {
|
||||
err := fmt.Errorf("decompressed backup exceeds maximum allowed size of %d bytes", maxSize)
|
||||
e.log.Infof("error checking extracted size: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
target, err := sanitizeArchivePath(dir, header.Name)
|
||||
if err != nil {
|
||||
e.log.Infof("error sanitizing archive path: %s", err.Error())
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
@@ -113,6 +114,66 @@ func TestUnzipAndExtractBackupRejectsPathTraversal(t *testing.T) {
|
||||
require.Contains(t, err.Error(), "invalid archive path")
|
||||
}
|
||||
|
||||
func TestUnzipAndExtractBackupRejectsLargeFile(t *testing.T) {
|
||||
SetMaxExtractionSize(1024)
|
||||
defer SetMaxExtractionSize(16 * 1024 * 1024 * 1024)
|
||||
ext := NewExtractor(test.NewLogger(), test.NewFakeFileSystem())
|
||||
|
||||
var buf bytes.Buffer
|
||||
gzw := gzip.NewWriter(&buf)
|
||||
tw := tar.NewWriter(gzw)
|
||||
|
||||
data := make([]byte, 2048) // 2KB data
|
||||
err := tw.WriteHeader(&tar.Header{
|
||||
Name: "large.txt",
|
||||
Mode: 0600,
|
||||
Typeflag: tar.TypeReg,
|
||||
Size: int64(len(data)),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = tw.Write(data)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, tw.Close())
|
||||
require.NoError(t, gzw.Close())
|
||||
|
||||
_, err = ext.UnzipAndExtractBackup(&buf)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "decompressed backup exceeds maximum allowed size")
|
||||
}
|
||||
|
||||
func TestUnzipAndExtractBackupRejectsManySmallFiles(t *testing.T) {
|
||||
SetMaxExtractionSize(1024)
|
||||
defer SetMaxExtractionSize(16 * 1024 * 1024 * 1024)
|
||||
ext := NewExtractor(test.NewLogger(), test.NewFakeFileSystem())
|
||||
|
||||
var buf bytes.Buffer
|
||||
gzw := gzip.NewWriter(&buf)
|
||||
tw := tar.NewWriter(gzw)
|
||||
|
||||
// Create 100 files of 20 bytes each (total 2000 bytes, exceeding the 1024 byte limit)
|
||||
for i := 0; i < 100; i++ {
|
||||
data := make([]byte, 20)
|
||||
err := tw.WriteHeader(&tar.Header{
|
||||
Name: fmt.Sprintf("small_%d.txt", i),
|
||||
Mode: 0600,
|
||||
Typeflag: tar.TypeReg,
|
||||
Size: int64(len(data)),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = tw.Write(data)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
require.NoError(t, tw.Close())
|
||||
require.NoError(t, gzw.Close())
|
||||
|
||||
_, err := ext.UnzipAndExtractBackup(&buf)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "decompressed backup exceeds maximum allowed size")
|
||||
}
|
||||
|
||||
func createArchive(files []string, fs filesystem.Interface) (string, error) {
|
||||
outName := "output.tar.gz"
|
||||
out, err := fs.Create(outName)
|
||||
|
||||
@@ -183,6 +183,7 @@ type Config struct {
|
||||
ConcurrentBackups int
|
||||
GlobalBackupVolumePoliciesConfigMap string
|
||||
DefaultResourceModifierConfigMap string
|
||||
MaxBackupExtractionSize int
|
||||
}
|
||||
|
||||
func GetDefaultConfig() *Config {
|
||||
@@ -289,4 +290,10 @@ func (c *Config) BindFlags(flags *pflag.FlagSet) {
|
||||
c.DefaultResourceModifierConfigMap,
|
||||
"The name of a ConfigMap in the Velero namespace containing default resource modifier rules applied to all restores. Ignored when a per-restore resource modifier is specified.",
|
||||
)
|
||||
flags.IntVar(
|
||||
&c.MaxBackupExtractionSize,
|
||||
"max-backup-extraction-size",
|
||||
c.MaxBackupExtractionSize,
|
||||
"Maximum size of a backup extraction in megabytes. If not set, default value (16GB) will be used.",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ import (
|
||||
"github.com/vmware-tanzu/velero/internal/storage"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
velerov2alpha1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
|
||||
"github.com/vmware-tanzu/velero/pkg/archive"
|
||||
"github.com/vmware-tanzu/velero/pkg/backup"
|
||||
"github.com/vmware-tanzu/velero/pkg/buildinfo"
|
||||
"github.com/vmware-tanzu/velero/pkg/client"
|
||||
@@ -935,6 +936,11 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
|
||||
}
|
||||
}
|
||||
|
||||
if s.config.MaxBackupExtractionSize > 0 {
|
||||
s.logger.Infof("Setting backup data extraction cap as %v MB", s.config.MaxBackupExtractionSize)
|
||||
archive.SetMaxExtractionSize(int64(s.config.MaxBackupExtractionSize) * 1024 * 1024)
|
||||
}
|
||||
|
||||
s.logger.Info("Server starting...")
|
||||
|
||||
if err := s.mgr.Start(s.ctx); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user