From c1dad4d063c75ee39819609082c056047c553e66 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 28 Apr 2014 23:22:54 -0600 Subject: [PATCH] Close extraneous file descriptors in containers Without this patch, containers inherit the open file descriptors of the daemon, so my "exec 42>&2" allows us to "echo >&42 some nasty error with some bad advice" directly into the daemon log. :) Also, "hack/dind" was already doing this due to issues caused by the inheritance, so I'm removing that hack too since this patch obsoletes it by generalizing it for all containers. Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- libcontainer/nsinit/init.go | 8 ++++++-- system/fds_linux.go | 38 +++++++++++++++++++++++++++++++++++++ system/fds_unsupported.go | 12 ++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 system/fds_linux.go create mode 100644 system/fds_unsupported.go diff --git a/libcontainer/nsinit/init.go b/libcontainer/nsinit/init.go index 67095fd..d6b40f3 100644 --- a/libcontainer/nsinit/init.go +++ b/libcontainer/nsinit/init.go @@ -130,12 +130,16 @@ func setupNetwork(container *libcontainer.Container, context libcontainer.Contex return nil } -// finalizeNamespace drops the caps and sets the correct user -// and working dir before execing the command inside the namespace +// finalizeNamespace drops the caps, sets the correct user +// and working dir, and closes any leaky file descriptors +// before execing the command inside the namespace func finalizeNamespace(container *libcontainer.Container) error { if err := capabilities.DropCapabilities(container); err != nil { return fmt.Errorf("drop capabilities %s", err) } + if err := system.CloseFdsFrom(3); err != nil { + return fmt.Errorf("close open file descriptors %s", err) + } if err := setupUser(container); err != nil { return fmt.Errorf("setup user %s", err) } diff --git a/system/fds_linux.go b/system/fds_linux.go new file mode 100644 index 0000000..53d2299 --- /dev/null +++ b/system/fds_linux.go @@ -0,0 +1,38 @@ +package system + +import ( + "io/ioutil" + "strconv" + "syscall" +) + +// Works similarly to OpenBSD's "closefrom(2)": +// The closefrom() call deletes all descriptors numbered fd and higher from +// the per-process file descriptor table. It is effectively the same as +// calling close(2) on each descriptor. +// http://www.openbsd.org/cgi-bin/man.cgi?query=closefrom&sektion=2 +// +// See also http://stackoverflow.com/a/918469/433558 +func CloseFdsFrom(minFd int) error { + fdList, err := ioutil.ReadDir("/proc/self/fd") + if err != nil { + return err + } + for _, fi := range fdList { + fd, err := strconv.Atoi(fi.Name()) + if err != nil { + // ignore non-numeric file names + continue + } + + if fd < minFd { + // ignore descriptors lower than our specified minimum + continue + } + + // intentionally ignore errors from syscall.Close + syscall.Close(fd) + // the cases where this might fail are basically file descriptors that have already been closed (including and especially the one that was created when ioutil.ReadDir did the "opendir" syscall) + } + return nil +} diff --git a/system/fds_unsupported.go b/system/fds_unsupported.go new file mode 100644 index 0000000..c1e08e8 --- /dev/null +++ b/system/fds_unsupported.go @@ -0,0 +1,12 @@ +// +build !linux + +package system + +import ( + "fmt" + "runtime" +) + +func CloseFdsFrom(minFd int) error { + return fmt.Errorf("CloseFdsFrom is unsupported on this platform (%s/%s)", runtime.GOOS, runtime.GOARCH) +}