server(filesystem): rebuild everything imaginable
This wonderfully large commit replaces basically everything under the `server/filesystem` package, re-implementing essentially everything. This is related to https://github.com/pterodactyl/wings/security/advisories/GHSA-494h-9924-xww9 If any vulnerabilities related to symlinks persist after this commit, I will be very upset. Signed-off-by: Matthew Penner <me@matthewp.io>
This commit is contained in:
@@ -167,13 +167,8 @@ func (dl *Download) Execute() error {
|
||||
return errors.New("downloader: got bad response status from endpoint: " + res.Status)
|
||||
}
|
||||
|
||||
// If there is a Content-Length header on this request go ahead and check that we can
|
||||
// even write the whole file before beginning this process. If there is no header present
|
||||
// we'll just have to give it a spin and see how it goes.
|
||||
if res.ContentLength > 0 {
|
||||
if err := dl.server.Filesystem().HasSpaceFor(res.ContentLength); err != nil {
|
||||
return errors.WrapIf(err, "downloader: failed to write file: not enough space")
|
||||
}
|
||||
if res.ContentLength < 1 {
|
||||
return errors.New("downloader: request is missing ContentLength")
|
||||
}
|
||||
|
||||
if dl.req.UseHeader {
|
||||
@@ -200,8 +195,10 @@ func (dl *Download) Execute() error {
|
||||
p := dl.Path()
|
||||
dl.server.Log().WithField("path", p).Debug("writing remote file to disk")
|
||||
|
||||
// Write the file while tracking the progress, Write will check that the
|
||||
// size of the file won't exceed the disk limit.
|
||||
r := io.TeeReader(res.Body, dl.counter(res.ContentLength))
|
||||
if err := dl.server.Filesystem().Writefile(p, r); err != nil {
|
||||
if err := dl.server.Filesystem().Write(p, r, res.ContentLength, 0o644); err != nil {
|
||||
return errors.WrapIf(err, "downloader: failed to write file to server directory")
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -45,6 +45,8 @@ func getDownloadBackup(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// The use of `os` here is safe as backups are not stored within server
|
||||
// accessible directories.
|
||||
f, err := os.Open(b.Path())
|
||||
if err != nil {
|
||||
middleware.CaptureAndAbort(c, err)
|
||||
@@ -76,26 +78,18 @@ func getDownloadFile(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
p, _ := s.Filesystem().SafePath(token.FilePath)
|
||||
st, err := os.Stat(p)
|
||||
// If there is an error or we're somehow trying to download a directory, just
|
||||
// respond with the appropriate error.
|
||||
if err != nil {
|
||||
middleware.CaptureAndAbort(c, err)
|
||||
return
|
||||
} else if st.IsDir() {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
||||
"error": "The requested resource was not found on this server.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(p)
|
||||
f, st, err := s.Filesystem().File(token.FilePath)
|
||||
if err != nil {
|
||||
middleware.CaptureAndAbort(c, err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
if st.IsDir() {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
|
||||
"error": "The requested resource was not found on this server.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Length", strconv.Itoa(int(st.Size())))
|
||||
c.Header("Content-Disposition", "attachment; filename="+strconv.Quote(st.Name()))
|
||||
|
||||
@@ -228,6 +228,7 @@ func deleteServer(c *gin.Context) {
|
||||
// In addition, servers with large amounts of files can take some time to finish deleting,
|
||||
// so we don't want to block the HTTP call while waiting on this.
|
||||
go func(p string) {
|
||||
_ = s.Filesystem().UnixFS().Close()
|
||||
if err := os.RemoveAll(p); err != nil {
|
||||
log.WithFields(log.Fields{"path": p, "error": err}).Warn("failed to remove server files during deletion process")
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
// getServerFileContents returns the contents of a file on the server.
|
||||
func getServerFileContents(c *gin.Context) {
|
||||
s := middleware.ExtractServer(c)
|
||||
p := "/" + strings.TrimLeft(c.Query("file"), "/")
|
||||
p := strings.TrimLeft(c.Query("file"), "/")
|
||||
f, st, err := s.Filesystem().File(p)
|
||||
if err != nil {
|
||||
middleware.CaptureAndAbort(c, err)
|
||||
@@ -129,7 +129,6 @@ func putServerRenameFiles(c *gin.Context) {
|
||||
}
|
||||
if err := fs.Rename(pf, pt); err != nil {
|
||||
// Return nil if the error is an is not exists.
|
||||
// NOTE: os.IsNotExist() does not work if the error is wrapped.
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
s.Log().WithField("error", err).
|
||||
WithField("from_path", pf).
|
||||
@@ -239,7 +238,15 @@ func postServerWriteFile(c *gin.Context) {
|
||||
middleware.CaptureAndAbort(c, err)
|
||||
return
|
||||
}
|
||||
if err := s.Filesystem().Writefile(f, c.Request.Body); err != nil {
|
||||
|
||||
if c.Request.ContentLength < 1 {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Missing Content-Length",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.Filesystem().Write(f, c.Request.Body, c.Request.ContentLength, 0o644); err != nil {
|
||||
if filesystem.IsErrorCode(err, filesystem.ErrCodeIsDirectory) {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Cannot write file, name conflicts with an existing directory by the same name.",
|
||||
@@ -589,15 +596,9 @@ func postServerUploadFiles(c *gin.Context) {
|
||||
}
|
||||
|
||||
for _, header := range headers {
|
||||
p, err := s.Filesystem().SafePath(filepath.Join(directory, header.Filename))
|
||||
if err != nil {
|
||||
middleware.CaptureAndAbort(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// We run this in a different method so I can use defer without any of
|
||||
// the consequences caused by calling it in a loop.
|
||||
if err := handleFileUpload(p, s, header); err != nil {
|
||||
if err := handleFileUpload(filepath.Join(directory, header.Filename), s, header); err != nil {
|
||||
middleware.CaptureAndAbort(c, err)
|
||||
return
|
||||
} else {
|
||||
@@ -619,7 +620,8 @@ func handleFileUpload(p string, s *server.Server, header *multipart.FileHeader)
|
||||
if err := s.Filesystem().IsIgnored(p); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Filesystem().Writefile(p, file); err != nil {
|
||||
|
||||
if err := s.Filesystem().Write(p, file, header.Size, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -106,6 +106,7 @@ func postTransfers(c *gin.Context) {
|
||||
if !successful && err != nil {
|
||||
// Delete all extracted files.
|
||||
go func(trnsfr *transfer.Transfer) {
|
||||
_ = trnsfr.Server.Filesystem().UnixFS().Close()
|
||||
if err := os.RemoveAll(trnsfr.Server.Filesystem().Path()); err != nil && !os.IsNotExist(err) {
|
||||
trnsfr.Log().WithError(err).Warn("failed to delete local server files")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user