Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

fix(util): add manually copy to prevent invalid cross-device link #1684

Merged
merged 2 commits into from
Feb 4, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 56 additions & 3 deletions util/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,19 +208,72 @@
return fmt.Errorf("destination directory %s already exists", dstDir)
}

// Get the parent directory of the destination directory
parentDir := filepath.Dir(dstDir)
if err := Mkdir(parentDir); err != nil {
return fmt.Errorf("failed to create parent directories for %s: %w", dstDir, err)
}

if err := os.Rename(srcDir, dstDir); err != nil {
return fmt.Errorf("failed to move directory from %s to %s: %w", srcDir, dstDir, err)
err := os.Rename(srcDir, dstDir)
if err != nil {
// To prevent invalid cross-device link perform a manual copy
if err := copyDirectory(srcDir, dstDir); err != nil {
return fmt.Errorf("failed to move directory from %s to %s: %w", srcDir, dstDir, err)
}

// Remove source directory after successful copy
if err := os.RemoveAll(srcDir); err != nil {
return fmt.Errorf("failed to remove source directory %s: %w", srcDir, err)
}

Check warning on line 226 in util/io.go

View check run for this annotation

Codecov / codecov/patch

util/io.go#L224-L226

Added lines #L224 - L226 were not covered by tests
}

return nil
}

func copyDirectory(src, dst string) error {
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

relPath, err := filepath.Rel(src, path)
if err != nil {
return err
}
dstPath := filepath.Join(dst, relPath)

if info.IsDir() {
return os.MkdirAll(dstPath, info.Mode())
}

Check warning on line 246 in util/io.go

View check run for this annotation

Codecov / codecov/patch

util/io.go#L238-L246

Added lines #L238 - L246 were not covered by tests

return copyFile(path, dstPath, info)

Check warning on line 248 in util/io.go

View check run for this annotation

Codecov / codecov/patch

util/io.go#L248

Added line #L248 was not covered by tests
})
}

func copyFile(src, dst string, info os.FileInfo) error {
input, err := os.Open(src)
if err != nil {
return err
}
defer func() {
_ = input.Close()
}()

Check warning on line 259 in util/io.go

View check run for this annotation

Codecov / codecov/patch

util/io.go#L252-L259

Added lines #L252 - L259 were not covered by tests

out, err := os.Create(dst)
if err != nil {
return err
}
defer func() {
_ = out.Close()
}()

Check warning on line 267 in util/io.go

View check run for this annotation

Codecov / codecov/patch

util/io.go#L261-L267

Added lines #L261 - L267 were not covered by tests

if _, err = io.Copy(out, input); err != nil {
return err
}

Check warning on line 271 in util/io.go

View check run for this annotation

Codecov / codecov/patch

util/io.go#L269-L271

Added lines #L269 - L271 were not covered by tests

// Preserve file permissions
return os.Chmod(dst, info.Mode())

Check warning on line 274 in util/io.go

View check run for this annotation

Codecov / codecov/patch

util/io.go#L274

Added line #L274 was not covered by tests
}

// SanitizeArchivePath mitigates the "Zip Slip" vulnerability by sanitizing archive file paths.
// It ensures that the file path is contained within the specified base directory to prevent directory
// traversal attacks. For more details on the vulnerability, see https://snyk.io/research/zip-slip-vulnerability.
Expand Down
Loading