Browse code

Merge pull request #17851 from Microsoft/10662-ArgumentEscaping

Prevent double escaping of Dockerfile commands on Windows

Antonio Murdaca authored on 2015/11/15 01:25:53
Showing 9 changed files
... ...
@@ -104,6 +104,8 @@ func (cli *DockerCli) CmdRun(args ...string) error {
104 104
 		return nil
105 105
 	}
106 106
 
107
+	config.ArgsEscaped = false
108
+
107 109
 	if !*flDetach {
108 110
 		if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
109 111
 			return err
... ...
@@ -389,6 +389,8 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
389 389
 	b.runConfig.Cmd = config.Cmd
390 390
 	// set build-time environment for 'run'.
391 391
 	b.runConfig.Env = append(b.runConfig.Env, cmdBuildEnv...)
392
+	// set config as already being escaped, this prevents double escaping on windows
393
+	b.runConfig.ArgsEscaped = true
392 394
 
393 395
 	logrus.Debugf("[BUILDER] Command to be executed: %v", b.runConfig.Cmd)
394 396
 
... ...
@@ -479,7 +481,7 @@ func entrypoint(b *Builder, args []string, attributes map[string]bool, original
479 479
 		if runtime.GOOS != "windows" {
480 480
 			b.runConfig.Entrypoint = stringutils.NewStrSlice("/bin/sh", "-c", parsed[0])
481 481
 		} else {
482
-			b.runConfig.Entrypoint = stringutils.NewStrSlice("cmd", "/S /C", parsed[0])
482
+			b.runConfig.Entrypoint = stringutils.NewStrSlice("cmd", "/S", "/C", parsed[0])
483 483
 		}
484 484
 	}
485 485
 
... ...
@@ -181,7 +181,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
181 181
 	if runtime.GOOS != "windows" {
182 182
 		b.runConfig.Cmd = stringutils.NewStrSlice("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest))
183 183
 	} else {
184
-		b.runConfig.Cmd = stringutils.NewStrSlice("cmd", "/S /C", fmt.Sprintf("REM (nop) %s %s in %s", cmdName, srcHash, dest))
184
+		b.runConfig.Cmd = stringutils.NewStrSlice("cmd", "/S", "/C", fmt.Sprintf("REM (nop) %s %s in %s", cmdName, srcHash, dest))
185 185
 	}
186 186
 	defer func(cmd *stringutils.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
187 187
 
... ...
@@ -136,6 +136,7 @@ func (daemon *Daemon) populateCommand(c *Container, env []string) error {
136 136
 		LayerPaths:  layerPaths,
137 137
 		Hostname:    c.Config.Hostname,
138 138
 		Isolation:   c.hostConfig.Isolation,
139
+		ArgsEscaped: c.Config.ArgsEscaped,
139 140
 	}
140 141
 
141 142
 	return nil
... ...
@@ -57,6 +57,7 @@ type Command struct {
57 57
 	LayerFolder string                   `json:"layer_folder"` // Layer folder for a command
58 58
 	LayerPaths  []string                 `json:"layer_paths"`  // Layer paths for a command
59 59
 	Isolation   runconfig.IsolationLevel `json:"isolation"`    // Isolation level for the container
60
+	ArgsEscaped bool                     `json:"args_escaped"` // True if args are already escaped
60 61
 }
61 62
 
62 63
 // ExitStatus provides exit reasons for a container.
63 64
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+//+build windows
1
+
2
+package windows
3
+
4
+import (
5
+	"errors"
6
+	"syscall"
7
+
8
+	"github.com/Sirupsen/logrus"
9
+	"github.com/docker/docker/daemon/execdriver"
10
+)
11
+
12
+// createCommandLine creates a command line from the Entrypoint and args
13
+// of the ProcessConfig. It escapes the arguments if they are not already
14
+// escaped
15
+func createCommandLine(processConfig *execdriver.ProcessConfig, alreadyEscaped bool) (commandLine string, err error) {
16
+	// While this should get caught earlier, just in case, validate that we
17
+	// have something to run.
18
+	if processConfig.Entrypoint == "" {
19
+		return "", errors.New("No entrypoint specified")
20
+	}
21
+
22
+	// Build the command line of the process
23
+	commandLine = processConfig.Entrypoint
24
+	logrus.Debugf("Entrypoint: %s", processConfig.Entrypoint)
25
+	for _, arg := range processConfig.Arguments {
26
+		logrus.Debugf("appending %s", arg)
27
+		if !alreadyEscaped {
28
+			arg = syscall.EscapeArg(arg)
29
+		}
30
+		commandLine += " " + arg
31
+	}
32
+
33
+	logrus.Debugf("commandLine: %s", commandLine)
34
+	return commandLine, nil
35
+}
... ...
@@ -3,7 +3,6 @@
3 3
 package windows
4 4
 
5 5
 import (
6
-	"errors"
7 6
 	"fmt"
8 7
 
9 8
 	"github.com/Sirupsen/logrus"
... ...
@@ -34,21 +33,11 @@ func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo
34 34
 	// Configure the environment for the process // Note NOT c.ProcessConfig.Tty
35 35
 	createProcessParms.Environment = setupEnvironmentVariables(processConfig.Env)
36 36
 
37
-	// While this should get caught earlier, just in case, validate that we
38
-	// have something to run.
39
-	if processConfig.Entrypoint == "" {
40
-		err = errors.New("No entrypoint specified")
41
-		logrus.Error(err)
42
-		return -1, err
43
-	}
37
+	createProcessParms.CommandLine, err = createCommandLine(&c.ProcessConfig, c.ArgsEscaped)
44 38
 
45
-	// Build the command line of the process
46
-	createProcessParms.CommandLine = processConfig.Entrypoint
47
-	for _, arg := range processConfig.Arguments {
48
-		logrus.Debugln("appending ", arg)
49
-		createProcessParms.CommandLine += " " + arg
39
+	if err != nil {
40
+		return -1, err
50 41
 	}
51
-	logrus.Debugln("commandLine: ", createProcessParms.CommandLine)
52 42
 
53 43
 	// Start the command running in the container.
54 44
 	pid, stdin, stdout, stderr, rc, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !processConfig.Tty, createProcessParms)
... ...
@@ -4,13 +4,11 @@ package windows
4 4
 
5 5
 import (
6 6
 	"encoding/json"
7
-	"errors"
8 7
 	"fmt"
9 8
 	"os"
10 9
 	"path/filepath"
11 10
 	"strconv"
12 11
 	"strings"
13
-	"syscall"
14 12
 
15 13
 	"github.com/Sirupsen/logrus"
16 14
 	"github.com/docker/docker/daemon/execdriver"
... ...
@@ -273,21 +271,11 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execd
273 273
 	// Configure the environment for the process
274 274
 	createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env)
275 275
 
276
-	// This should get caught earlier, but just in case - validate that we
277
-	// have something to run
278
-	if c.ProcessConfig.Entrypoint == "" {
279
-		err = errors.New("No entrypoint specified")
280
-		logrus.Error(err)
281
-		return execdriver.ExitStatus{ExitCode: -1}, err
282
-	}
276
+	createProcessParms.CommandLine, err = createCommandLine(&c.ProcessConfig, c.ArgsEscaped)
283 277
 
284
-	// Build the command line of the process
285
-	createProcessParms.CommandLine = c.ProcessConfig.Entrypoint
286
-	for _, arg := range c.ProcessConfig.Arguments {
287
-		logrus.Debugln("appending ", arg)
288
-		createProcessParms.CommandLine += " " + syscall.EscapeArg(arg)
278
+	if err != nil {
279
+		return execdriver.ExitStatus{ExitCode: -1}, err
289 280
 	}
290
-	logrus.Debugf("CommandLine: %s", createProcessParms.CommandLine)
291 281
 
292 282
 	// Start the command running in the container.
293 283
 	pid, stdin, stdout, stderr, _, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !c.ProcessConfig.Tty, createProcessParms)
... ...
@@ -30,6 +30,7 @@ type Config struct {
30 30
 	StdinOnce       bool                  // If true, close stdin after the 1 attached client disconnects.
31 31
 	Env             []string              // List of environment variable to set in the container
32 32
 	Cmd             *stringutils.StrSlice // Command to run when starting the container
33
+	ArgsEscaped     bool                  `json:",omitempty"` // True if command is already escaped (Windows specific)
33 34
 	Image           string                // Name of the image as it was passed by the operator (eg. could be symbolic)
34 35
 	Volumes         map[string]struct{}   // List of volumes (mounts) used for the container
35 36
 	WorkingDir      string                // Current directory (PWD) in the command will be launched