|
7 | 7 | "os" |
8 | 8 | "path/filepath" |
9 | 9 | "strconv" |
| 10 | + "strings" |
10 | 11 | "sync" |
11 | 12 |
|
12 | 13 | "github.com/fxamacker/cbor/v2" |
@@ -50,6 +51,24 @@ func download(key, domain, id, destination string) error { |
50 | 51 | return downloadTraversal(downloader, key, domain, &node, destination) |
51 | 52 | } |
52 | 53 |
|
| 54 | +// safePath ensures the resulting path stays within the base directory |
| 55 | +func safePath(base, name string) (string, error) { |
| 56 | + joined := filepath.Join(base, name) |
| 57 | + absBase, err := filepath.Abs(base) |
| 58 | + if err != nil { |
| 59 | + return "", fmt.Errorf("getting absolute base path: %w", err) |
| 60 | + } |
| 61 | + absJoined, err := filepath.Abs(joined) |
| 62 | + if err != nil { |
| 63 | + return "", fmt.Errorf("getting absolute joined path: %w", err) |
| 64 | + } |
| 65 | + // Ensure the joined path is within the base directory |
| 66 | + if !strings.HasPrefix(absJoined, absBase+string(filepath.Separator)) && absJoined != absBase { |
| 67 | + return "", fmt.Errorf("path traversal detected: %s escapes %s", name, base) |
| 68 | + } |
| 69 | + return joined, nil |
| 70 | +} |
| 71 | + |
53 | 72 | func downloadTraversal(downloader *protocol.ParallelDownloader, key, domain string, node *protocol.Node, destination string) error { |
54 | 73 | if node.Hash != nil { |
55 | 74 | hash := *node.Hash |
@@ -82,10 +101,17 @@ func downloadTraversal(downloader *protocol.ParallelDownloader, key, domain stri |
82 | 101 | var mu sync.Mutex |
83 | 102 | var wg sync.WaitGroup |
84 | 103 | for name, child := range node.Children { |
| 104 | + childPath, err := safePath(destination, name) |
| 105 | + if err != nil { |
| 106 | + mu.Lock() |
| 107 | + errors = append(errors, err) |
| 108 | + mu.Unlock() |
| 109 | + continue |
| 110 | + } |
85 | 111 | wg.Add(1) |
86 | 112 | go func() { |
87 | 113 | defer wg.Done() |
88 | | - if err := downloadTraversal(downloader, key, domain, child, filepath.Join(destination, name)); err != nil { |
| 114 | + if err := downloadTraversal(downloader, key, domain, child, childPath); err != nil { |
89 | 115 | mu.Lock() |
90 | 116 | errors = append(errors, err) |
91 | 117 | mu.Unlock() |
|
0 commit comments