Address security vulnerabilities allowing certain internal processes to potentiallty escape server data directory

This commit is contained in:
Dane Everitt 2020-07-18 11:40:38 -07:00
parent 6e1844a8c9
commit 4f1b0c67d6
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
3 changed files with 54 additions and 7 deletions

View File

@ -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()

View File

@ -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{

View File

@ -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) {