99 "maps"
1010 "os"
1111 "os/exec"
12+ "path"
1213 "path/filepath"
14+ "regexp"
1315 "strings"
1416 "time"
1517
@@ -348,21 +350,33 @@ func executeSingleStep(
348350 scriptWorkDir = workDir + "/" + opts .Task .Path
349351 }
350352
353+ if err := validateContainerTempPath (containerTemp ); err != nil {
354+ return bytes.Buffer {}, bytes.Buffer {}, errors .Wrap (err , "validating run script target" )
355+ }
356+ runScriptMount , err := dockerBindMount (runScriptFile , containerTemp )
357+ if err != nil {
358+ return bytes.Buffer {}, bytes.Buffer {}, errors .Wrap (err , "creating run script mount" )
359+ }
360+
351361 args := append ([]string {
352362 "run" ,
353363 "--rm" ,
354364 "--init" ,
355365 "--cidfile" , cidFile ,
356366 "--workdir" , scriptWorkDir ,
357- "--mount" , fmt . Sprintf ( "type=bind,source=%s,target=%s,ro" , runScriptFile , containerTemp ) ,
367+ "--mount" , runScriptMount ,
358368 }, workspaceOpts ... )
359369
360370 if opts .ForceRoot {
361371 args = append (args , "--user" , "0:0" )
362372 }
363373
364374 for target , source := range filesToMount {
365- args = append (args , "--mount" , fmt .Sprintf ("type=bind,source=%s,target=%s,ro" , source .Name (), target ))
375+ mountArg , err := dockerBindMount (source .Name (), target )
376+ if err != nil {
377+ return bytes.Buffer {}, bytes.Buffer {}, errors .Wrap (err , "creating files mount" )
378+ }
379+ args = append (args , "--mount" , mountArg )
366380 }
367381
368382 // Mount any paths on the local system to the docker container. The paths have already been validated during parsing.
@@ -371,7 +385,11 @@ func executeSingleStep(
371385 if err != nil {
372386 return bytes.Buffer {}, bytes.Buffer {}, err
373387 }
374- args = append (args , "--mount" , fmt .Sprintf ("type=bind,source=%s,target=%s,ro" , workspaceFilePath , mount .Mountpoint ))
388+ mountArg , err := dockerBindMount (workspaceFilePath , mount .Mountpoint )
389+ if err != nil {
390+ return bytes.Buffer {}, bytes.Buffer {}, errors .Wrap (err , "creating host mount" )
391+ }
392+ args = append (args , "--mount" , mountArg )
375393 }
376394
377395 for k , v := range env {
@@ -479,6 +497,8 @@ func setOutputs(stepOutputs batcheslib.Outputs, global map[string]any, stepCtx *
479497 return nil
480498}
481499
500+ var containerTempPathPattern = regexp .MustCompile (`^/[A-Za-z0-9_./-]+$` )
501+
482502func probeImageForShell (ctx context.Context , image string ) (shell , tempfile string , err error ) {
483503 // We need to know two things to be able to run a shell script:
484504 //
@@ -508,9 +528,14 @@ func probeImageForShell(ctx context.Context, image string) (shell, tempfile stri
508528 if runErr := cmd .Run (); runErr != nil {
509529 err = errors .Append (err , errors .Wrapf (runErr , "probing shell %q:\n %s" , shell , stderr .String ()))
510530 } else {
531+ tempfile , runErr = parseContainerTempPath (stdout .String ())
532+ if runErr != nil {
533+ err = errors .Append (err , errors .Wrapf (runErr , "probing shell %q" , shell ))
534+ continue
535+ }
536+
511537 // Even if there were previous errors, we can now ignore them.
512538 err = nil
513- tempfile = strings .TrimSpace (stdout .String ())
514539 return
515540 }
516541 }
@@ -520,6 +545,39 @@ func probeImageForShell(ctx context.Context, image string) (shell, tempfile stri
520545 return
521546}
522547
548+ func parseContainerTempPath (output string ) (string , error ) {
549+ // mktemp writes one path followed by a newline. Remove that one expected
550+ // delimiter, then reject any additional output rather than allowing the
551+ // image to inject Docker's comma-delimited mount grammar.
552+ tempfile := strings .TrimSuffix (output , "\n " )
553+ if tempfile == "" {
554+ return "" , errors .New ("mktemp returned an empty path" )
555+ }
556+ if strings .ContainsAny (tempfile , "\r \n " ) {
557+ return "" , errors .New ("mktemp returned more than one line" )
558+ }
559+ if err := validateContainerTempPath (tempfile ); err != nil {
560+ return "" , err
561+ }
562+ return tempfile , nil
563+ }
564+
565+ func validateContainerTempPath (tempfile string ) error {
566+ if ! path .IsAbs (tempfile ) || ! containerTempPathPattern .MatchString (tempfile ) {
567+ return errors .Newf ("mktemp returned invalid path %q" , tempfile )
568+ }
569+ return nil
570+ }
571+
572+ func dockerBindMount (source , target string ) (string , error ) {
573+ for name , value := range map [string ]string {"source" : source , "target" : target } {
574+ if value == "" || strings .ContainsAny (value , ",\r \n \x00 " ) {
575+ return "" , errors .Newf ("invalid Docker mount %s %q" , name , value )
576+ }
577+ }
578+ return fmt .Sprintf ("type=bind,source=%s,target=%s,ro" , source , target ), nil
579+ }
580+
523581// createFilesToMount creates temporary files with the contents of Step.Files
524582// that are to be mounted into the container that executes the step.
525583func createFilesToMount (tempDir string , step batcheslib.Step , stepContext * template.StepContext ) (map [string ]* os.File , func (), error ) {
0 commit comments