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
203 changes: 203 additions & 0 deletions SPECS/vitess/CVE-2026-27969.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
From 7d6f34eec3d29a0e7b69d0180bbaa1646d80e1ad Mon Sep 17 00:00:00 2001
From: AllSpark <allspark@microsoft.com>
Date: Mon, 2 Mar 2026 20:32:06 +0000
Subject: [PATCH] mysqlctl: use fileutil.SafePathJoin to validate FileEntry
paths and prevent path traversal; add tests for fullPath; add SafePathJoin
implementation

Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
Upstream-reference: https://github.com/vitessio/vitess/pull/19470.diff
---
go/fileutil/safe_path_join.go | 47 ++++++++++++
go/vt/mysqlctl/builtinbackupengine.go | 7 +-
go/vt/mysqlctl/builtinbackupengine_test.go | 88 ++++++++++++++++++++++
3 files changed, 140 insertions(+), 2 deletions(-)
create mode 100644 go/fileutil/safe_path_join.go

diff --git a/go/fileutil/safe_path_join.go b/go/fileutil/safe_path_join.go
new file mode 100644
index 0000000..6d1a1ef
--- /dev/null
+++ b/go/fileutil/safe_path_join.go
@@ -0,0 +1,47 @@
+/*
+Copyright 2024 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 fileutil contains utility functions related to files and paths.
+package fileutil
+
+import (
+ "errors"
+ "path"
+ "strings"
+)
+
+// ErrInvalidJoinedPath is returned when SafePathJoin detects that the joined
+// path escapes the provided base directory.
+var ErrInvalidJoinedPath = errors.New("invalid joined path: escapes base directory")
+
+// SafePathJoin joins base and rel into a cleaned path, ensuring that rel does
+// not escape the base directory. It rejects path traversal sequences that would
+// resolve outside of base, e.g. "../../etc/passwd".
+func SafePathJoin(base, rel string) (string, error) {
+ // Clean both base and rel to normalize any redundant segments.
+ cleanBase := path.Clean(base)
+ if cleanBase == "." {
+ cleanBase = ""
+ }
+ joined := path.Clean(path.Join(cleanBase, rel))
+
+ // Ensure the joined path resides within the base directory. It must either
+ // equal the base or have the base followed by a path separator.
+ if cleanBase != "" && joined != cleanBase && !strings.HasPrefix(joined, cleanBase+"/") {
+ return "", ErrInvalidJoinedPath
+ }
+ return joined, nil
+}
diff --git a/go/vt/mysqlctl/builtinbackupengine.go b/go/vt/mysqlctl/builtinbackupengine.go
index cf8c6a1..8fac602 100644
--- a/go/vt/mysqlctl/builtinbackupengine.go
+++ b/go/vt/mysqlctl/builtinbackupengine.go
@@ -35,6 +35,7 @@ import (
"github.com/spf13/pflag"
"golang.org/x/sync/semaphore"

+ "vitess.io/vitess/go/fileutil"
"vitess.io/vitess/go/ioutil"
"vitess.io/vitess/go/mysql"
"vitess.io/vitess/go/vt/concurrency"
@@ -154,7 +155,9 @@ func registerBuiltinBackupEngineFlags(fs *pflag.FlagSet) {
fs.UintVar(&builtinBackupFileWriteBufferSize, "builtinbackup-file-write-buffer-size", builtinBackupFileWriteBufferSize, "write files using an IO buffer of this many bytes. Golang defaults are used when set to 0.")
}

-// fullPath returns the full path of the entry, based on its type
+// fullPath returns the full path of the entry, based on its type.
+// It validates that the resolved path does not escape the base directory
+// via path traversal (e.g. "../../" sequences in fe.Name).
func (fe *FileEntry) fullPath(cnf *Mycnf) (string, error) {
// find the root to use
var root string
@@ -171,7 +174,7 @@ func (fe *FileEntry) fullPath(cnf *Mycnf) (string, error) {
return "", vterrors.Errorf(vtrpc.Code_UNKNOWN, "unknown base: %v", fe.Base)
}

- return path.Join(fe.ParentPath, root, fe.Name), nil
+ return fileutil.SafePathJoin(path.Join(fe.ParentPath, root), fe.Name)
}

// open attempts t oopen the file
diff --git a/go/vt/mysqlctl/builtinbackupengine_test.go b/go/vt/mysqlctl/builtinbackupengine_test.go
index 1d30956..35579bc 100644
--- a/go/vt/mysqlctl/builtinbackupengine_test.go
+++ b/go/vt/mysqlctl/builtinbackupengine_test.go
@@ -67,6 +67,94 @@ func createBackupFiles(root string, fileCount int, ext string) error {
if _, err := f.Write([]byte("hello, world!")); err != nil {
return err
}
+
+func TestFileEntryFullPath(t *testing.T) {
+ cnf := &mysqlctl.Mycnf{
+ DataDir: "/vt/data",
+ InnodbDataHomeDir: "/vt/innodb-data",
+ InnodbLogGroupHomeDir: "/vt/innodb-log",
+ BinLogPath: "/vt/binlogs/mysql-bin",
+ }
+
+ tests := []struct {
+ name string
+ entry mysqlctl.FileEntry
+ wantPath string
+ wantError error
+ }{
+ {
+ name: "valid relative path in DataDir",
+ entry: mysqlctl.FileEntry{Base: "data", Name: "mydb/table1.ibd"},
+ wantPath: "/vt/data/mydb/table1.ibd",
+ },
+ {
+ name: "valid relative path in InnodbDataHomeDir",
+ entry: mysqlctl.FileEntry{Base: "innodb_data_home", Name: "ibdata1"},
+ wantPath: "/vt/innodb-data/ibdata1",
+ },
+ {
+ name: "valid relative path in InnodbLogGroupHomeDir",
+ entry: mysqlctl.FileEntry{Base: "innodb_log_group_home", Name: "ib_logfile0"},
+ wantPath: "/vt/innodb-log/ib_logfile0",
+ },
+ {
+ name: "valid relative path in BinlogDir",
+ entry: mysqlctl.FileEntry{Base: "binlog_dir", Name: "mysql-bin.000001"},
+ wantPath: "/vt/binlogs/mysql-bin.000001",
+ },
+ {
+ name: "valid path with ParentPath",
+ entry: mysqlctl.FileEntry{Base: "data", Name: "mydb/table1.ibd", ParentPath: "/tmp/restore"},
+ wantPath: "/tmp/restore/vt/data/mydb/table1.ibd",
+ },
+ {
+ name: "path traversal escapes base directory",
+ entry: mysqlctl.FileEntry{Base: "data", Name: "../../etc/passwd"},
+ wantError: fileutil.ErrInvalidJoinedPath,
+ },
+ {
+ name: "path traversal with deeper nesting",
+ entry: mysqlctl.FileEntry{Base: "data", Name: "mydb/../../../etc/shadow"},
+ wantError: fileutil.ErrInvalidJoinedPath,
+ },
+ {
+ name: "path traversal to root",
+ entry: mysqlctl.FileEntry{Base: "data", Name: "../../../../../etc/crontab"},
+ wantError: fileutil.ErrInvalidJoinedPath,
+ },
+ {
+ name: "path traversal escapes ParentPath",
+ entry: mysqlctl.FileEntry{Base: "data", Name: "../../../../etc/passwd", ParentPath: "/tmp/restore"},
+ wantError: fileutil.ErrInvalidJoinedPath,
+ },
+ {
+ name: "relative path with dot-dot that stays within base",
+ entry: mysqlctl.FileEntry{Base: "data", Name: "mydb/../mydb/table1.ibd"},
+ wantPath: "/vt/data/mydb/table1.ibd",
+ },
+ }
+
+ // Test unknown base separately since it returns a different error type.
+ t.Run("unknown base", func(t *testing.T) {
+ entry := mysqlctl.FileEntry{Base: "unknown", Name: "file"}
+ _, err := entry.fullPath(cnf)
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), "unknown base")
+ })
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := tt.entry.fullPath(cnf)
+ if tt.wantError != nil {
+ require.ErrorIs(t, err, tt.wantError)
+ } else {
+ require.NoError(t, err)
+ assert.Equal(t, tt.wantPath, got)
+ }
+ })
+ }
+}
+
defer f.Close()
}

--
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: 17.0.7
Release: 12%{?dist}
Release: 13%{?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 @@ -33,6 +33,7 @@ Patch3: CVE-2024-53257.patch
Patch4: CVE-2025-22870.patch
# CVE-2025-22872 is fixed in go net v0.38 by https://github.com/golang/net/commit/e1fcd82abba34df74614020343be8eb1fe85f0d9
Patch5: CVE-2025-22872.patch
Patch6: CVE-2026-27969.patch
BuildRequires: golang

%description
Expand Down Expand Up @@ -102,6 +103,9 @@ go test -v ./go/cmd/... \
%{_bindir}/*

%changelog
* Tue Mar 03 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 17.0.7-13
- Patch for CVE-2026-27969

* Thu Oct 09 2025 Mykhailo Bykhovtsev <mbykhovtsev@microsoft.com> - 17.0.7-12
- Enable debuginfo subpackage generation

Expand Down
Loading