Aleksa Sarai 2017-01-13 17:01:54 +00:00 committed by Git OBS Bridge
parent 4199169c81
commit 6c28b7232c
6 changed files with 241 additions and 7 deletions

225
CVE-2016-8649.patch Normal file
View File

@ -0,0 +1,225 @@
From 9729fc3127c5e27d59a0126f52e91c61037173ae Mon Sep 17 00:00:00 2001
From: Aleksa Sarai <asarai@suse.de>
Date: Tue, 29 Nov 2016 01:25:06 +1100
Subject: [PATCH] libcontainer: init: only pass stateDirFd when creating a
container
If we pass a file descriptor to the host filesystem while joining a
container, there is a race condition where a process inside the
container can ptrace(2) the joining process and stop it from closing its
file descriptor to the stateDirFd. Then the process can access the *host*
filesystem from that file descriptor.
To fix this, don't open or pass the stateDirFd to the init process
unless we're creating a new container. A proper fix for this would be to
remove the need for even passing around directory file descriptors
(which are quite dangerous in the context of mount namespaces).
SUSE: This is an additional patch which was the original "fix" for the
CVE. It was agreed that the patch was too complicated to be used
as a fix for a security issue -- but it will be upstreamed once
the embargo passes.
Fixes: CVE-2016-8649
Signed-off-by: Aleksa Sarai <asarai@suse.de>
---
libcontainer/container_linux.go | 30 ++++++++++++++++++------------
libcontainer/factory_linux.go | 37 ++++++++++++++++++++++---------------
libcontainer/init_linux.go | 3 +--
libcontainer/process_linux.go | 2 --
libcontainer/setns_init_linux.go | 7 +------
5 files changed, 42 insertions(+), 37 deletions(-)
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
index 29c8b3437be3..4110af6fd89d 100644
--- a/libcontainer/container_linux.go
+++ b/libcontainer/container_linux.go
@@ -294,21 +294,29 @@ func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProces
if err != nil {
return nil, newSystemErrorWithCause(err, "creating new init pipe")
}
- rootDir, err := os.Open(c.root)
- if err != nil {
- return nil, err
- }
- cmd, err := c.commandTemplate(p, childPipe, rootDir)
+ cmd, err := c.commandTemplate(p, childPipe)
if err != nil {
return nil, newSystemErrorWithCause(err, "creating new command template")
}
if !doInit {
- return c.newSetnsProcess(p, cmd, parentPipe, childPipe, rootDir)
+ return c.newSetnsProcess(p, cmd, parentPipe, childPipe)
}
+
+ // We only set up rootDir if we're not doing a `runc exec`. The reason for
+ // this is to avoid cases where a racing, unprivileged process inside the
+ // container can get access to the statedir file descriptor (which would
+ // allow for container rootfs escape).
+ rootDir, err := os.Open(c.root)
+ if err != nil {
+ return nil, err
+ }
+ cmd.ExtraFiles = append(cmd.ExtraFiles, rootDir)
+ cmd.Env = append(cmd.Env,
+ fmt.Sprintf("_LIBCONTAINER_STATEDIR=%d", stdioFdCount+len(cmd.ExtraFiles)-1))
return c.newInitProcess(p, cmd, parentPipe, childPipe, rootDir)
}
-func (c *linuxContainer) commandTemplate(p *Process, childPipe, rootDir *os.File) (*exec.Cmd, error) {
+func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.Cmd, error) {
cmd := exec.Command(c.initArgs[0], c.initArgs[1:]...)
cmd.Stdin = p.Stdin
cmd.Stdout = p.Stdout
@@ -317,10 +325,9 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe, rootDir *os.File
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
- cmd.ExtraFiles = append(p.ExtraFiles, childPipe, rootDir)
+ cmd.ExtraFiles = append(p.ExtraFiles, childPipe)
cmd.Env = append(cmd.Env,
- fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-2),
- fmt.Sprintf("_LIBCONTAINER_STATEDIR=%d", stdioFdCount+len(cmd.ExtraFiles)-1))
+ fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1))
// NOTE: when running a container with no PID namespace and the parent process spawning the container is
// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason
// even with the parent still running.
@@ -357,7 +364,7 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
}, nil
}
-func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe, rootDir *os.File) (*setnsProcess, error) {
+func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*setnsProcess, error) {
cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initSetns))
state, err := c.currentState()
if err != nil {
@@ -378,7 +385,6 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
config: c.newInitConfig(p),
process: p,
bootstrapData: data,
- rootDir: rootDir,
}, nil
}
diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go
index cc336c147fb9..e3478d5b50e3 100644
--- a/libcontainer/factory_linux.go
+++ b/libcontainer/factory_linux.go
@@ -222,27 +222,34 @@ func (l *LinuxFactory) Type() string {
// StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state
// This is a low level implementation detail of the reexec and should not be consumed externally
func (l *LinuxFactory) StartInitialization() (err error) {
- var pipefd, rootfd int
- for _, pair := range []struct {
- k string
- v *int
- }{
- {"_LIBCONTAINER_INITPIPE", &pipefd},
- {"_LIBCONTAINER_STATEDIR", &rootfd},
- } {
-
- s := os.Getenv(pair.k)
+ var (
+ pipefd, rootfd int
+ envInitPipe = os.Getenv("_LIBCONTAINER_INITPIPE")
+ envStateDir = os.Getenv("_LIBCONTAINER_STATEDIR")
+ )
- i, err := strconv.Atoi(s)
- if err != nil {
- return fmt.Errorf("unable to convert %s=%s to int", pair.k, s)
- }
- *pair.v = i
+ // Get the INITPIPE.
+ pipefd, err = strconv.Atoi(envInitPipe)
+ if err != nil {
+ return fmt.Errorf("unable to convert _LIBCONTAINER_INITPIPE=%s to int: %s", envInitPipe, err)
}
+
var (
pipe = os.NewFile(uintptr(pipefd), "pipe")
it = initType(os.Getenv("_LIBCONTAINER_INITTYPE"))
)
+
+ // Only get the STATEDIR if we're an init process. It's a bit late to do
+ // anything about this if we've already brought in an fd (a racing process
+ // could've opened the fd by now because we're in the PID namespace).
+ rootfd = -1
+ if it == initStandard {
+ rootfd, err = strconv.Atoi(envStateDir)
+ if err != nil {
+ return fmt.Errorf("unable to convert _LIBCONTAINER_STATEDIR=%s to int: %s", envStateDir, err)
+ }
+ }
+
// clear the current process's environment to clean any libcontainer
// specific env vars.
os.Clearenv()
diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go
index 4043d51c0bd0..b1e6762ecdf3 100644
--- a/libcontainer/init_linux.go
+++ b/libcontainer/init_linux.go
@@ -77,8 +77,7 @@ func newContainerInit(t initType, pipe *os.File, stateDirFD int) (initer, error)
switch t {
case initSetns:
return &linuxSetnsInit{
- config: config,
- stateDirFD: stateDirFD,
+ config: config,
}, nil
case initStandard:
return &linuxStandardInit{
diff --git a/libcontainer/process_linux.go b/libcontainer/process_linux.go
index 5b81317fd711..7b90c6b8faae 100644
--- a/libcontainer/process_linux.go
+++ b/libcontainer/process_linux.go
@@ -51,7 +51,6 @@ type setnsProcess struct {
fds []string
process *Process
bootstrapData io.Reader
- rootDir *os.File
}
func (p *setnsProcess) startTime() (string, error) {
@@ -70,7 +69,6 @@ func (p *setnsProcess) start() (err error) {
defer p.parentPipe.Close()
err = p.cmd.Start()
p.childPipe.Close()
- p.rootDir.Close()
if err != nil {
return newSystemErrorWithCause(err, "starting setns process")
}
diff --git a/libcontainer/setns_init_linux.go b/libcontainer/setns_init_linux.go
index 7f5f182402cc..2a8f34528179 100644
--- a/libcontainer/setns_init_linux.go
+++ b/libcontainer/setns_init_linux.go
@@ -5,7 +5,6 @@ package libcontainer
import (
"fmt"
"os"
- "syscall"
"github.com/opencontainers/runc/libcontainer/apparmor"
"github.com/opencontainers/runc/libcontainer/keys"
@@ -17,8 +16,7 @@ import (
// linuxSetnsInit performs the container's initialization for running a new process
// inside an existing container.
type linuxSetnsInit struct {
- config *initConfig
- stateDirFD int
+ config *initConfig
}
func (l *linuxSetnsInit) getSessionRingName() string {
@@ -51,8 +49,5 @@ func (l *linuxSetnsInit) Init() error {
if err := label.SetProcessLabel(l.config.ProcessLabel); err != nil {
return err
}
- // close the statedir fd before exec because the kernel resets dumpable in the wrong order
- // https://github.com/torvalds/linux/blob/v4.9/fs/exec.c#L1290-L1318
- syscall.Close(l.stateDirFD)
return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ())
}
--
2.11.0

View File

@ -8,7 +8,7 @@
<param name="scm">git</param>
<param name="filename">runc</param>
<param name="versionformat">git.%h</param>
<param name="revision">f59ba3cdd76fdc08c004f42aa915996f6f420899</param>
<param name="revision">50a19c6ff828</param>
<param name="exclude">.git</param>
</service>
<service name="recompress" mode="disabled">

3
runc-git.50a19c6.tar.xz Normal file
View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:971d6eaeb495b0d3368004521dc73fba670d4e0e3fd51408d450cd5bdc393dcf
size 406228

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a82fddecb61fbef51d9dc1de2db9688c0936ea48ad7f023c65d8341aea8d2421
size 406472

View File

@ -1,3 +1,10 @@
-------------------------------------------------------------------
Fri Jan 13 13:58:33 UTC 2017 - jmassaguerpla@suse.com
- fix CVE-2016-9962 bsc#1012568 and applying the patch
CVE-2016-9962.patch, because 1.12.6 partially fixes it (it contains
the first patch attached in bsc#1012568)
-------------------------------------------------------------------
Mon Dec 19 12:49:38 UTC 2016 - jmassaguerpla@suse.com

View File

@ -1,7 +1,7 @@
#
# spec file for package runc
#
# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany.
# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@ -40,13 +40,13 @@
# FIX-OPENSUSE: This will be removed as soon as we move Docker's runC fork into
# a separate package. This whole versioning mess is caused by
# Docker vendoring non-releases of runC.
%define git_version f59ba3cdd76f
%define git_version 50a19c6
# How to get the git_revision
# git clone ${url}.git runc-upstream
# cd runc-upstream
# git checkout $git_version
# git_revision=r$(git rev-list HEAD | wc -l)
%define git_revision r2818
%define git_revision r2819
%define version_unconverted %{git_version}
Name: runc
@ -57,6 +57,7 @@ License: Apache-2.0
Group: System/Management
Url: https://github.com/opencontainers/runc
Source: %{name}-git.%{git_version}.tar.xz
Patch0: CVE-2016-8649.patch
BuildRequires: fdupes
%ifarch %go_arches
BuildRequires: go >= 1.5
@ -102,6 +103,7 @@ Test package for runc. It contains the source code and the tests.
%prep
%setup -q -n %{name}-git.%{git_version}
%patch0 -p1
%build
# Do not use symlinks. If you want to run the unit tests for this package at