Skip to content

Commit 43b3496

Browse files
committed
server(filesystem): fix archiver path matching
Closes pterodactyl/panel#4630
1 parent 38c69eb commit 43b3496

File tree

2 files changed

+141
-3
lines changed

2 files changed

+141
-3
lines changed

server/filesystem/archive.go

+16-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package filesystem
33
import (
44
"archive/tar"
55
"context"
6+
"fmt"
67
"io"
78
"io/fs"
89
"os"
@@ -66,6 +67,8 @@ type Archive struct {
6667

6768
// Files specifies the files to archive, this takes priority over the Ignore option, if
6869
// unspecified, all files in the BasePath will be archived unless Ignore is set.
70+
//
71+
// All items in Files must be absolute within BasePath.
6972
Files []string
7073

7174
// Progress wraps the writer of the archive to pass through the progress tracker.
@@ -97,6 +100,14 @@ func (a *Archive) Create(ctx context.Context, dst string) error {
97100

98101
// Stream .
99102
func (a *Archive) Stream(ctx context.Context, w io.Writer) error {
103+
for _, f := range a.Files {
104+
if strings.HasPrefix(f, a.BasePath) {
105+
continue
106+
}
107+
108+
return fmt.Errorf("archive: all entries in Files must be absolute and within BasePath: %s\n", f)
109+
}
110+
100111
// Choose which compression level to use based on the compression_level configuration option
101112
var compressionLevel int
102113
switch config.Get().System.Backups.CompressionLevel {
@@ -190,9 +201,11 @@ func (a *Archive) callback(tw *TarProgress, opts ...func(path string, relative s
190201
func (a *Archive) withFilesCallback(tw *TarProgress) func(path string, de *godirwalk.Dirent) error {
191202
return a.callback(tw, func(p string, rp string) error {
192203
for _, f := range a.Files {
193-
// If the given doesn't match, or doesn't have the same prefix continue
194-
// to the next item in the loop.
195-
if p != f && !strings.HasPrefix(strings.TrimSuffix(p, "/")+"/", f) {
204+
// Allow exact file matches, otherwise check if file is within a parent directory.
205+
//
206+
// The slashes are added in the prefix checks to prevent partial name matches from being
207+
// included in the archive.
208+
if f != p && !strings.HasPrefix(strings.TrimSuffix(p, "/")+"/", strings.TrimSuffix(f, "/")+"/") {
196209
continue
197210
}
198211

server/filesystem/archive_test.go

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package filesystem
2+
3+
import (
4+
"context"
5+
iofs "io/fs"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"testing"
10+
11+
. "github.com/franela/goblin"
12+
"github.com/mholt/archiver/v4"
13+
)
14+
15+
func TestArchive_Stream(t *testing.T) {
16+
g := Goblin(t)
17+
fs, rfs := NewFs()
18+
19+
g.Describe("Archive", func() {
20+
g.AfterEach(func() {
21+
// Reset the filesystem after each run.
22+
rfs.reset()
23+
})
24+
25+
g.It("throws an error when passed invalid file paths", func() {
26+
a := &Archive{
27+
BasePath: fs.Path(),
28+
Files: []string{
29+
// To use the archiver properly, this needs to be filepath.Join(BasePath, "yeet")
30+
// However, this test tests that we actually validate that behavior.
31+
"yeet",
32+
},
33+
}
34+
35+
g.Assert(a.Create(context.Background(), "")).IsNotNil()
36+
})
37+
38+
g.It("creates archive with intended files", func() {
39+
g.Assert(fs.CreateDirectory("test", "/")).IsNil()
40+
g.Assert(fs.CreateDirectory("test2", "/")).IsNil()
41+
42+
err := fs.Writefile("test/file.txt", strings.NewReader("hello, world!\n"))
43+
g.Assert(err).IsNil()
44+
45+
err = fs.Writefile("test2/file.txt", strings.NewReader("hello, world!\n"))
46+
g.Assert(err).IsNil()
47+
48+
err = fs.Writefile("test_file.txt", strings.NewReader("hello, world!\n"))
49+
g.Assert(err).IsNil()
50+
51+
err = fs.Writefile("test_file.txt.old", strings.NewReader("hello, world!\n"))
52+
g.Assert(err).IsNil()
53+
54+
a := &Archive{
55+
BasePath: fs.Path(),
56+
Files: []string{
57+
filepath.Join(fs.Path(), "test"),
58+
filepath.Join(fs.Path(), "test_file.txt"),
59+
},
60+
}
61+
62+
// Create the archive
63+
archivePath := filepath.Join(rfs.root, "archive.tar.gz")
64+
g.Assert(a.Create(context.Background(), archivePath)).IsNil()
65+
66+
// Ensure the archive exists.
67+
_, err = os.Stat(archivePath)
68+
g.Assert(err).IsNil()
69+
70+
// Open the archive.
71+
genericFs, err := archiver.FileSystem(archivePath)
72+
g.Assert(err).IsNil()
73+
74+
// Assert that we are opening an archive.
75+
afs, ok := genericFs.(archiver.ArchiveFS)
76+
g.Assert(ok).IsTrue()
77+
78+
// Get the names of the files recursively from the archive.
79+
files, err := getFiles(afs, ".")
80+
g.Assert(err).IsNil()
81+
82+
// Ensure the files in the archive match what we are expecting.
83+
g.Assert(files).Equal([]string{
84+
"test_file.txt",
85+
"test/file.txt",
86+
})
87+
})
88+
})
89+
}
90+
91+
func getFiles(f iofs.ReadDirFS, name string) ([]string, error) {
92+
var v []string
93+
94+
entries, err := f.ReadDir(name)
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
for _, e := range entries {
100+
entryName := e.Name()
101+
if name != "." {
102+
entryName = filepath.Join(name, entryName)
103+
}
104+
105+
if e.IsDir() {
106+
files, err := getFiles(f, entryName)
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
if files == nil {
112+
return nil, nil
113+
}
114+
115+
for _, f := range files {
116+
v = append(v, f)
117+
}
118+
continue
119+
}
120+
121+
v = append(v, entryName)
122+
}
123+
124+
return v, nil
125+
}

0 commit comments

Comments
 (0)