diff -Nru juju-core-1-1.25.6/debian/changelog juju-core-1-1.25.6/debian/changelog --- juju-core-1-1.25.6/debian/changelog 2016-08-16 17:34:56.000000000 +0000 +++ juju-core-1-1.25.6/debian/changelog 2017-05-26 22:29:45.000000000 +0000 @@ -1,3 +1,13 @@ +juju-core-1 (1.25.6-0ubuntu1.16.04.2) xenial-security; urgency=medium + + * SECURITY UPDATE: Privilege escalation via juju-run (LP: #1682411) + - debian/patches/CVE-2017-9232.patch: create a unix domain socket with + restricted permissions to limit juju-run to only similarly privileged + processes. + - CVE-2017-9232 + + -- Seth Arnold Fri, 26 May 2017 15:29:18 -0700 + juju-core-1 (1.25.6-0ubuntu1.16.04.1) xenial-proposed; urgency=medium [ Martin Packman ] diff -Nru juju-core-1-1.25.6/debian/patches/CVE-2017-9232.patch juju-core-1-1.25.6/debian/patches/CVE-2017-9232.patch --- juju-core-1-1.25.6/debian/patches/CVE-2017-9232.patch 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1-1.25.6/debian/patches/CVE-2017-9232.patch 2017-05-26 22:30:07.000000000 +0000 @@ -0,0 +1,286 @@ +Origin: https://bugs.launchpad.net/juju/+bug/1682411/+attachment/4883282/+files/2.0.4-juju-run-1682411.diff +Author: John Meinel +Subject: Create Unix domain socket with restricted permissions + +--- + src/github.com/juju/juju/apiserver/charmrevisionupdater/updater.go | 4 - + src/github.com/juju/juju/apiserver/charmrevisionupdater/updater_test.go | 10 ++ + src/github.com/juju/juju/apiserver/client/run.go | 6 - + src/github.com/juju/juju/apiserver/client/run_unix_test.go | 6 - + src/github.com/juju/juju/cmd/jujud/run.go | 2 + src/github.com/juju/juju/cmd/jujud/run_test.go | 2 + src/github.com/juju/juju/juju/sockets/sockets_nix.go | 40 +++++++++- + src/github.com/juju/juju/worker/uniter/runner/jujuc/server.go | 10 ++ + src/github.com/juju/juju/worker/uniter/runner/runner.go | 2 + src/github.com/juju/juju/worker/uniter/uniter.go | 7 - + 10 files changed, 67 insertions(+), 22 deletions(-) + +Index: b/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater.go +=================================================================== +--- a/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater.go ++++ b/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater.go +@@ -12,6 +12,7 @@ + "github.com/juju/juju/apiserver/common" + "github.com/juju/juju/apiserver/params" + "github.com/juju/juju/state" ++ "github.com/juju/juju/version" + ) + + var logger = loggo.GetLogger("juju.apiserver.charmrevisionupdater") +@@ -116,7 +117,8 @@ + logger.Infof("retrieving revision information for %d charms", len(curls)) + repo := NewCharmStore(charmrepo.NewCharmStoreParams{}) + repo = repo.(*charmrepo.CharmStore).WithJujuAttrs(map[string]string{ +- "environment_uuid": uuid, ++ "environment_uuid": uuid, ++ "controller_version": version.Current.String(), + }) + revInfo, err := repo.Latest(curls...) + if err != nil { +Index: b/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater_test.go +=================================================================== +--- a/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater_test.go ++++ b/src/github.com/juju/juju/apiserver/charmrevisionupdater/updater_test.go +@@ -6,6 +6,7 @@ + import ( + "net/http" + "net/http/httptest" ++ "sort" + + "github.com/juju/errors" + jc "github.com/juju/testing/checkers" +@@ -19,6 +20,7 @@ + apiservertesting "github.com/juju/juju/apiserver/testing" + jujutesting "github.com/juju/juju/juju/testing" + "github.com/juju/juju/state" ++ "github.com/juju/juju/version" + ) + + type charmVersionSuite struct { +@@ -171,5 +173,11 @@ + + env, err := s.State.Environment() + c.Assert(err, jc.ErrorIsNil) +- c.Assert(header.Get(charmrepo.JujuMetadataHTTPHeader), gc.Equals, "environment_uuid="+env.UUID()) ++ jujuHeader := header[charmrepo.JujuMetadataHTTPHeader] ++ sort.Strings(jujuHeader) ++ c.Assert(jujuHeader, gc.DeepEquals, ++ []string{ ++ "controller_version=" + version.Current.String(), ++ "environment_uuid=" + env.UUID(), ++ }) + } +Index: b/src/github.com/juju/juju/apiserver/client/run.go +=================================================================== +--- a/src/github.com/juju/juju/apiserver/client/run.go ++++ b/src/github.com/juju/juju/apiserver/client/run.go +@@ -106,7 +106,7 @@ + if err != nil { + return results, err + } +- command := fmt.Sprintf("juju-run %s %s", unit.Name(), quotedCommands) ++ command := fmt.Sprintf("sudo juju-run %s %s", unit.Name(), quotedCommands) + execParam := remoteParamsForMachine(machine, command, run.Timeout) + execParam.UnitId = unit.Name() + params = append(params, execParam) +@@ -116,7 +116,7 @@ + if err != nil { + return results, err + } +- command := fmt.Sprintf("juju-run --no-context %s", quotedCommands) ++ command := fmt.Sprintf("sudo juju-run --no-context %s", quotedCommands) + execParam := remoteParamsForMachine(machine, command, run.Timeout) + params = append(params, execParam) + } +@@ -134,7 +134,7 @@ + } + var params []*RemoteExec + quotedCommands := utils.ShQuote(run.Commands) +- command := fmt.Sprintf("juju-run --no-context %s", quotedCommands) ++ command := fmt.Sprintf("sudo juju-run --no-context %s", quotedCommands) + for _, machine := range machines { + params = append(params, remoteParamsForMachine(machine, command, run.Timeout)) + } +Index: b/src/github.com/juju/juju/apiserver/client/run_unix_test.go +=================================================================== +--- a/src/github.com/juju/juju/apiserver/client/run_unix_test.go ++++ b/src/github.com/juju/juju/apiserver/client/run_unix_test.go +@@ -6,9 +6,9 @@ + package client_test + + var expectedCommand = []string{ +- "juju-run --no-context 'hostname'\n", +- "juju-run magic/0 'hostname'\n", +- "juju-run magic/1 'hostname'\n", ++ "sudo juju-run --no-context 'hostname'\n", ++ "sudo juju-run magic/0 'hostname'\n", ++ "sudo juju-run magic/1 'hostname'\n", + } + + var echoInputShowArgs = `#!/bin/bash +Index: b/src/github.com/juju/juju/cmd/jujud/run.go +=================================================================== +--- a/src/github.com/juju/juju/cmd/jujud/run.go ++++ b/src/github.com/juju/juju/cmd/jujud/run.go +@@ -143,7 +143,7 @@ + } + client, err := sockets.Dial(c.socketPath()) + if err != nil { +- return nil, errors.Trace(err) ++ return nil, errors.Annotate(err, "dialing juju run socket") + } + defer client.Close() + +Index: b/src/github.com/juju/juju/cmd/jujud/run_test.go +=================================================================== +--- a/src/github.com/juju/juju/cmd/jujud/run_test.go ++++ b/src/github.com/juju/juju/cmd/jujud/run_test.go +@@ -217,7 +217,7 @@ + c.Assert(err, jc.ErrorIsNil) + + _, err = testing.RunCommand(c, s.runCommand(), "foo/1", "bar") +- c.Assert(err, gc.ErrorMatches, `dial unix .*/run.socket:.*`+utils.NoSuchFileErrRegexp) ++ c.Assert(err, gc.ErrorMatches, `.*dial unix .*/run.socket:.*`+utils.NoSuchFileErrRegexp) + } + + func (s *RunTestSuite) TestRunning(c *gc.C) { +Index: b/src/github.com/juju/juju/juju/sockets/sockets_nix.go +=================================================================== +--- a/src/github.com/juju/juju/juju/sockets/sockets_nix.go ++++ b/src/github.com/juju/juju/juju/sockets/sockets_nix.go +@@ -3,9 +3,14 @@ + package sockets + + import ( ++ "io/ioutil" + "net" + "net/rpc" + "os" ++ "path/filepath" ++ "strings" ++ ++ "github.com/juju/errors" + ) + + func Dial(socketPath string) (*rpc.Client, error) { +@@ -17,10 +22,37 @@ + if err := os.Remove(socketPath); err != nil { + logger.Tracef("ignoring error on removing %q: %v", socketPath, err) + } +- listener, err := net.Listen("unix", socketPath) ++ if strings.HasPrefix(socketPath, "@") { ++ listener, err := net.Listen("unix", socketPath) ++ return listener, errors.Trace(err) ++ } ++ // Listen directly to abstract domain sockets. ++ // We first create the socket in a temporary directory as a subdirectory of ++ // the target dir so we know we can get the permissions correct and still ++ // rename the socket into the correct place. ++ // ioutil.TempDir creates the temporary directory as 0700 so it starts with ++ // the right perms as well. ++ socketDir := filepath.Dir(socketPath) ++ tempdir, err := ioutil.TempDir(socketDir, "") ++ if err != nil { ++ return nil, errors.Trace(err) ++ } ++ defer os.RemoveAll(tempdir) ++ // Keep the socket path as short as possible so as not to ++ // exceed the 108 length limit. ++ tempSocketPath := filepath.Join(tempdir, "s") ++ listener, err := net.Listen("unix", tempSocketPath) + if err != nil { +- logger.Errorf("failed to listen on unix:%s: %v", socketPath, err) +- return nil, err ++ logger.Errorf("failed to listen on unix:%s: %v", tempSocketPath, err) ++ return nil, errors.Trace(err) ++ } ++ if err := os.Chmod(tempSocketPath, 0700); err != nil { ++ listener.Close() ++ return nil, errors.Annotatef(err, "could not chmod socket %v", tempSocketPath) ++ } ++ if err := os.Rename(tempSocketPath, socketPath); err != nil { ++ listener.Close() ++ return nil, errors.Annotatef(err, "could not rename socket %v", tempSocketPath) + } +- return listener, err ++ return listener, nil + } +Index: b/src/github.com/juju/juju/worker/uniter/runner/jujuc/server.go +=================================================================== +--- a/src/github.com/juju/juju/worker/uniter/runner/jujuc/server.go ++++ b/src/github.com/juju/juju/worker/uniter/runner/jujuc/server.go +@@ -12,6 +12,7 @@ + "io" + "net" + "net/rpc" ++ "os" + "path/filepath" + "sort" + "sync" +@@ -202,7 +203,7 @@ + } + listener, err := sockets.Listen(socketPath) + if err != nil { +- return nil, err ++ return nil, errors.Annotate(err, "listening to jujuc socket") + } + s := &Server{ + socketPath: socketPath, +@@ -247,6 +248,13 @@ + func (s *Server) Close() { + close(s.closing) + s.listener.Close() ++ // We need to remove the socket path because ++ // we renamed the path after opening the ++ // socket and it won't be cleaned up automatically. ++ // Ignore error as we can't do much here ++ // anyway and remove the path if we start the ++ // server again. ++ os.Remove(s.socketPath) + <-s.closed + } + +Index: b/src/github.com/juju/juju/worker/uniter/runner/runner.go +=================================================================== +--- a/src/github.com/juju/juju/worker/uniter/runner/runner.go ++++ b/src/github.com/juju/juju/worker/uniter/runner/runner.go +@@ -201,7 +201,7 @@ + } + srv, err := jujuc.NewServer(getCmd, runner.paths.GetJujucSocket()) + if err != nil { +- return nil, err ++ return nil, errors.Annotate(err, "starting jujuc server") + } + go srv.Run() + return srv, nil +Index: b/src/github.com/juju/juju/worker/uniter/uniter.go +=================================================================== +--- a/src/github.com/juju/juju/worker/uniter/uniter.go ++++ b/src/github.com/juju/juju/worker/uniter/uniter.go +@@ -20,7 +20,6 @@ + + "github.com/juju/juju/api/uniter" + "github.com/juju/juju/apiserver/params" +- "github.com/juju/juju/version" + "github.com/juju/juju/worker" + "github.com/juju/juju/worker/leadership" + "github.com/juju/juju/worker/uniter/charm" +@@ -276,17 +275,13 @@ + logger.Debugf("starting juju-run listener on unix:%s", u.paths.Runtime.JujuRunSocket) + u.runListener, err = NewRunListener(u, u.paths.Runtime.JujuRunSocket) + if err != nil { +- return err ++ return errors.Annotate(err, "creating juju run listener") + } + u.addCleanup(func() error { + // TODO(fwereade): RunListener returns no error on Close. This seems wrong. + u.runListener.Close() + return nil + }) +- // The socket needs to have permissions 777 in order for other users to use it. +- if version.Current.OS != version.Windows { +- return os.Chmod(u.paths.Runtime.JujuRunSocket, 0777) +- } + return nil + } + diff -Nru juju-core-1-1.25.6/debian/patches/series juju-core-1-1.25.6/debian/patches/series --- juju-core-1-1.25.6/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ juju-core-1-1.25.6/debian/patches/series 2017-05-26 22:30:07.000000000 +0000 @@ -0,0 +1 @@ +CVE-2017-9232.patch