[security] remove "SafeJoin" function

This function was not actually safe in theory. If an unknown stat source was passed in it would be possible for a symlinked file to not be detected as a symlink, thus skipping any safe path resolutions.

This would happen if the stat source was a regular os.Stat call and not an os.Lstat call, but since there is no way to differentiate between those two in the code, it is safer to just manually apply this logic in the positions where we _know_ for certain that we're working with the results of an Lstat call.
This commit is contained in:
Dane Everitt 2020-10-01 21:28:38 -07:00
parent e3e89a2ecc
commit 244640d0c1
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
3 changed files with 19 additions and 25 deletions

View File

@ -62,10 +62,21 @@ func (a *Archiver) Archive() error {
} }
for _, file := range fileInfo { for _, file := range fileInfo {
f, err := a.Server.Filesystem().SafeJoin(path, file) f := filepath.Join(path, file.Name())
// If the file is a symlink we cannot safely assume that the result of a filepath.Join() will be
// a safe destination. We need to check if the file is a symlink, and if so pass off to the SafePath
// function to resolve it to the final destination.
//
// ioutil.ReadDir() calls Lstat, so this will work correctly. If it did not call Lstat, but rather
// just did a normal Stat call, this would fail since that would be looking at the symlink destination
// and not the actual file in this listing.
if file.Mode()&os.ModeSymlink != 0 {
f, err = a.Server.Filesystem().SafePath(filepath.Join(path, file.Name()))
if err != nil { if err != nil {
return err return err
} }
}
files = append(files, f) files = append(files, f)
} }

View File

@ -412,7 +412,11 @@ func (fs *Filesystem) ListDirectory(p string) ([]*Stat, error) {
var m *mimetype.MIME var m *mimetype.MIME
var d = "inode/directory" var d = "inode/directory"
if !f.IsDir() { if !f.IsDir() {
cleanedp, _ := fs.SafeJoin(cleaned, f) cleanedp := filepath.Join(cleaned, f.Name())
if f.Mode()&os.ModeSymlink != 0 {
cleanedp, _ = fs.SafePath(filepath.Join(cleaned, f.Name()))
}
if cleanedp != "" { if cleanedp != "" {
m, _ = mimetype.DetectFile(filepath.Join(cleaned, f.Name())) m, _ = mimetype.DetectFile(filepath.Join(cleaned, f.Name()))
} else { } else {

View File

@ -92,27 +92,6 @@ func (fs *Filesystem) unsafeIsInDataDirectory(p string) bool {
return strings.HasPrefix(strings.TrimSuffix(p, "/")+"/", strings.TrimSuffix(fs.Path(), "/")+"/") return strings.HasPrefix(strings.TrimSuffix(p, "/")+"/", strings.TrimSuffix(fs.Path(), "/")+"/")
} }
// 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) {