Address security vulnerabilities allowing certain internal processes to potentiallty escape server data directory
This commit is contained in:
parent
6e1844a8c9
commit
4f1b0c67d6
|
@ -52,7 +52,12 @@ func (a *Archiver) Archive() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range fileInfo {
|
for _, file := range fileInfo {
|
||||||
files = append(files, filepath.Join(path, file.Name()))
|
f, err := a.Server.Filesystem.SafeJoin(path, file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
files = append(files, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
stat, err := a.Stat()
|
stat, err := a.Stat()
|
||||||
|
|
|
@ -107,6 +107,27 @@ func (fs *Filesystem) SafePath(p string) (string, error) {
|
||||||
return "", InvalidPathResolution
|
return "", InvalidPathResolution
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to keep some of the codebase a little cleaner. Returns a "safe" version of the path
|
||||||
|
// joined with a file. This is important because you cannot just assume that appending a file to a cleaned
|
||||||
|
// path will result in a cleaned path to that file. For example, imagine you have the following scenario:
|
||||||
|
//
|
||||||
|
// my_bad_file -> symlink:/etc/passwd
|
||||||
|
//
|
||||||
|
// cleaned := SafePath("../../etc") -> "/"
|
||||||
|
// filepath.Join(cleaned, my_bad_file) -> "/my_bad_file"
|
||||||
|
//
|
||||||
|
// You might think that "/my_bad_file" is fine since it isn't pointing to the original "../../etc/my_bad_file".
|
||||||
|
// However, this doesn't account for symlinks where the file might be pointing outside of the directory, so
|
||||||
|
// calling a function such as Chown against it would chown the symlinked location, and not the file within the
|
||||||
|
// Wings daemon.
|
||||||
|
func (fs *Filesystem) SafeJoin(dir string, f os.FileInfo) (string, error) {
|
||||||
|
if f.Mode()&os.ModeSymlink != 0 {
|
||||||
|
return fs.SafePath(filepath.Join(dir, f.Name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(dir, f.Name()), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Executes the fs.SafePath function in parallel against an array of paths. If any of the calls
|
// Executes the fs.SafePath function in parallel against an array of paths. If any of the calls
|
||||||
// fails an error will be returned.
|
// fails an error will be returned.
|
||||||
func (fs *Filesystem) ParallelSafePath(paths []string) ([]string, error) {
|
func (fs *Filesystem) ParallelSafePath(paths []string) ([]string, error) {
|
||||||
|
@ -456,16 +477,27 @@ func (fs *Filesystem) chownDirectory(path string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
|
// Do not attempt to chmod a symlink. Go's os.Chown function will affect the symlink
|
||||||
|
// so if it points to a location outside the data directory the user would be able to
|
||||||
|
// (un)intentionally modify that files permissions.
|
||||||
|
if f.Mode()&os.ModeSymlink != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := fs.SafeJoin(cleaned, f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if f.IsDir() {
|
if f.IsDir() {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
go func(p string) {
|
go func(p string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
fs.chownDirectory(p)
|
fs.chownDirectory(p)
|
||||||
}(filepath.Join(cleaned, f.Name()))
|
}(p)
|
||||||
} else {
|
} else {
|
||||||
// Chown the file.
|
os.Chown(p, fs.Configuration.User.Uid, fs.Configuration.User.Gid)
|
||||||
os.Chown(filepath.Join(cleaned, f.Name()), fs.Configuration.User.Uid, fs.Configuration.User.Gid)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -600,7 +632,14 @@ func (fs *Filesystem) ListDirectory(p string) ([]*Stat, error) {
|
||||||
|
|
||||||
var m = "inode/directory"
|
var m = "inode/directory"
|
||||||
if !f.IsDir() {
|
if !f.IsDir() {
|
||||||
m, _, _ = mimetype.DetectFile(filepath.Join(cleaned, f.Name()))
|
cleanedp, _ := fs.SafeJoin(cleaned, f)
|
||||||
|
if cleanedp != "" {
|
||||||
|
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
|
||||||
|
// the server data path.
|
||||||
|
m = "application/octet-stream"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out[idx] = &Stat{
|
out[idx] = &Stat{
|
||||||
|
|
|
@ -60,9 +60,12 @@ func (w *PooledFileWalker) process(path string) error {
|
||||||
// callback function. If we encounter a directory, push that directory onto the worker queue
|
// callback function. If we encounter a directory, push that directory onto the worker queue
|
||||||
// to be processed.
|
// to be processed.
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
sp := filepath.Join(p, f.Name())
|
sp, err := w.Filesystem.SafeJoin(p, f)
|
||||||
i, err := os.Stat(sp)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
i, err := os.Stat(sp)
|
||||||
// You might end up getting an error about a file or folder not existing if the given path
|
// You might end up getting an error about a file or folder not existing if the given path
|
||||||
// if it is an invalid symlink. We can safely just skip over these files I believe.
|
// if it is an invalid symlink. We can safely just skip over these files I believe.
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user