|
4 | 4 | "context" |
5 | 5 | "encoding/json" |
6 | 6 | "fmt" |
| 7 | + "io" |
7 | 8 | "os" |
8 | 9 | "os/exec" |
9 | 10 | "path/filepath" |
@@ -177,42 +178,75 @@ func downloadFromProxy(ctx context.Context, module, version string) (string, fun |
177 | 178 | // copyDir recursively copies a directory tree, handling symlinks. |
178 | 179 | // Directories are created with write permissions to allow file creation inside them, |
179 | 180 | // which is necessary when copying from read-only sources like the go module cache. |
| 181 | +// Uses os.Root for safe, race-free filesystem operations within the source and destination directories. |
180 | 182 | func copyDir(src, dst string) error { |
| 183 | + srcRoot, err := os.OpenRoot(src) |
| 184 | + if err != nil { |
| 185 | + return fmt.Errorf("failed to open source directory: %w", err) |
| 186 | + } |
| 187 | + defer srcRoot.Close() |
| 188 | + |
| 189 | + dstRoot, err := os.OpenRoot(dst) |
| 190 | + if err != nil { |
| 191 | + return fmt.Errorf("failed to open destination directory: %w", err) |
| 192 | + } |
| 193 | + defer dstRoot.Close() |
| 194 | + |
181 | 195 | return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { |
182 | 196 | if err != nil { |
183 | 197 | return err |
184 | 198 | } |
185 | 199 |
|
186 | | - // compute destination path |
| 200 | + // compute relative path for root-scoped operations |
187 | 201 | relPath, err := filepath.Rel(src, path) |
188 | 202 | if err != nil { |
189 | 203 | return err |
190 | 204 | } |
191 | | - dstPath := filepath.Join(dst, relPath) |
192 | 205 |
|
193 | | - // handle symlinks |
| 206 | + // skip the root directory itself |
| 207 | + if relPath == "." { |
| 208 | + return nil |
| 209 | + } |
| 210 | + |
| 211 | + // handle symlinks - read target and recreate in destination. |
| 212 | + // Note: os.Root.Readlink/Symlink require Go 1.25+, so we use standard functions here. |
| 213 | + // This is safe because we're copying from controlled sources (go module cache or our git clone) |
| 214 | + // to a temp directory we created. |
194 | 215 | if info.Mode()&os.ModeSymlink != 0 { |
195 | 216 | target, err := os.Readlink(path) |
196 | 217 | if err != nil { |
197 | 218 | return err |
198 | 219 | } |
199 | | - return os.Symlink(target, dstPath) |
| 220 | + dstPath := filepath.Join(dst, relPath) |
| 221 | + return os.Symlink(target, dstPath) //nolint:gosec // G122: destination is our temp directory |
200 | 222 | } |
201 | 223 |
|
202 | 224 | if info.IsDir() { |
203 | 225 | // ensure directories are writable so we can create files inside them |
204 | 226 | // (go module cache directories are read-only) |
205 | 227 | perm := info.Mode().Perm() | 0200 // add user write permission |
206 | | - return os.MkdirAll(dstPath, perm) |
| 228 | + return dstRoot.Mkdir(relPath, perm) |
207 | 229 | } |
208 | 230 |
|
209 | | - // copy file with write permission to allow go build to work |
210 | | - data, err := os.ReadFile(path) |
211 | | - if err != nil { |
212 | | - return err |
213 | | - } |
214 | | - // ensure files are writable (go module cache files are read-only) |
215 | | - perm := info.Mode().Perm() | 0200 // add user write permission |
216 | | - return os.WriteFile(dstPath, data, perm) |
| 231 | + // copy file using root-scoped operations |
| 232 | + return copyFileWithRoot(srcRoot, dstRoot, relPath, info.Mode().Perm()|0200) |
217 | 233 | }) |
218 | 234 | } |
| 235 | + |
| 236 | +// copyFileWithRoot copies a single file using root-scoped file handles for safe operations. |
| 237 | +func copyFileWithRoot(srcRoot, dstRoot *os.Root, relPath string, perm os.FileMode) error { |
| 238 | + srcFile, err := srcRoot.Open(relPath) |
| 239 | + if err != nil { |
| 240 | + return err |
| 241 | + } |
| 242 | + defer srcFile.Close() |
| 243 | + |
| 244 | + dstFile, err := dstRoot.OpenFile(relPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) |
| 245 | + if err != nil { |
| 246 | + return err |
| 247 | + } |
| 248 | + defer dstFile.Close() |
| 249 | + |
| 250 | + _, err = io.Copy(dstFile, srcFile) |
| 251 | + return err |
| 252 | +} |
0 commit comments