diff --git a/router/router_server_files.go b/router/router_server_files.go index ade9f12..999b0c0 100644 --- a/router/router_server_files.go +++ b/router/router_server_files.go @@ -37,6 +37,15 @@ func getServerFileContents(c *gin.Context) { return } defer f.Close() + // Don't allow a named pipe to be opened. + // + // @see https://github.com/pterodactyl/panel/issues/4059 + if st.Mode()&os.ModeNamedPipe != 0 { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{ + "error": "Cannot open files of this type.", + }) + return + } c.Header("X-Mime-Type", st.Mimetype) c.Header("Content-Length", strconv.Itoa(int(st.Size()))) diff --git a/server/filesystem/filesystem.go b/server/filesystem/filesystem.go index 7ece4de..f4f66b9 100644 --- a/server/filesystem/filesystem.go +++ b/server/filesystem/filesystem.go @@ -115,19 +115,6 @@ func (fs *Filesystem) Touch(p string, flag int) (*os.File, error) { return f, nil } -// Reads a file on the system and returns it as a byte representation in a file -// reader. This is not the most memory efficient usage since it will be reading the -// entirety of the file into memory. -func (fs *Filesystem) Readfile(p string, w io.Writer) error { - file, _, err := fs.File(p) - if err != nil { - return err - } - defer file.Close() - _, err = bufio.NewReader(file).WriteTo(w) - return err -} - // Writefile writes a file to the system. If the file does not already exist one // will be created. This will also properly recalculate the disk space used by // the server when writing new files or modifying existing ones. @@ -492,7 +479,11 @@ func (fs *Filesystem) ListDirectory(p string) ([]Stat, error) { cleanedp, _ = fs.SafePath(filepath.Join(cleaned, f.Name())) } - if cleanedp != "" { + // Don't try to detect the type on a pipe — this will just hang the application and + // you'll never get a response back. + // + // @see https://github.com/pterodactyl/panel/issues/4059 + if cleanedp != "" && f.Mode()&os.ModeNamedPipe == 0 { m, _ = mimetype.DetectFile(filepath.Join(cleaned, f.Name())) } else { // Just pass this for an unknown type because the file could not safely be resolved within diff --git a/server/filesystem/filesystem_test.go b/server/filesystem/filesystem_test.go index 43c2cc2..b56ecec 100644 --- a/server/filesystem/filesystem_test.go +++ b/server/filesystem/filesystem_test.go @@ -1,6 +1,7 @@ package filesystem import ( + "bufio" "bytes" "errors" "math/rand" @@ -44,6 +45,14 @@ type rootFs struct { root string } +func getFileContent(file *os.File) string { + var w bytes.Buffer + if _, err := bufio.NewReader(file).WriteTo(&w); err != nil { + panic(err) + } + return w.String() +} + func (rfs *rootFs) CreateServerFile(p string, c []byte) error { f, err := os.Create(filepath.Join(rfs.root, "/server", p)) @@ -75,54 +84,6 @@ func (rfs *rootFs) reset() { } } -func TestFilesystem_Readfile(t *testing.T) { - g := Goblin(t) - fs, rfs := NewFs() - - g.Describe("Readfile", func() { - buf := &bytes.Buffer{} - - g.It("opens a file if it exists on the system", func() { - err := rfs.CreateServerFileFromString("test.txt", "testing") - g.Assert(err).IsNil() - - err = fs.Readfile("test.txt", buf) - g.Assert(err).IsNil() - g.Assert(buf.String()).Equal("testing") - }) - - g.It("returns an error if the file does not exist", func() { - err := fs.Readfile("test.txt", buf) - g.Assert(err).IsNotNil() - g.Assert(errors.Is(err, os.ErrNotExist)).IsTrue() - }) - - g.It("returns an error if the \"file\" is a directory", func() { - err := os.Mkdir(filepath.Join(rfs.root, "/server/test.txt"), 0o755) - g.Assert(err).IsNil() - - err = fs.Readfile("test.txt", buf) - g.Assert(err).IsNotNil() - g.Assert(IsErrorCode(err, ErrCodeIsDirectory)).IsTrue() - }) - - g.It("cannot open a file outside the root directory", func() { - err := rfs.CreateServerFileFromString("/../test.txt", "testing") - g.Assert(err).IsNil() - - err = fs.Readfile("/../test.txt", buf) - g.Assert(err).IsNotNil() - g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue() - }) - - g.AfterEach(func() { - buf.Truncate(0) - atomic.StoreInt64(&fs.diskUsed, 0) - rfs.reset() - }) - }) -} - func TestFilesystem_Writefile(t *testing.T) { g := Goblin(t) fs, rfs := NewFs() @@ -140,9 +101,10 @@ func TestFilesystem_Writefile(t *testing.T) { err := fs.Writefile("test.txt", r) g.Assert(err).IsNil() - err = fs.Readfile("test.txt", buf) + f, _, err := fs.File("test.txt") g.Assert(err).IsNil() - g.Assert(buf.String()).Equal("test file content") + defer f.Close() + g.Assert(getFileContent(f)).Equal("test file content") g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(r.Size()) }) @@ -152,9 +114,10 @@ func TestFilesystem_Writefile(t *testing.T) { err := fs.Writefile("/some/nested/test.txt", r) g.Assert(err).IsNil() - err = fs.Readfile("/some/nested/test.txt", buf) + f, _, err := fs.File("/some/nested/test.txt") g.Assert(err).IsNil() - g.Assert(buf.String()).Equal("test file content") + defer f.Close() + g.Assert(getFileContent(f)).Equal("test file content") }) g.It("can create a new file inside a nested directory without a trailing slash", func() { @@ -163,9 +126,10 @@ func TestFilesystem_Writefile(t *testing.T) { err := fs.Writefile("some/../foo/bar/test.txt", r) g.Assert(err).IsNil() - err = fs.Readfile("foo/bar/test.txt", buf) + f, _, err := fs.File("foo/bar/test.txt") g.Assert(err).IsNil() - g.Assert(buf.String()).Equal("test file content") + defer f.Close() + g.Assert(getFileContent(f)).Equal("test file content") }) g.It("cannot create a file outside the root directory", func() { @@ -190,28 +154,6 @@ func TestFilesystem_Writefile(t *testing.T) { g.Assert(IsErrorCode(err, ErrCodeDiskSpace)).IsTrue() }) - /*g.It("updates the total space used when a file is appended to", func() { - atomic.StoreInt64(&fs.diskUsed, 100) - - b := make([]byte, 100) - _, _ = rand.Read(b) - - r := bytes.NewReader(b) - err := fs.Writefile("test.txt", r) - g.Assert(err).IsNil() - g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(int64(200)) - - // If we write less data than already exists, we should expect the total - // disk used to be decremented. - b = make([]byte, 50) - _, _ = rand.Read(b) - - r = bytes.NewReader(b) - err = fs.Writefile("test.txt", r) - g.Assert(err).IsNil() - g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(int64(150)) - })*/ - g.It("truncates the file when writing new contents", func() { r := bytes.NewReader([]byte("original data")) err := fs.Writefile("test.txt", r) @@ -221,9 +163,10 @@ func TestFilesystem_Writefile(t *testing.T) { err = fs.Writefile("test.txt", r) g.Assert(err).IsNil() - err = fs.Readfile("test.txt", buf) + f, _, err := fs.File("test.txt") g.Assert(err).IsNil() - g.Assert(buf.String()).Equal("new data") + defer f.Close() + g.Assert(getFileContent(f)).Equal("new data") }) g.AfterEach(func() { diff --git a/server/filesystem/path_test.go b/server/filesystem/path_test.go index 46760c7..ebdb71a 100644 --- a/server/filesystem/path_test.go +++ b/server/filesystem/path_test.go @@ -119,16 +119,6 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) { panic(err) } - g.Describe("Readfile", func() { - g.It("cannot read a file symlinked outside the root", func() { - b := bytes.Buffer{} - - err := fs.Readfile("symlinked.txt", &b) - g.Assert(err).IsNotNil() - g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue() - }) - }) - g.Describe("Writefile", func() { g.It("cannot write to a file symlinked outside the root", func() { r := bytes.NewReader([]byte("testing"))