From c5bc7d515836cffee90ba5bb342272bba64d9f37 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 15:17:39 +0200 Subject: [PATCH 1/2] Utils: Add ShellQuoteArguments --- utils/utils.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/utils/utils.go b/utils/utils.go index 4941eae42d..623f00296e 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1139,6 +1139,41 @@ func (e *StatusError) Error() string { return fmt.Sprintf("Status: %d", e.Status) } +func quote(word string, buf *bytes.Buffer) { + // Bail out early for "simple" strings + if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") { + buf.WriteString(word) + return + } + + buf.WriteString("'") + + for i := 0; i < len(word); i++ { + b := word[i] + if b == '\'' { + // Replace literal ' with a close ', a \', and a open ' + buf.WriteString("'\\''") + } else { + buf.WriteByte(b) + } + } + + buf.WriteString("'") +} + +// Take a list of strings and escape them so they will be handled right +// when passed as arguments to an program via a shell +func ShellQuoteArguments(args []string) string { + var buf bytes.Buffer + for i, arg := range args { + if i != 0 { + buf.WriteByte(' ') + } + quote(arg, &buf) + } + return buf.String() +} + func IsClosedError(err error) bool { /* This comparison is ugly, but unfortunately, net.go doesn't export errClosing. * See: From 157d99a72786c454dfaad8b0800914cc80879aa8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 29 Oct 2013 16:16:51 +0100 Subject: [PATCH 2/2] lxc: Work around lxc-start need for private mounts lxc-start requires / to be mounted private, otherwise the changes it does inside the container (both mounts and unmounts) will propagate out to the host. We work around this by starting up lxc-start in its own namespace where we set / to rshared. Unfortunately go can't really execute any code between clone and exec, so we can't do this in a nice way. Instead we have a horrible hack that use the unshare command, the shell and the mount command... --- container.go | 27 +++++++++++++++++++++++---- utils.go | 15 +++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/container.go b/container.go index 50bf2ec674..ac21119d1a 100644 --- a/container.go +++ b/container.go @@ -863,7 +863,13 @@ func (container *Container) Start() (err error) { return err } + var lxcStart string = "lxc-start" + if container.hostConfig.Privileged && container.runtime.capabilities.AppArmor { + lxcStart = path.Join(container.runtime.config.Root, "lxc-start-unconfined") + } + params := []string{ + lxcStart, "-n", container.ID, "-f", container.lxcConfigPath(), "--", @@ -956,11 +962,24 @@ func (container *Container) Start() (err error) { params = append(params, "--", container.Path) params = append(params, container.Args...) - var lxcStart string = "lxc-start" - if container.hostConfig.Privileged && container.runtime.capabilities.AppArmor { - lxcStart = path.Join(container.runtime.config.Root, "lxc-start-unconfined") + if RootIsShared() { + // lxc-start really needs / to be non-shared, or all kinds of stuff break + // when lxc-start unmount things and those unmounts propagate to the main + // mount namespace. + // What we really want is to clone into a new namespace and then + // mount / MS_REC|MS_SLAVE, but since we can't really clone or fork + // without exec in go we have to do this horrible shell hack... + shellString := + "mount --make-rslave /; exec " + + utils.ShellQuoteArguments(params) + + params = []string{ + "unshare", "-m", "--", "/bin/sh", "-c", shellString, + } } - container.cmd = exec.Command(lxcStart, params...) + + container.cmd = exec.Command(params[0], params[1:]...) + // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { return err diff --git a/utils.go b/utils.go index 81715881ae..22d83d6bee 100644 --- a/utils.go +++ b/utils.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/dotcloud/docker/namesgenerator" "github.com/dotcloud/docker/utils" + "io/ioutil" "strconv" "strings" ) @@ -301,6 +302,20 @@ func parseLink(rawLink string) (map[string]string, error) { return utils.PartParser("name:alias", rawLink) } +func RootIsShared() bool { + if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { + for _, line := range strings.Split(string(data), "\n") { + cols := strings.Split(line, " ") + if len(cols) >= 6 && cols[4] == "/" { + return strings.HasPrefix(cols[6], "shared") + } + } + } + + // No idea, probably safe to assume so + return true +} + type checker struct { runtime *Runtime }