1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/execdriver/lxc/driver.go
Alexander Larsson 08ab554195 Fix handling of shared roots
If rootIsShared() is detected we apply the shell stuff to early, before
the real command and arguments are added to the parameters. This
means they get passed on to unshare rather than docker-init, breaking
docker on e.g. fedora like:

goroutine 1 [running]:
runtime.panic(0x678340, 0x9b3fd7)
	/usr/lib64/golang/src/pkg/runtime/panic.c:266 +0xb6
github.com/dotcloud/docker/execdriver/lxc.func·001(0xc21000a1b0, 0xc21001eab0, 0x7fff24715faf)
	/home/alex/vcs/go/src/github.com/dotcloud/docker/execdriver/lxc/driver.go:41 +0x525
github.com/dotcloud/docker/sysinit.executeProgram(0xc21000a1b0, 0xc21000a1b0, 0xa)
	/home/alex/vcs/go/src/github.com/dotcloud/docker/sysinit/sysinit.go:34 +0xca
github.com/dotcloud/docker/sysinit.SysInit()
	/home/alex/vcs/go/src/github.com/dotcloud/docker/sysinit/sysinit.go:88 +0x791
main.main()
	/home/alex/vcs/go/src/github.com/dotcloud/docker/dockerinit/dockerinit.go:14 +0x1a

The fix is to construct the full params array before escaping it.

Docker-DCO-1.1-Signed-off-by: Alexander Larsson <alexl@redhat.com> (github: alexlarsson)
2014-01-21 10:22:13 +01:00

323 lines
6.8 KiB
Go

package lxc
import (
"fmt"
"github.com/dotcloud/docker/execdriver"
"github.com/dotcloud/docker/utils"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"strconv"
"strings"
"syscall"
"time"
)
const DriverName = "lxc"
func init() {
execdriver.RegisterInitFunc(DriverName, func(args *execdriver.InitArgs) error {
if err := setupHostname(args); err != nil {
return err
}
if err := setupNetworking(args); err != nil {
return err
}
if err := setupCapabilities(args); err != nil {
return err
}
if err := setupWorkingDirectory(args); err != nil {
return err
}
if err := changeUser(args); err != nil {
return err
}
path, err := exec.LookPath(args.Args[0])
if err != nil {
log.Printf("Unable to locate %v", args.Args[0])
os.Exit(127)
}
if err := syscall.Exec(path, args.Args, os.Environ()); err != nil {
return fmt.Errorf("dockerinit unable to execute %s - %s", path, err)
}
panic("Unreachable")
})
}
type driver struct {
root string // root path for the driver to use
apparmor bool
sharedRoot bool
}
func NewDriver(root string, apparmor bool) (*driver, error) {
// setup unconfined symlink
if err := linkLxcStart(root); err != nil {
return nil, err
}
return &driver{
apparmor: apparmor,
root: root,
sharedRoot: rootIsShared(),
}, nil
}
func (d *driver) Name() string {
version := d.version()
return fmt.Sprintf("%s-%s", DriverName, version)
}
func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) {
configPath, err := d.generateLXCConfig(c)
if err != nil {
return -1, err
}
params := []string{
"lxc-start",
"-n", c.ID,
"-f", configPath,
"--",
c.InitPath,
"-driver",
DriverName,
}
if c.Network != nil {
params = append(params,
"-g", c.Network.Gateway,
"-i", fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network.IPPrefixLen),
"-mtu", strconv.Itoa(c.Network.Mtu),
)
}
if c.User != "" {
params = append(params, "-u", c.User)
}
if c.Privileged {
if d.apparmor {
params[0] = path.Join(d.root, "lxc-start-unconfined")
}
params = append(params, "-privileged")
}
if c.WorkingDir != "" {
params = append(params, "-w", c.WorkingDir)
}
params = append(params, "--", c.Entrypoint)
params = append(params, c.Arguments...)
if d.sharedRoot {
// 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,
}
}
var (
name = params[0]
arg = params[1:]
)
aname, err := exec.LookPath(name)
if err != nil {
aname = name
}
c.Path = aname
c.Args = append([]string{name}, arg...)
if err := c.Start(); err != nil {
return -1, err
}
var (
waitErr error
waitLock = make(chan struct{})
)
go func() {
if err := c.Wait(); err != nil {
waitErr = err
}
close(waitLock)
}()
// Poll lxc for RUNNING status
if err := d.waitForStart(c, waitLock); err != nil {
return -1, err
}
if startCallback != nil {
startCallback(c)
}
<-waitLock
return c.GetExitCode(), waitErr
}
func (d *driver) Kill(c *execdriver.Process, sig int) error {
return d.kill(c, sig)
}
func (d *driver) Wait(id string) error {
for {
output, err := exec.Command("lxc-info", "-n", id).CombinedOutput()
if err != nil {
return err
}
if !strings.Contains(string(output), "RUNNING") {
return nil
}
time.Sleep(500 * time.Millisecond)
}
}
func (d *driver) version() string {
version := ""
if output, err := exec.Command("lxc-version").CombinedOutput(); err == nil {
outputStr := string(output)
if len(strings.SplitN(outputStr, ":", 2)) == 2 {
version = strings.TrimSpace(strings.SplitN(outputStr, ":", 2)[1])
}
}
return version
}
func (d *driver) kill(c *execdriver.Process, sig int) error {
output, err := exec.Command("lxc-kill", "-n", c.ID, strconv.Itoa(sig)).CombinedOutput()
if err != nil {
return fmt.Errorf("Err: %s Output: %s", err, output)
}
return nil
}
func (d *driver) waitForStart(c *execdriver.Process, waitLock chan struct{}) error {
var (
err error
output []byte
)
// We wait for the container to be fully running.
// Timeout after 5 seconds. In case of broken pipe, just retry.
// Note: The container can run and finish correctly before
// the end of this loop
for now := time.Now(); time.Since(now) < 5*time.Second; {
select {
case <-waitLock:
// If the process dies while waiting for it, just return
return nil
if c.ProcessState != nil && c.ProcessState.Exited() {
return nil
}
default:
}
output, err = d.getInfo(c.ID)
if err != nil {
output, err = d.getInfo(c.ID)
if err != nil {
return err
}
}
if strings.Contains(string(output), "RUNNING") {
return nil
}
time.Sleep(50 * time.Millisecond)
}
return execdriver.ErrNotRunning
}
func (d *driver) getInfo(id string) ([]byte, error) {
return exec.Command("lxc-info", "-s", "-n", id).CombinedOutput()
}
type info struct {
ID string
driver *driver
}
func (i *info) IsRunning() bool {
var running bool
output, err := i.driver.getInfo(i.ID)
if err != nil {
panic(err)
}
if strings.Contains(string(output), "RUNNING") {
running = true
}
return running
}
func (d *driver) Info(id string) execdriver.Info {
return &info{
ID: id,
driver: d,
}
}
func linkLxcStart(root string) error {
sourcePath, err := exec.LookPath("lxc-start")
if err != nil {
return err
}
targetPath := path.Join(root, "lxc-start-unconfined")
if _, err := os.Lstat(targetPath); err != nil && !os.IsNotExist(err) {
return err
} else if err == nil {
if err := os.Remove(targetPath); err != nil {
return err
}
}
return os.Symlink(sourcePath, targetPath)
}
// TODO: This can be moved to the mountinfo reader in the mount pkg
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
}
func (d *driver) generateLXCConfig(p *execdriver.Process) (string, error) {
root := path.Join(d.root, "containers", p.ID, "config.lxc")
fo, err := os.Create(root)
if err != nil {
return "", err
}
defer fo.Close()
if err := LxcTemplateCompiled.Execute(fo, struct {
*execdriver.Process
AppArmor bool
}{
Process: p,
AppArmor: d.apparmor,
}); err != nil {
return "", err
}
return root, nil
}