fixed bug with store sync extraction into unwritable directory (#737)

This commit is contained in:
Zack Brady
2026-08-18 19:42:58 -04:00
committed by GitHub
parent 47e74cfd8e
commit 29a5c697b1
5 changed files with 19 additions and 4 deletions
+4 -2
View File
@@ -163,12 +163,14 @@ func SyncCmd(ctx context.Context, o *flags.SyncOpts, s *store.Layout, rso *flags
if err != nil {
return fmt.Errorf("failed to fetch product manifest for [%s]: %w", productName, err)
}
err = ExtractCmd(ctx, &flags.ExtractOpts{StoreRootOpts: o.StoreRootOpts}, s, fmt.Sprintf("hauler/%s-manifest.yaml:%s", parts[0], tag))
// The manifest is output for the user, so it goes to workDir, not tempDir (removed when sync returns).
workDir := flags.ResolveWorkDir(ro)
err = ExtractCmd(ctx, &flags.ExtractOpts{StoreRootOpts: o.StoreRootOpts, DestinationDir: workDir}, s, fmt.Sprintf("hauler/%s-manifest.yaml:%s", parts[0], tag))
if err != nil {
return err
}
fileName := fmt.Sprintf("%s-manifest.yaml", parts[0])
fi, err := os.Open(fileName)
fi, err := os.Open(filepath.Join(workDir, fileName))
if err != nil {
return err
}
+2
View File
@@ -7,6 +7,7 @@ type CliRootOpts struct {
HaulerDir string
IgnoreErrors bool
AuditLevel string
WorkDir string
}
func AddRootFlags(cmd *cobra.Command, ro *CliRootOpts) {
@@ -16,4 +17,5 @@ func AddRootFlags(cmd *cobra.Command, ro *CliRootOpts) {
pf.StringVarP(&ro.HaulerDir, "haulerdir", "d", "", "Set the location of the hauler directory (default $HOME/.hauler)")
pf.BoolVar(&ro.IgnoreErrors, "ignore-errors", false, "Warn and continue instead of failing on errors, including storing images that failed verification (defaults false)")
pf.StringVar(&ro.AuditLevel, "audit-level", "", "Set the audit logging level (none, standard, verbose) (defaults standard)")
pf.StringVarP(&ro.WorkDir, "work-dir", "w", "", "(Optional) Set the directory for output that commands would otherwise write to the current directory (default: current directory)")
}
+8
View File
@@ -144,3 +144,11 @@ func resolveHaulerDir(ro *CliRootOpts) string {
home, _ := os.UserHomeDir()
return filepath.Join(home, consts.DefaultHaulerDirName)
}
// ResolveWorkDir returns the configured output dir, or "" to mean the current directory (legacy behavior).
func ResolveWorkDir(ro *CliRootOpts) string {
if ro != nil && ro.WorkDir != "" {
return ro.WorkDir
}
return os.Getenv(consts.HaulerWorkDir)
}
+4 -2
View File
@@ -89,8 +89,10 @@ func (s *pusher) Push(ctx context.Context, desc ocispec.Descriptor) (ccontent.Wr
}
fullFileName := filepath.Join(destDir, filename)
// Guard against path traversal (e.g. filename containing "../")
if !strings.HasPrefix(fullFileName, destDir+string(filepath.Separator)) {
// Guard against path traversal (e.g. "../"). filepath.Rel handles this
// correctly even when destDir is "/", unlike a plain prefix check.
rel, err := filepath.Rel(destDir, fullFileName)
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return nil, fmt.Errorf("path_traversal_disallowed: %q resolves outside destination dir", filename)
}
+1
View File
@@ -95,6 +95,7 @@ const (
// environment variables
HaulerDir = "HAULER_DIR"
HaulerTempDir = "HAULER_TEMP_DIR"
HaulerWorkDir = "HAULER_WORK_DIR"
HaulerStoreDir = "HAULER_STORE_DIR"
HaulerIgnoreErrors = "HAULER_IGNORE_ERRORS"
HaulerRetries = "HAULER_RETRIES"