Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 246 additions & 0 deletions SPECS/vitess/CVE-2026-27965.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
From 33a0a5658e79a1226e372630a77ac6d63e8cb0d2 Mon Sep 17 00:00:00 2001
From: Tim Vaillancourt <tim@timvaillancourt.com>
Date: Tue, 24 Feb 2026 20:21:37 +0100
Subject: [PATCH] Restore: make loading compressor commands from `MANIFEST`
opt-in (#19460)

Signed-off-by: Tim Vaillancourt <tim@timvaillancourt.com>
Co-authored-by: Mohamed Hamza <mhamza@fastmail.com>
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
Upstream-reference: https://github.com/vitessio/vitess/commit/4c0173293907af9cb942a6683c465c3f1e9fdb5c.patch
---
.../backup/vtctlbackup/backup_test.go | 1 +
.../backup/vtctlbackup/backup_utils.go | 4 +
.../backup/xtrabackup/xtrabackup_test.go | 1 +
go/vt/mysqlctl/builtinbackupengine.go | 5 +-
go/vt/mysqlctl/compression.go | 18 +++-
.../compression_external_decompressor_test.go | 93 +++++++++++++++++++
go/vt/mysqlctl/xtrabackupengine.go | 5 +-
7 files changed, 118 insertions(+), 9 deletions(-)
create mode 100644 go/vt/mysqlctl/compression_external_decompressor_test.go

diff --git a/go/test/endtoend/backup/vtctlbackup/backup_test.go b/go/test/endtoend/backup/vtctlbackup/backup_test.go
index 92c7a2f..154f2d1 100644
--- a/go/test/endtoend/backup/vtctlbackup/backup_test.go
+++ b/go/test/endtoend/backup/vtctlbackup/backup_test.go
@@ -57,6 +57,7 @@ func TestBuiltinBackupWithExternalZstdCompressionAndManifestedDecompressor(t *te
CompressorEngineName: "external",
ExternalCompressorCmd: "zstd",
ExternalCompressorExt: ".zst",
+ ExternalDecompressorUseManifest: true,
ManifestExternalDecompressorCmd: "zstd -d",
}

diff --git a/go/test/endtoend/backup/vtctlbackup/backup_utils.go b/go/test/endtoend/backup/vtctlbackup/backup_utils.go
index a70d180..3823eef 100644
--- a/go/test/endtoend/backup/vtctlbackup/backup_utils.go
+++ b/go/test/endtoend/backup/vtctlbackup/backup_utils.go
@@ -97,6 +97,7 @@ type CompressionDetails struct {
ExternalCompressorCmd string
ExternalCompressorExt string
ExternalDecompressorCmd string
+ ExternalDecompressorUseManifest bool
ManifestExternalDecompressorCmd string
}

@@ -287,6 +288,9 @@ func getCompressorArgs(cDetails *CompressionDetails) []string {
if cDetails.ExternalDecompressorCmd != "" {
args = append(args, fmt.Sprintf("--external-decompressor=%s", cDetails.ExternalDecompressorCmd))
}
+ if cDetails.ExternalDecompressorUseManifest {
+ args = append(args, "--external-decompressor-use-manifest")
+ }
if cDetails.ManifestExternalDecompressorCmd != "" {
args = append(args, fmt.Sprintf("--manifest-external-decompressor=%s", cDetails.ManifestExternalDecompressorCmd))
}
diff --git a/go/test/endtoend/backup/xtrabackup/xtrabackup_test.go b/go/test/endtoend/backup/xtrabackup/xtrabackup_test.go
index 3402a17..e18f229 100644
--- a/go/test/endtoend/backup/xtrabackup/xtrabackup_test.go
+++ b/go/test/endtoend/backup/xtrabackup/xtrabackup_test.go
@@ -59,6 +59,7 @@ func TestXtrabackupWithExternalZstdCompressionAndManifestedDecompressor(t *testi
CompressorEngineName: "external",
ExternalCompressorCmd: "zstd",
ExternalCompressorExt: ".zst",
+ ExternalDecompressorUseManifest: true,
ManifestExternalDecompressorCmd: "zstd -d",
}

diff --git a/go/vt/mysqlctl/builtinbackupengine.go b/go/vt/mysqlctl/builtinbackupengine.go
index 94ed7bd..fcf7521 100644
--- a/go/vt/mysqlctl/builtinbackupengine.go
+++ b/go/vt/mysqlctl/builtinbackupengine.go
@@ -1120,10 +1120,7 @@ func (be *BuiltinBackupEngine) restoreFile(ctx context.Context, params RestorePa
// for backward compatibility
deCompressionEngine = PgzipCompressor
}
- externalDecompressorCmd := ExternalDecompressorCmd
- if externalDecompressorCmd == "" && bm.ExternalDecompressor != "" {
- externalDecompressorCmd = bm.ExternalDecompressor
- }
+ externalDecompressorCmd := resolveExternalDecompressor(bm.ExternalDecompressor)
if externalDecompressorCmd != "" {
if deCompressionEngine == ExternalCompressor {
deCompressionEngine = externalDecompressorCmd
diff --git a/go/vt/mysqlctl/compression.go b/go/vt/mysqlctl/compression.go
index c2d3cbb..5737b70 100644
--- a/go/vt/mysqlctl/compression.go
+++ b/go/vt/mysqlctl/compression.go
@@ -52,9 +52,10 @@ var (
ExternalCompressorCmd string
ExternalCompressorExt string
ExternalDecompressorCmd string
+ ExternalDecompressorUseManifest bool
ManifestExternalDecompressorCmd string

- errUnsupportedDeCompressionEngine = errors.New("unsupported engine in MANIFEST. You need to provide --external-decompressor if using 'external' compression engine")
+ errUnsupportedDeCompressionEngine = errors.New("unsupported engine in MANIFEST. You need to provide --external-decompressor if using 'external' compression engine. Alternatively, set --external-decompressor-use-manifest to use the decompressor command from the backup manifest, but this is NOT RECOMMENDED as it is a security risk")
errUnsupportedCompressionEngine = errors.New("unsupported engine value for --compression-engine-name. supported values are 'external', 'pgzip', 'pargzip', 'zstd', 'lz4'")

// this is used by getEngineFromExtension() to figure out which engine to use in case the user didn't specify
@@ -77,6 +78,7 @@ func registerBackupCompressionFlags(fs *pflag.FlagSet) {
fs.StringVar(&ExternalCompressorCmd, "external-compressor", ExternalCompressorCmd, "command with arguments to use when compressing a backup.")
fs.StringVar(&ExternalCompressorExt, "external-compressor-extension", ExternalCompressorExt, "extension to use when using an external compressor.")
fs.StringVar(&ExternalDecompressorCmd, "external-decompressor", ExternalDecompressorCmd, "command with arguments to use when decompressing a backup.")
+ fs.BoolVar(&ExternalDecompressorUseManifest, "external-decompressor-use-manifest", ExternalDecompressorUseManifest, "allows the decompressor command stored in the backup manifest to be used at restore time. Enabling this is a security risk: an attacker with write access to the backup storage could modify the manifest to execute arbitrary commands on the tablet as the Vitess user. NOT RECOMMENDED.")
fs.StringVar(&ManifestExternalDecompressorCmd, "manifest-external-decompressor", ManifestExternalDecompressorCmd, "command with arguments to store in the backup manifest when compressing a backup with an external compression engine.")
}

@@ -91,6 +93,20 @@ func getExtensionFromEngine(engine string) (string, error) {
return "", fmt.Errorf("%w %q", errUnsupportedCompressionEngine, engine)
}

+// resolveExternalDecompressor returns the external decompressor command to use
+// at restore time. The CLI flag (--external-decompressor) takes precedence. The
+// backup manifest value is only used when --external-decompressor-use-manifest
+// is explicitly set to true.
+func resolveExternalDecompressor(manifestDecompressor string) string {
+ if ExternalDecompressorCmd != "" {
+ return ExternalDecompressorCmd
+ }
+ if ExternalDecompressorUseManifest && manifestDecompressor != "" {
+ return manifestDecompressor
+ }
+ return ""
+}
+
// Validates if the external decompressor exists and return its path.
func validateExternalCmd(cmd string) (string, error) {
if cmd == "" {
diff --git a/go/vt/mysqlctl/compression_external_decompressor_test.go b/go/vt/mysqlctl/compression_external_decompressor_test.go
new file mode 100644
index 0000000..fd4732a
--- /dev/null
+++ b/go/vt/mysqlctl/compression_external_decompressor_test.go
@@ -0,0 +1,93 @@
+/*
+Copyright 2026 The Vitess Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package mysqlctl
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestResolveExternalDecompressor(t *testing.T) {
+ tests := []struct {
+ name string
+ cliDecompressorCmd string
+ useManifest bool
+ manifestDecompressor string
+ expected string
+ }{
+ {
+ name: "CLI flag takes precedence over manifest",
+ cliDecompressorCmd: "zstd -d",
+ useManifest: true,
+ manifestDecompressor: "gzip -d",
+ expected: "zstd -d",
+ },
+ {
+ name: "CLI flag takes precedence even when use-manifest is false",
+ cliDecompressorCmd: "zstd -d",
+ useManifest: false,
+ manifestDecompressor: "gzip -d",
+ expected: "zstd -d",
+ },
+ {
+ name: "manifest used when use-manifest is true and no CLI flag",
+ cliDecompressorCmd: "",
+ useManifest: true,
+ manifestDecompressor: "gzip -d",
+ expected: "gzip -d",
+ },
+ {
+ name: "manifest ignored when use-manifest is false",
+ cliDecompressorCmd: "",
+ useManifest: false,
+ manifestDecompressor: "gzip -d",
+ expected: "",
+ },
+ {
+ name: "empty when nothing is set",
+ cliDecompressorCmd: "",
+ useManifest: false,
+ manifestDecompressor: "",
+ expected: "",
+ },
+ {
+ name: "empty when use-manifest is true but manifest is empty",
+ cliDecompressorCmd: "",
+ useManifest: true,
+ manifestDecompressor: "",
+ expected: "",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ origCmd := ExternalDecompressorCmd
+ origAllow := ExternalDecompressorUseManifest
+ t.Cleanup(func() {
+ ExternalDecompressorCmd = origCmd
+ ExternalDecompressorUseManifest = origAllow
+ })
+
+ ExternalDecompressorCmd = tt.cliDecompressorCmd
+ ExternalDecompressorUseManifest = tt.useManifest
+
+ result := resolveExternalDecompressor(tt.manifestDecompressor)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}
diff --git a/go/vt/mysqlctl/xtrabackupengine.go b/go/vt/mysqlctl/xtrabackupengine.go
index 3f8491f..7552a90 100644
--- a/go/vt/mysqlctl/xtrabackupengine.go
+++ b/go/vt/mysqlctl/xtrabackupengine.go
@@ -644,10 +644,7 @@ func (be *XtrabackupEngine) extractFiles(ctx context.Context, logger logutil.Log
// then we assign the default value of compressionEngine.
deCompressionEngine = PgzipCompressor
}
- externalDecompressorCmd := ExternalDecompressorCmd
- if externalDecompressorCmd == "" && bm.ExternalDecompressor != "" {
- externalDecompressorCmd = bm.ExternalDecompressor
- }
+ externalDecompressorCmd := resolveExternalDecompressor(bm.ExternalDecompressor)
if externalDecompressorCmd != "" {
if deCompressionEngine == ExternalCompressor {
deCompressionEngine = externalDecompressorCmd
--
2.45.4

6 changes: 5 additions & 1 deletion SPECS/vitess/vitess.spec
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

Name: vitess
Version: 19.0.4
Release: 7%{?dist}
Release: 8%{?dist}
Summary: Database clustering system for horizontal scaling of MySQL
# Upstream license specification: MIT and Apache-2.0
License: MIT and ASL 2.0
Expand Down Expand Up @@ -31,6 +31,7 @@ Patch1: CVE-2024-45339.patch
Patch2: CVE-2025-22868.patch
Patch3: CVE-2025-22870.patch
Patch4: CVE-2024-53257.patch
Patch5: CVE-2026-27965.patch
BuildRequires: golang < 1.23

%description
Expand Down Expand Up @@ -108,6 +109,9 @@ go check -t go/cmd \
%{_bindir}/*

%changelog
* Thu Feb 26 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 19.0.4-8
- Patch for CVE-2026-27965

* Fri Apr 11 2025 Kevin Lockwood <v-klockwood@microsoft.com> - 19.0.4-7
- Add patch for CVE-2024-53257

Expand Down
Loading