Compare commits
7 Commits
release/v1
...
release/v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4902051abd | ||
|
|
4d9fee383f | ||
|
|
429ac62dba | ||
|
|
020abec6f2 | ||
|
|
dac9685298 | ||
|
|
519d38f238 | ||
|
|
1d17233d6d |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.11.4
|
||||||
|
### Fixed
|
||||||
|
* CVE-2023-25168
|
||||||
|
|
||||||
|
## v1.11.3
|
||||||
|
### Fixed
|
||||||
|
* CVE-2023-25152
|
||||||
|
|
||||||
## v1.11.2
|
## v1.11.2
|
||||||
### Fixed
|
### Fixed
|
||||||
* Backups being restored from remote storage (s3) erroring out due to a closed stream.
|
* Backups being restored from remote storage (s3) erroring out due to a closed stream.
|
||||||
@@ -60,6 +68,14 @@
|
|||||||
* Archive progress is now reported correctly.
|
* Archive progress is now reported correctly.
|
||||||
* Labels for containers can now be set by the Panel.
|
* Labels for containers can now be set by the Panel.
|
||||||
|
|
||||||
|
## v1.7.4
|
||||||
|
### Fixed
|
||||||
|
* CVE-2023-25168
|
||||||
|
|
||||||
|
## v1.7.3
|
||||||
|
### Fixed
|
||||||
|
* CVE-2023-25152
|
||||||
|
|
||||||
## v1.7.2
|
## v1.7.2
|
||||||
### Fixed
|
### Fixed
|
||||||
* The S3 backup driver now supports Cloudflare R2
|
* The S3 backup driver now supports Cloudflare R2
|
||||||
|
|||||||
@@ -15,14 +15,12 @@ dependencies, and allowing users to authenticate with the same credentials they
|
|||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
I would like to extend my sincere thanks to the following sponsors for helping find Pterodactyl's developement.
|
I would like to extend my sincere thanks to the following sponsors for helping find Pterodactyl's development.
|
||||||
[Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi)
|
[Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi)
|
||||||
|
|
||||||
| Company | About |
|
| Company | About |
|
||||||
|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| [**WISP**](https://wisp.gg) | Extra features. |
|
| [**WISP**](https://wisp.gg) | Extra features. |
|
||||||
| [**Fragnet**](https://fragnet.net) | Providing low latency, high-end game hosting solutions to gamers, game studios and eSports platforms. |
|
|
||||||
| [**RocketNode**](https://rocketnode.com/) | Innovative game server hosting combined with a straightforward control panel, affordable prices, and Rocket-Fast support. |
|
|
||||||
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
||||||
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
|
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
|
||||||
| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! |
|
| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! |
|
||||||
@@ -30,6 +28,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f
|
|||||||
| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa. |
|
| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa. |
|
||||||
| [**Pterodactyl Market**](https://pterodactylmarket.com/) | Pterodactyl Market is a one-and-stop shop for Pterodactyl. In our market, you can find Add-ons, Themes, Eggs, and more for Pterodactyl. |
|
| [**Pterodactyl Market**](https://pterodactylmarket.com/) | Pterodactyl Market is a one-and-stop shop for Pterodactyl. In our market, you can find Add-ons, Themes, Eggs, and more for Pterodactyl. |
|
||||||
| [**UltraServers**](https://ultraservers.com/) | Deploy premium games hosting with the click of a button. Manage and swap games with ease and let us take care of the rest. We currently support Minecraft, Rust, ARK, 7 Days to Die, Garys MOD, CS:GO, Satisfactory and others. |
|
| [**UltraServers**](https://ultraservers.com/) | Deploy premium games hosting with the click of a button. Manage and swap games with ease and let us take care of the rest. We currently support Minecraft, Rust, ARK, 7 Days to Die, Garys MOD, CS:GO, Satisfactory and others. |
|
||||||
|
| [**Realms Hosting**](https://realmshosting.com/) | Want to build your Gaming Empire? Use Realms Hosting today to kick start your game server hosting with outstanding DDOS Protection, 24/7 Support, Cheap Prices and a Custom Control Panel. | |
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ func (fs *Filesystem) Writefile(p string, r io.Reader) error {
|
|||||||
// Adjust the disk usage to account for the old size and the new size of the file.
|
// Adjust the disk usage to account for the old size and the new size of the file.
|
||||||
fs.addDisk(sz - currentSize)
|
fs.addDisk(sz - currentSize)
|
||||||
|
|
||||||
return fs.Chown(cleaned)
|
return fs.unsafeChown(cleaned)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new directory (name) at a specified path (p) for the server.
|
// Creates a new directory (name) at a specified path (p) for the server.
|
||||||
@@ -223,7 +223,12 @@ func (fs *Filesystem) Chown(path string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return fs.unsafeChown(cleaned)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsafeChown chowns the given path, without checking if the path is safe. This should only be used
|
||||||
|
// when the path has already been checked.
|
||||||
|
func (fs *Filesystem) unsafeChown(path string) error {
|
||||||
if fs.isTest {
|
if fs.isTest {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -232,19 +237,19 @@ func (fs *Filesystem) Chown(path string) error {
|
|||||||
gid := config.Get().System.User.Gid
|
gid := config.Get().System.User.Gid
|
||||||
|
|
||||||
// Start by just chowning the initial path that we received.
|
// Start by just chowning the initial path that we received.
|
||||||
if err := os.Chown(cleaned, uid, gid); err != nil {
|
if err := os.Chown(path, uid, gid); err != nil {
|
||||||
return errors.Wrap(err, "server/filesystem: chown: failed to chown path")
|
return errors.Wrap(err, "server/filesystem: chown: failed to chown path")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is not a directory we can now return from the function, there is nothing
|
// If this is not a directory we can now return from the function, there is nothing
|
||||||
// left that we need to do.
|
// left that we need to do.
|
||||||
if st, err := os.Stat(cleaned); err != nil || !st.IsDir() {
|
if st, err := os.Stat(path); err != nil || !st.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this was a directory, begin walking over its contents recursively and ensure that all
|
// If this was a directory, begin walking over its contents recursively and ensure that all
|
||||||
// of the subfiles and directories get their permissions updated as well.
|
// of the subfiles and directories get their permissions updated as well.
|
||||||
err = godirwalk.Walk(cleaned, &godirwalk.Options{
|
err := godirwalk.Walk(path, &godirwalk.Options{
|
||||||
Unsorted: true,
|
Unsorted: true,
|
||||||
Callback: func(p string, e *godirwalk.Dirent) error {
|
Callback: func(p string, e *godirwalk.Dirent) error {
|
||||||
// Do not attempt to chown a symlink. Go's os.Chown function will affect the symlink
|
// Do not attempt to chown a symlink. Go's os.Chown function will affect the symlink
|
||||||
@@ -261,7 +266,6 @@ func (fs *Filesystem) Chown(path string) error {
|
|||||||
return os.Chown(p, uid, gid)
|
return os.Chown(p, uid, gid)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return errors.Wrap(err, "server/filesystem: chown: failed to chown during walk function")
|
return errors.Wrap(err, "server/filesystem: chown: failed to chown during walk function")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,10 +387,9 @@ func (fs *Filesystem) TruncateRootDirectory() error {
|
|||||||
// Delete removes a file or folder from the system. Prevents the user from
|
// Delete removes a file or folder from the system. Prevents the user from
|
||||||
// accidentally (or maliciously) removing their root server data directory.
|
// accidentally (or maliciously) removing their root server data directory.
|
||||||
func (fs *Filesystem) Delete(p string) error {
|
func (fs *Filesystem) Delete(p string) error {
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
// This is one of the few (only?) places in the codebase where we're explicitly not using
|
// This is one of the few (only?) places in the codebase where we're explicitly not using
|
||||||
// the SafePath functionality when working with user provided input. If we did, you would
|
// the SafePath functionality when working with user provided input. If we did, you would
|
||||||
// not be able to delete a file that is a symlink pointing to a location outside of the data
|
// not be able to delete a file that is a symlink pointing to a location outside the data
|
||||||
// directory.
|
// directory.
|
||||||
//
|
//
|
||||||
// We also want to avoid resolving a symlink that points _within_ the data directory and thus
|
// We also want to avoid resolving a symlink that points _within_ the data directory and thus
|
||||||
@@ -403,25 +406,65 @@ func (fs *Filesystem) Delete(p string) error {
|
|||||||
return errors.New("cannot delete root server directory")
|
return errors.New("cannot delete root server directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
if st, err := os.Lstat(resolved); err != nil {
|
st, err := os.Lstat(resolved)
|
||||||
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
fs.error(err).Warn("error while attempting to stat file before deletion")
|
fs.error(err).Warn("error while attempting to stat file before deletion")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if !st.IsDir() {
|
// The following logic is used to handle a case where a user attempts to
|
||||||
fs.addDisk(-st.Size())
|
// delete a file that does not exist through a directory symlink.
|
||||||
} else {
|
// We don't want to reveal that the file does not exist, so we validate
|
||||||
wg.Add(1)
|
// the path of the symlink and return a bad path error if it is invalid.
|
||||||
go func(wg *sync.WaitGroup, st os.FileInfo, resolved string) {
|
|
||||||
defer wg.Done()
|
// The requested file or directory doesn't exist, so at this point we
|
||||||
if s, err := fs.DirectorySize(resolved); err == nil {
|
// need to iterate up the path chain until we hit a directory that
|
||||||
fs.addDisk(-s)
|
// _does_ exist and can be validated.
|
||||||
|
parts := strings.Split(filepath.Dir(resolved), "/")
|
||||||
|
|
||||||
|
// Range over all the path parts and form directory paths from the end
|
||||||
|
// moving up until we have a valid resolution, or we run out of paths to
|
||||||
|
// try.
|
||||||
|
for k := range parts {
|
||||||
|
try := strings.Join(parts[:(len(parts)-k)], "/")
|
||||||
|
if !fs.unsafeIsInDataDirectory(try) {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}(&wg, st, resolved)
|
|
||||||
|
t, err := filepath.EvalSymlinks(try)
|
||||||
|
if err == nil {
|
||||||
|
if !fs.unsafeIsInDataDirectory(t) {
|
||||||
|
return NewBadPathResolution(p, t)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
// Always return early if the file does not exist.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the file is not a symlink, we need to check that it is not within a
|
||||||
|
// symlinked directory that points outside the data directory.
|
||||||
|
if st.Mode()&os.ModeSymlink == 0 {
|
||||||
|
ep, err := filepath.EvalSymlinks(resolved)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if !fs.unsafeIsInDataDirectory(ep) {
|
||||||
|
return NewBadPathResolution(p, ep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if st.IsDir() {
|
||||||
|
if s, err := fs.DirectorySize(resolved); err == nil {
|
||||||
|
fs.addDisk(-s)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fs.addDisk(-st.Size())
|
||||||
|
}
|
||||||
|
|
||||||
return os.RemoveAll(resolved)
|
return os.RemoveAll(resolved)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -537,6 +537,80 @@ func TestFilesystem_Delete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
g.It("deletes a symlink but not it's target within the root directory", func() {
|
||||||
|
// Symlink to a file inside the root directory.
|
||||||
|
err := os.Symlink(filepath.Join(rfs.root, "server/source.txt"), filepath.Join(rfs.root, "server/symlink.txt"))
|
||||||
|
g.Assert(err).IsNil()
|
||||||
|
|
||||||
|
// Delete the symlink itself.
|
||||||
|
err = fs.Delete("symlink.txt")
|
||||||
|
g.Assert(err).IsNil()
|
||||||
|
|
||||||
|
// Ensure the symlink was deleted.
|
||||||
|
_, err = os.Lstat(filepath.Join(rfs.root, "server/symlink.txt"))
|
||||||
|
g.Assert(err).IsNotNil()
|
||||||
|
|
||||||
|
// Ensure the symlink target still exists.
|
||||||
|
_, err = os.Lstat(filepath.Join(rfs.root, "server/source.txt"))
|
||||||
|
g.Assert(err).IsNil()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("does not delete files symlinked outside of the root directory", func() {
|
||||||
|
// Create a file outside the root directory.
|
||||||
|
err := rfs.CreateServerFileFromString("/../source.txt", "test content")
|
||||||
|
g.Assert(err).IsNil()
|
||||||
|
|
||||||
|
// Create a symlink to the file outside the root directory.
|
||||||
|
err = os.Symlink(filepath.Join(rfs.root, "source.txt"), filepath.Join(rfs.root, "/server/symlink.txt"))
|
||||||
|
g.Assert(err).IsNil()
|
||||||
|
|
||||||
|
// Delete the symlink. (This should pass as we will delete the symlink itself, not it's target)
|
||||||
|
err = fs.Delete("symlink.txt")
|
||||||
|
g.Assert(err).IsNil()
|
||||||
|
|
||||||
|
// Ensure the file outside the root directory still exists.
|
||||||
|
_, err = os.Lstat(filepath.Join(rfs.root, "source.txt"))
|
||||||
|
g.Assert(err).IsNil()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("does not delete files symlinked through a directory outside of the root directory", func() {
|
||||||
|
// Create a directory outside the root directory.
|
||||||
|
err := os.Mkdir(filepath.Join(rfs.root, "foo"), 0o755)
|
||||||
|
g.Assert(err).IsNil()
|
||||||
|
|
||||||
|
// Create a file inside the directory that is outside the root.
|
||||||
|
err = rfs.CreateServerFileFromString("/../foo/source.txt", "test content")
|
||||||
|
g.Assert(err).IsNil()
|
||||||
|
|
||||||
|
// Symlink the directory that is outside the root to a file inside the root.
|
||||||
|
err = os.Symlink(filepath.Join(rfs.root, "foo"), filepath.Join(rfs.root, "server/symlink"))
|
||||||
|
g.Assert(err).IsNil()
|
||||||
|
|
||||||
|
// Delete a file inside the symlinked directory.
|
||||||
|
err = fs.Delete("symlink/source.txt")
|
||||||
|
g.Assert(err).IsNotNil()
|
||||||
|
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
|
||||||
|
|
||||||
|
// Ensure the file outside the root directory still exists.
|
||||||
|
_, err = os.Lstat(filepath.Join(rfs.root, "foo/source.txt"))
|
||||||
|
g.Assert(err).IsNil()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("returns an error when trying to delete a non-existent file symlinked through a directory outside of the root directory", func() {
|
||||||
|
// Create a directory outside the root directory.
|
||||||
|
err := os.Mkdir(filepath.Join(rfs.root, "foo2"), 0o755)
|
||||||
|
g.Assert(err).IsNil()
|
||||||
|
|
||||||
|
// Symlink the directory that is outside the root to a file inside the root.
|
||||||
|
err = os.Symlink(filepath.Join(rfs.root, "foo2"), filepath.Join(rfs.root, "server/symlink"))
|
||||||
|
g.Assert(err).IsNil()
|
||||||
|
|
||||||
|
// Delete a file inside the symlinked directory.
|
||||||
|
err = fs.Delete("symlink/source.txt")
|
||||||
|
g.Assert(err).IsNotNil()
|
||||||
|
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
g.AfterEach(func() {
|
g.AfterEach(func() {
|
||||||
rfs.reset()
|
rfs.reset()
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package filesystem
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
iofs "io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -33,8 +34,6 @@ func (fs *Filesystem) IsIgnored(paths ...string) error {
|
|||||||
// This logic is actually copied over from the SFTP server code. Ideally that eventually
|
// This logic is actually copied over from the SFTP server code. Ideally that eventually
|
||||||
// either gets ported into this application, or is able to make use of this package.
|
// either gets ported into this application, or is able to make use of this package.
|
||||||
func (fs *Filesystem) SafePath(p string) (string, error) {
|
func (fs *Filesystem) SafePath(p string) (string, error) {
|
||||||
var nonExistentPathResolution string
|
|
||||||
|
|
||||||
// Start with a cleaned up path before checking the more complex bits.
|
// Start with a cleaned up path before checking the more complex bits.
|
||||||
r := fs.unsafeFilePath(p)
|
r := fs.unsafeFilePath(p)
|
||||||
|
|
||||||
@@ -44,47 +43,24 @@ func (fs *Filesystem) SafePath(p string) (string, error) {
|
|||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return "", errors.Wrap(err, "server/filesystem: failed to evaluate symlink")
|
return "", errors.Wrap(err, "server/filesystem: failed to evaluate symlink")
|
||||||
} else if os.IsNotExist(err) {
|
} else if os.IsNotExist(err) {
|
||||||
// The requested directory doesn't exist, so at this point we need to iterate up the
|
// The target of one of the symlinks (EvalSymlinks is recursive) does not exist.
|
||||||
// path chain until we hit a directory that _does_ exist and can be validated.
|
// So we get what target path does not exist and check if it's within the data
|
||||||
parts := strings.Split(filepath.Dir(r), "/")
|
// directory. If it is, we return the original path, otherwise we return an error.
|
||||||
|
pErr, ok := err.(*iofs.PathError)
|
||||||
var try string
|
if !ok {
|
||||||
// Range over all of the path parts and form directory pathings from the end
|
return "", errors.Wrap(err, "server/filesystem: failed to evaluate symlink")
|
||||||
// moving up until we have a valid resolution or we run out of paths to try.
|
|
||||||
for k := range parts {
|
|
||||||
try = strings.Join(parts[:(len(parts)-k)], "/")
|
|
||||||
|
|
||||||
if !fs.unsafeIsInDataDirectory(try) {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
ep = pErr.Path
|
||||||
t, err := filepath.EvalSymlinks(try)
|
|
||||||
if err == nil {
|
|
||||||
nonExistentPathResolution = t
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the new path doesn't start with their root directory there is clearly an escape
|
|
||||||
// attempt going on, and we should NOT resolve this path for them.
|
|
||||||
if nonExistentPathResolution != "" {
|
|
||||||
if !fs.unsafeIsInDataDirectory(nonExistentPathResolution) {
|
|
||||||
return "", NewBadPathResolution(p, nonExistentPathResolution)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the nonExistentPathResolution variable is not empty then the initial path requested
|
|
||||||
// did not exist and we looped through the pathway until we found a match. At this point
|
|
||||||
// we've confirmed the first matched pathway exists in the root server directory, so we
|
|
||||||
// can go ahead and just return the path that was requested initially.
|
|
||||||
return r, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the requested directory from EvalSymlinks begins with the server root directory go
|
// If the requested directory from EvalSymlinks begins with the server root directory go
|
||||||
// ahead and return it. If not we'll return an error which will block any further action
|
// ahead and return it. If not we'll return an error which will block any further action
|
||||||
// on the file.
|
// on the file.
|
||||||
if fs.unsafeIsInDataDirectory(ep) {
|
if fs.unsafeIsInDataDirectory(ep) {
|
||||||
return ep, nil
|
// Returning the original path here instead of the resolved path ensures that
|
||||||
|
// whatever the user is trying to do will work as expected. If we returned the
|
||||||
|
// resolved path, the user would be unable to know that it is in fact a symlink.
|
||||||
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", NewBadPathResolution(p, r)
|
return "", NewBadPathResolution(p, r)
|
||||||
|
|||||||
@@ -115,6 +115,14 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := os.Symlink(filepath.Join(rfs.root, "malicious_does_not_exist.txt"), filepath.Join(rfs.root, "/server/symlinked_does_not_exist.txt")); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Symlink(filepath.Join(rfs.root, "/server/symlinked_does_not_exist.txt"), filepath.Join(rfs.root, "/server/symlinked_does_not_exist2.txt")); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.Symlink(filepath.Join(rfs.root, "/malicious_dir"), filepath.Join(rfs.root, "/server/external_dir")); err != nil {
|
if err := os.Symlink(filepath.Join(rfs.root, "/malicious_dir"), filepath.Join(rfs.root, "/server/external_dir")); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -128,6 +136,22 @@ func TestFilesystem_Blocks_Symlinks(t *testing.T) {
|
|||||||
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
|
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
g.It("cannot write to a non-existent file symlinked outside the root", func() {
|
||||||
|
r := bytes.NewReader([]byte("testing what the fuck"))
|
||||||
|
|
||||||
|
err := fs.Writefile("symlinked_does_not_exist.txt", r)
|
||||||
|
g.Assert(err).IsNotNil()
|
||||||
|
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("cannot write to chained symlinks with target that does not exist outside the root", func() {
|
||||||
|
r := bytes.NewReader([]byte("testing what the fuck"))
|
||||||
|
|
||||||
|
err := fs.Writefile("symlinked_does_not_exist2.txt", r)
|
||||||
|
g.Assert(err).IsNotNil()
|
||||||
|
g.Assert(IsErrorCode(err, ErrCodePathResolution)).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
g.It("cannot write a file to a directory symlinked outside the root", func() {
|
g.It("cannot write a file to a directory symlinked outside the root", func() {
|
||||||
r := bytes.NewReader([]byte("testing"))
|
r := bytes.NewReader([]byte("testing"))
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
var Version = "develop"
|
var Version = "1.11.4"
|
||||||
|
|||||||
Reference in New Issue
Block a user