main: if unprivileged, reexec in a user namespace
If our CLI is invoked as an unprivileged user (uid != 0),
* create a namespace using our own UID and GID as "0" followed by the
ranges matching our name and our primary group's name that we find in
/etc/subuid and /etc/subgid (the latter by way of using newuidmap and
newgidmap)
* re-exec ourselves inside of that user namespace, prepending global CLI arguments that:
* override the driver from storage.conf with "vfs"
* override the storage root from storage.conf with a "containers/storage" subdirectory
of $XDG_DATA_HOME, or $HOME/.local/share.
* override the storage runroot from storage.conf with either "$XDG_RUNTIME_DIR/run" or
"/var/run/user/$uid/run"
* set default ID mapping settings to map all of the ranges matching
our name and our primary group's name that we found in /etc/subuid
and /etc/subgid
* can still be overridden using the command line
Add a "buildah unshare" CLI that will start an arbitrary command in the
first namespace, so that manual cleanup of locations used by the second
namespace will be possible.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #823
Approved by: rhatdan
This commit is contained in:
parent
1accccec61
commit
18063d19b6
2
Makefile
2
Makefile
|
|
@ -17,7 +17,7 @@ LDFLAGS := -ldflags '-X main.gitCommit=${GIT_COMMIT} -X main.buildInfo=${BUILD_I
|
||||||
|
|
||||||
all: buildah imgtype docs
|
all: buildah imgtype docs
|
||||||
|
|
||||||
buildah: *.go imagebuildah/*.go bind/*.go cmd/buildah/*.go docker/*.go pkg/cli/*.go pkg/parse/*.go util/*.go
|
buildah: *.go imagebuildah/*.go bind/*.go cmd/buildah/*.go docker/*.go pkg/cli/*.go pkg/parse/*.go unshare/*.c unshare/*.go util/*.go
|
||||||
$(GO) build $(LDFLAGS) -o buildah $(BUILDFLAGS) ./cmd/buildah
|
$(GO) build $(LDFLAGS) -o buildah $(BUILDFLAGS) ./cmd/buildah
|
||||||
|
|
||||||
imgtype: *.go docker/*.go util/*.go tests/imgtype/imgtype.go
|
imgtype: *.go docker/*.go util/*.go tests/imgtype/imgtype.go
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ func main() {
|
||||||
debug = true
|
debug = true
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
}
|
}
|
||||||
|
maybeReexecUsingUserNamespace(c, false)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
app.After = func(c *cli.Context) error {
|
app.After = func(c *cli.Context) error {
|
||||||
|
|
@ -110,6 +111,7 @@ func main() {
|
||||||
runCommand,
|
runCommand,
|
||||||
tagCommand,
|
tagCommand,
|
||||||
umountCommand,
|
umountCommand,
|
||||||
|
unshareCommand,
|
||||||
versionCommand,
|
versionCommand,
|
||||||
}
|
}
|
||||||
err := app.Run(os.Args)
|
err := app.Run(os.Args)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,234 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/projectatomic/buildah/unshare"
|
||||||
|
"github.com/projectatomic/buildah/util"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// startedInUserNS is an environment variable that, if set, means that we shouldn't try
|
||||||
|
// to create and enter a new user namespace and then re-exec ourselves.
|
||||||
|
startedInUserNS = "_BUILDAH_STARTED_IN_USERNS"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
unshareDescription = "Runs a command in a modified user namespace"
|
||||||
|
unshareCommand = cli.Command{
|
||||||
|
Name: "unshare",
|
||||||
|
Usage: "Run a command in a modified user namespace",
|
||||||
|
Description: unshareDescription,
|
||||||
|
Action: unshareCmd,
|
||||||
|
ArgsUsage: "[COMMAND [ARGS [...]]]",
|
||||||
|
SkipArgReorder: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type runnable interface {
|
||||||
|
Run() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func maybeReexecUsingUserNamespace(c *cli.Context, evenForRoot bool) {
|
||||||
|
// If we've already been through this once, no need to try again.
|
||||||
|
if os.Getenv(startedInUserNS) != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out if we're already root, or "root", which is close enough,
|
||||||
|
// unless we've been explicitly told to do this even for root.
|
||||||
|
me, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error determining current user: %v", err)
|
||||||
|
cli.OsExiter(1)
|
||||||
|
}
|
||||||
|
if me.Uid == "0" && !evenForRoot {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uidNum, err := strconv.ParseUint(me.Uid, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error parsing current UID %s: %v", me.Uid, err)
|
||||||
|
cli.OsExiter(1)
|
||||||
|
}
|
||||||
|
gidNum, err := strconv.ParseUint(me.Gid, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error parsing current GID %s: %v", me.Gid, err)
|
||||||
|
cli.OsExiter(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
// Read the set of ID mappings that we're allowed to use. Each range
|
||||||
|
// in /etc/subuid and /etc/subgid file is a starting ID and a range size.
|
||||||
|
uidmap, gidmap, err := util.GetSubIDMappings(me.Username, me.Username)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error reading allowed ID mappings: %v", err)
|
||||||
|
cli.OsExiter(1)
|
||||||
|
}
|
||||||
|
if len(uidmap) == 0 {
|
||||||
|
logrus.Warnf("Found no UID ranges set aside for user %q in /etc/subuid.", me.Username)
|
||||||
|
}
|
||||||
|
if len(gidmap) == 0 {
|
||||||
|
logrus.Warnf("Found no GID ranges set aside for user %q in /etc/subgid.", me.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build modified maps that map us to uid/gid 0, and maps every other
|
||||||
|
// range to itself. In a namespace that uses this map, the invoking
|
||||||
|
// user will appear to be root. This should let us create storage
|
||||||
|
// directories and access credentials under the invoking user's home
|
||||||
|
// directory.
|
||||||
|
uidmap2 := append([]specs.LinuxIDMapping{{HostID: uint32(uidNum), ContainerID: 0, Size: 1}}, uidmap...)
|
||||||
|
for i := range uidmap2[1:] {
|
||||||
|
uidmap2[i+1].ContainerID = uidmap2[i+1].HostID
|
||||||
|
}
|
||||||
|
gidmap2 := append([]specs.LinuxIDMapping{{HostID: uint32(gidNum), ContainerID: 0, Size: 1}}, gidmap...)
|
||||||
|
for i := range gidmap2[1:] {
|
||||||
|
gidmap2[i+1].ContainerID = gidmap2[i+1].HostID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the uidmap and gidmap ranges, consecutively, starting at 0.
|
||||||
|
// When used to created a namespace inside of a namespace that uses the
|
||||||
|
// maps we've created above, they'll produce mappings which don't map
|
||||||
|
// in the invoking user. This is more suitable for running commands in
|
||||||
|
// containers, so we'll want to use it as a default for any containers
|
||||||
|
// that we create.
|
||||||
|
umap := new(bytes.Buffer)
|
||||||
|
for i := range uidmap {
|
||||||
|
if i > 0 {
|
||||||
|
fmt.Fprintf(umap, ",")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(umap, "%d:%d:%d", uidmap[i].ContainerID, uidmap[i].HostID, uidmap[i].Size)
|
||||||
|
}
|
||||||
|
gmap := new(bytes.Buffer)
|
||||||
|
for i := range gidmap {
|
||||||
|
if i > 0 {
|
||||||
|
fmt.Fprintf(gmap, ",")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(gmap, "%d:%d:%d", gidmap[i].ContainerID, gidmap[i].HostID, gidmap[i].Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add args to change the global defaults.
|
||||||
|
defaultStorageDriver := "vfs"
|
||||||
|
defaultRoot, err := util.UnsharedRootPath(me.HomeDir)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("%v", err)
|
||||||
|
cli.OsExiter(1)
|
||||||
|
}
|
||||||
|
defaultRunroot, err := util.UnsharedRunrootPath(me.Uid)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("%v", err)
|
||||||
|
cli.OsExiter(1)
|
||||||
|
}
|
||||||
|
var moreArgs []string
|
||||||
|
if !c.GlobalIsSet("storage-driver") || !c.GlobalIsSet("root") || !c.GlobalIsSet("runroot") || (!c.GlobalIsSet("userns-uid-map") && !c.GlobalIsSet("userns-gid-map")) {
|
||||||
|
logrus.Infof("Running without privileges, assuming arguments:")
|
||||||
|
if !c.GlobalIsSet("storage-driver") {
|
||||||
|
logrus.Infof(" --storage-driver %q", defaultStorageDriver)
|
||||||
|
moreArgs = append(moreArgs, "--storage-driver", defaultStorageDriver)
|
||||||
|
}
|
||||||
|
if !c.GlobalIsSet("root") {
|
||||||
|
logrus.Infof(" --root %q", defaultRoot)
|
||||||
|
moreArgs = append(moreArgs, "--root", defaultRoot)
|
||||||
|
}
|
||||||
|
if !c.GlobalIsSet("runroot") {
|
||||||
|
logrus.Infof(" --runroot %q", defaultRunroot)
|
||||||
|
moreArgs = append(moreArgs, "--runroot", defaultRunroot)
|
||||||
|
}
|
||||||
|
if !c.GlobalIsSet("userns-uid-map") && !c.GlobalIsSet("userns-gid-map") && umap.Len() > 0 && gmap.Len() > 0 {
|
||||||
|
logrus.Infof(" --userns-uid-map %q --userns-gid-map %q", umap.String(), gmap.String())
|
||||||
|
moreArgs = append(moreArgs, "--userns-uid-map", umap.String(), "--userns-gid-map", gmap.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlike most uses of reexec or unshare, we're using a name that
|
||||||
|
// _won't_ be recognized as a registered reexec handler, since we
|
||||||
|
// _want_ to fall through reexec.Init() to the normal main().
|
||||||
|
cmd := unshare.Command(append(append([]string{"buildah-unprivileged"}, moreArgs...), os.Args[1:]...)...)
|
||||||
|
|
||||||
|
// If, somehow, we don't become UID 0 in our child, indicate that the child shouldn't try again.
|
||||||
|
if err = os.Setenv(startedInUserNS, "1"); err != nil {
|
||||||
|
logrus.Errorf("error setting %s=1 in environment: %v", startedInUserNS, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reuse our stdio.
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
// Set up a new user namespace with the ID mapping.
|
||||||
|
cmd.UnshareFlags = syscall.CLONE_NEWUSER
|
||||||
|
cmd.UseNewuidmap = true
|
||||||
|
cmd.UidMappings = uidmap2
|
||||||
|
cmd.UseNewgidmap = true
|
||||||
|
cmd.GidMappings = gidmap2
|
||||||
|
cmd.GidMappingsEnableSetgroups = true
|
||||||
|
|
||||||
|
// Finish up.
|
||||||
|
logrus.Debugf("running %+v with environment %+v, UID map %+v, and GID map %+v", cmd.Cmd.Args, os.Environ(), cmd.UidMappings, cmd.GidMappings)
|
||||||
|
execRunnable(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// execRunnable runs the specified unshare command, captures its exit status,
|
||||||
|
// and exits with the same status.
|
||||||
|
func execRunnable(cmd runnable) {
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
if exitError, ok := errors.Cause(err).(*exec.ExitError); ok {
|
||||||
|
if exitError.ProcessState.Exited() {
|
||||||
|
if waitStatus, ok := exitError.ProcessState.Sys().(syscall.WaitStatus); ok {
|
||||||
|
if waitStatus.Exited() {
|
||||||
|
logrus.Errorf("%v", exitError)
|
||||||
|
os.Exit(waitStatus.ExitStatus())
|
||||||
|
}
|
||||||
|
if waitStatus.Signaled() {
|
||||||
|
logrus.Errorf("%v", exitError)
|
||||||
|
os.Exit(int(waitStatus.Signal()) + 128)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logrus.Errorf("%v", err)
|
||||||
|
logrus.Errorf("(unable to determine exit status)")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unshareCmd execs whatever using the ID mappings that we want to use for ourselves
|
||||||
|
func unshareCmd(c *cli.Context) error {
|
||||||
|
// force reexec using the configured ID mappings
|
||||||
|
maybeReexecUsingUserNamespace(c, true)
|
||||||
|
// exec the specified command, if there is one
|
||||||
|
args := c.Args()
|
||||||
|
if len(args) < 1 {
|
||||||
|
// try to exec the shell, if one's set
|
||||||
|
shell, shellSet := os.LookupEnv("SHELL")
|
||||||
|
if !shellSet {
|
||||||
|
logrus.Errorf("no command specified")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
args = []string{shell}
|
||||||
|
}
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
cmd.Env = append(os.Environ(), "USER=root", "USERNAME=root", "GROUP=root", "LOGNAME=root", "UID=0", "GID=0")
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
execRunnable(cmd)
|
||||||
|
os.Exit(1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
var (
|
||||||
|
unshareCommand = cli.Command{
|
||||||
|
Name: "unshare",
|
||||||
|
Hidden: true,
|
||||||
|
Action: func(c *cli.Context) error { return nil },
|
||||||
|
SkipArgReorder: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func maybeReexecUsingUserNamespace(c *cli.Context, evenForRoot bool) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -753,6 +753,16 @@ return 1
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_buildah_unshare() {
|
||||||
|
local boolean_options="
|
||||||
|
--help
|
||||||
|
-h
|
||||||
|
"
|
||||||
|
|
||||||
|
local options_with_args="
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
_buildah_version() {
|
_buildah_version() {
|
||||||
local boolean_options="
|
local boolean_options="
|
||||||
--help
|
--help
|
||||||
|
|
@ -761,7 +771,7 @@ return 1
|
||||||
|
|
||||||
local options_with_args="
|
local options_with_args="
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildah() {
|
_buildah() {
|
||||||
local previous_extglob_setting=$(shopt -p extglob)
|
local previous_extglob_setting=$(shopt -p extglob)
|
||||||
|
|
@ -787,6 +797,7 @@ return 1
|
||||||
tag
|
tag
|
||||||
umount
|
umount
|
||||||
unmount
|
unmount
|
||||||
|
unshare
|
||||||
version
|
version
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
Name: buildah
|
Name: buildah
|
||||||
# Bump version in buildah.go too
|
# Bump version in buildah.go too
|
||||||
Version: 1.2
|
Version: 1.2
|
||||||
Release: 1.git%{shortcommit}%{?dist}
|
Release: 2.git%{shortcommit}%{?dist}
|
||||||
Summary: A command line tool used to creating OCI Images
|
Summary: A command line tool used to creating OCI Images
|
||||||
License: ASL 2.0
|
License: ASL 2.0
|
||||||
URL: https://%{provider_prefix}
|
URL: https://%{provider_prefix}
|
||||||
|
|
@ -49,6 +49,7 @@ BuildRequires: make
|
||||||
Requires: runc >= 1.0.0-6
|
Requires: runc >= 1.0.0-6
|
||||||
Requires: container-selinux
|
Requires: container-selinux
|
||||||
Requires: skopeo-containers
|
Requires: skopeo-containers
|
||||||
|
Requires: shadow-utils
|
||||||
Provides: %{repo} = %{version}-%{release}
|
Provides: %{repo} = %{version}-%{release}
|
||||||
|
|
||||||
%description
|
%description
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
# buildah-unshare "19" "June 2018" "buildah"
|
||||||
|
|
||||||
|
## NAME
|
||||||
|
buildah\-unshare - Run a command inside of a modified user namespace.
|
||||||
|
|
||||||
|
## SYNOPSIS
|
||||||
|
**buildah** **unshare** [*options* [...] --] [**command**]
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
Launches a process (by default, *$SHELL*) in a new user namespace. The user
|
||||||
|
namespace is configured so that the invoking user's UID and primary GID appear
|
||||||
|
to be UID 0 and GID 0, respectively. Any ranges which match that user and
|
||||||
|
group in /etc/subuid and /etc/subgid are also mapped in as themselves with the
|
||||||
|
help of the *newuidmap(1)* and *newgidmap(1)* helpers.
|
||||||
|
|
||||||
|
This is mainly useful for troubleshooting unprivileged operations and for
|
||||||
|
manually clearing storage and other data related to images and containers.
|
||||||
|
|
||||||
|
## EXAMPLE
|
||||||
|
|
||||||
|
buildah unshare id
|
||||||
|
|
||||||
|
buildah unshare pwd
|
||||||
|
|
||||||
|
buildah unshare cat /proc/self/uid\_map
|
||||||
|
|
||||||
|
buildah unshare cat /proc/self/gid\_map
|
||||||
|
|
||||||
|
buildah unshare rm -fr $HOME/.local/share/containers/storage /var/run/user/\`id -u\`/run
|
||||||
|
|
||||||
|
## SEE ALSO
|
||||||
|
buildah(1), namespaces(7), newuidmap(1), newgidmap(1), user\_namespaces(7)
|
||||||
|
|
@ -46,23 +46,23 @@ be used, as the default behavior of using the system-wide configuration
|
||||||
|
|
||||||
**--root** **value**
|
**--root** **value**
|
||||||
|
|
||||||
Storage root dir (default: "/var/lib/containers/storage")
|
Storage root dir (default: "/var/lib/containers/storage" for UID 0, "$HOME/.local/share/containers/storage" for other users)
|
||||||
Default root dir is configured in /etc/containers/storage.conf
|
Default root dir is configured in /etc/containers/storage.conf
|
||||||
|
|
||||||
**--runroot** **value**
|
**--runroot** **value**
|
||||||
|
|
||||||
Storage state dir (default: "/var/run/containers/storage")
|
Storage state dir (default: "/var/run/containers/storage" for UID 0, "/var/run/user/$UID/run" for other users)
|
||||||
Default state dir is configured in /etc/containers/storage.conf
|
Default state dir is configured in /etc/containers/storage.conf
|
||||||
|
|
||||||
**--storage-driver** **value**
|
**--storage-driver** **value**
|
||||||
|
|
||||||
Storage driver. Default Storage driver is configured in /etc/containers/storage.conf. Overriding
|
Storage driver. The default storage driver for UID 0 is configured in /etc/containers/storage.conf, and is *vfs* for other users.
|
||||||
this option, will drop the storage-opt definitions was well from the storage.conf file. User must
|
Overriding this option will cause the *storage-opt* settings in /etc/containers/storage.conf to be ignored. The user must
|
||||||
specify additional options via the `--storage-opt` flag.
|
specify additional options via the `--storage-opt` flag.
|
||||||
|
|
||||||
**--storage-opt** **value**
|
**--storage-opt** **value**
|
||||||
|
|
||||||
Storage driver option, Default Storage driver options are configured in /etc/containers/storage.conf
|
Storage driver option, Default storage driver options are configured in /etc/containers/storage.conf
|
||||||
|
|
||||||
**--userns-uid-map** *mapping*
|
**--userns-uid-map** *mapping*
|
||||||
|
|
||||||
|
|
@ -107,8 +107,9 @@ Print the version
|
||||||
| buildah-run(1) | Run a command inside of the container. |
|
| buildah-run(1) | Run a command inside of the container. |
|
||||||
| buildah-tag(1) | Add an additional name to a local image. |
|
| buildah-tag(1) | Add an additional name to a local image. |
|
||||||
| buildah-umount(1) | Unmount a working container's root file system. |
|
| buildah-umount(1) | Unmount a working container's root file system. |
|
||||||
| buildah-version(1) | Display the Buildah Version Information |
|
| buildah-unshare(1) | Launch a command in a user namespace with modified ID mappings. |
|
||||||
| storage.conf(5) | Syntax of Container Storage configuration file |
|
| buildah-version(1) | Display the Buildah Version Information |
|
||||||
|
| storage.conf(5) | Syntax of Container Storage configuration file |
|
||||||
|
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
@ -134,7 +135,7 @@ Print the version
|
||||||
registries.conf is the configuration file which specifies which container registries should be consulted when completing image names which do not include a registry or domain portion.
|
registries.conf is the configuration file which specifies which container registries should be consulted when completing image names which do not include a registry or domain portion.
|
||||||
|
|
||||||
## SEE ALSO
|
## SEE ALSO
|
||||||
podman(1), mounts.conf(5), registries.conf(5), storage.conf(5)
|
podman(1), mounts.conf(5), newuidmap(1), newgidmap(1), registries.conf(5), storage.conf(5)
|
||||||
|
|
||||||
## HISTORY
|
## HISTORY
|
||||||
December 2017, Originally compiled by Tom Sweeney <tsweeney@redhat.com>
|
December 2017, Originally compiled by Tom Sweeney <tsweeney@redhat.com>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <grp.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static int _buildah_unshare_parse_envint(const char *envname) {
|
||||||
|
char *p, *q;
|
||||||
|
long l;
|
||||||
|
|
||||||
|
p = getenv(envname);
|
||||||
|
if (p == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
q = NULL;
|
||||||
|
l = strtol(p, &q, 10);
|
||||||
|
if ((q == NULL) || (*q != '\0')) {
|
||||||
|
fprintf(stderr, "Error parsing \"%s\"=\"%s\"!\n", envname, p);
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
unsetenv(envname);
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _buildah_unshare(void)
|
||||||
|
{
|
||||||
|
int flags, pidfd, continuefd, n, pgrp, sid, ctty, allow_setgroups;
|
||||||
|
char buf[2048];
|
||||||
|
|
||||||
|
flags = _buildah_unshare_parse_envint("_Buildah-unshare");
|
||||||
|
if (flags == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((flags & CLONE_NEWUSER) != 0) {
|
||||||
|
if (unshare(CLONE_NEWUSER) == -1) {
|
||||||
|
fprintf(stderr, "Error during unshare(CLONE_NEWUSER): %m\n");
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pidfd = _buildah_unshare_parse_envint("_Buildah-pid-pipe");
|
||||||
|
if (pidfd != -1) {
|
||||||
|
snprintf(buf, sizeof(buf), "%llu", (unsigned long long) getpid());
|
||||||
|
if (write(pidfd, buf, strlen(buf)) != strlen(buf)) {
|
||||||
|
fprintf(stderr, "Error writing PID to pipe on fd %d: %m\n", pidfd);
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
close(pidfd);
|
||||||
|
}
|
||||||
|
continuefd = _buildah_unshare_parse_envint("_Buildah-continue-pipe");
|
||||||
|
if (continuefd != -1) {
|
||||||
|
n = read(continuefd, buf, sizeof(buf));
|
||||||
|
if (n > 0) {
|
||||||
|
fprintf(stderr, "Error: %.*s\n", n, buf);
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
close(continuefd);
|
||||||
|
}
|
||||||
|
sid = _buildah_unshare_parse_envint("_Buildah-setsid");
|
||||||
|
if (sid == 1) {
|
||||||
|
if (setsid() == -1) {
|
||||||
|
fprintf(stderr, "Error during setsid: %m\n");
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pgrp = _buildah_unshare_parse_envint("_Buildah-setpgrp");
|
||||||
|
if (pgrp == 1) {
|
||||||
|
if (setpgrp() == -1) {
|
||||||
|
fprintf(stderr, "Error during setpgrp: %m\n");
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctty = _buildah_unshare_parse_envint("_Buildah-ctty");
|
||||||
|
if (ctty != -1) {
|
||||||
|
if (ioctl(ctty, TIOCSCTTY, 0) == -1) {
|
||||||
|
fprintf(stderr, "Error while setting controlling terminal to %d: %m\n", ctty);
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allow_setgroups = _buildah_unshare_parse_envint("_Buildah-allow-setgroups");
|
||||||
|
if ((flags & CLONE_NEWUSER) != 0) {
|
||||||
|
if (allow_setgroups == 1) {
|
||||||
|
if (setgroups(0, NULL) != 0) {
|
||||||
|
fprintf(stderr, "Error during setgroups(0, NULL): %m\n");
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (setresgid(0, 0, 0) != 0) {
|
||||||
|
fprintf(stderr, "Error during setresgid(0): %m\n");
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
if (setresuid(0, 0, 0) != 0) {
|
||||||
|
fprintf(stderr, "Error during setresuid(0): %m\n");
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((flags & ~CLONE_NEWUSER) != 0) {
|
||||||
|
if (unshare(flags & ~CLONE_NEWUSER) == -1) {
|
||||||
|
fprintf(stderr, "Error during unshare(...): %m\n");
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,273 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package unshare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containers/storage/pkg/reexec"
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/projectatomic/buildah/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cmd wraps an exec.Cmd created by the reexec package in unshare(), and
|
||||||
|
// handles setting ID maps and other related settings by triggering
|
||||||
|
// initialization code in the child.
|
||||||
|
type Cmd struct {
|
||||||
|
*exec.Cmd
|
||||||
|
UnshareFlags int
|
||||||
|
UseNewuidmap bool
|
||||||
|
UidMappings []specs.LinuxIDMapping
|
||||||
|
UseNewgidmap bool
|
||||||
|
GidMappings []specs.LinuxIDMapping
|
||||||
|
GidMappingsEnableSetgroups bool
|
||||||
|
Setsid bool
|
||||||
|
Setpgrp bool
|
||||||
|
Ctty *os.File
|
||||||
|
OOMScoreAdj int
|
||||||
|
Hook func(pid int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command creates a new Cmd which can be customized.
|
||||||
|
func Command(args ...string) *Cmd {
|
||||||
|
cmd := reexec.Command(args...)
|
||||||
|
return &Cmd{
|
||||||
|
Cmd: cmd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cmd) Start() error {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
// Set an environment variable to tell the child to synchronize its startup.
|
||||||
|
if c.Env == nil {
|
||||||
|
c.Env = os.Environ()
|
||||||
|
}
|
||||||
|
c.Env = append(c.Env, fmt.Sprintf("_Buildah-unshare=%d", c.UnshareFlags))
|
||||||
|
|
||||||
|
// Create the pipe for reading the child's PID.
|
||||||
|
pidRead, pidWrite, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error creating pid pipe")
|
||||||
|
}
|
||||||
|
c.Env = append(c.Env, fmt.Sprintf("_Buildah-pid-pipe=%d", len(c.ExtraFiles)+3))
|
||||||
|
c.ExtraFiles = append(c.ExtraFiles, pidWrite)
|
||||||
|
|
||||||
|
// Create the pipe for letting the child know to proceed.
|
||||||
|
continueRead, continueWrite, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
pidRead.Close()
|
||||||
|
pidWrite.Close()
|
||||||
|
return errors.Wrapf(err, "error creating pid pipe")
|
||||||
|
}
|
||||||
|
c.Env = append(c.Env, fmt.Sprintf("_Buildah-continue-pipe=%d", len(c.ExtraFiles)+3))
|
||||||
|
c.ExtraFiles = append(c.ExtraFiles, continueRead)
|
||||||
|
|
||||||
|
// Pass along other instructions.
|
||||||
|
if c.Setsid {
|
||||||
|
c.Env = append(c.Env, "_Buildah-setsid=1")
|
||||||
|
}
|
||||||
|
if c.Setpgrp {
|
||||||
|
c.Env = append(c.Env, "_Buildah-setpgrp=1")
|
||||||
|
}
|
||||||
|
if c.Ctty != nil {
|
||||||
|
c.Env = append(c.Env, fmt.Sprintf("_Buildah-ctty=%d", len(c.ExtraFiles)+3))
|
||||||
|
c.ExtraFiles = append(c.ExtraFiles, c.Ctty)
|
||||||
|
}
|
||||||
|
if c.GidMappingsEnableSetgroups {
|
||||||
|
c.Env = append(c.Env, "_Buildah-allow-setgroups=1")
|
||||||
|
} else {
|
||||||
|
c.Env = append(c.Env, "_Buildah-allow-setgroups=0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we clean up our pipes.
|
||||||
|
defer func() {
|
||||||
|
if pidRead != nil {
|
||||||
|
pidRead.Close()
|
||||||
|
}
|
||||||
|
if pidWrite != nil {
|
||||||
|
pidWrite.Close()
|
||||||
|
}
|
||||||
|
if continueRead != nil {
|
||||||
|
continueRead.Close()
|
||||||
|
}
|
||||||
|
if continueWrite != nil {
|
||||||
|
continueWrite.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Start the new process.
|
||||||
|
err = c.Cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the ends of the pipes that the parent doesn't need.
|
||||||
|
continueRead.Close()
|
||||||
|
continueRead = nil
|
||||||
|
pidWrite.Close()
|
||||||
|
pidWrite = nil
|
||||||
|
|
||||||
|
// Read the child's PID from the pipe.
|
||||||
|
pidString := ""
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
io.Copy(b, pidRead)
|
||||||
|
pidString = b.String()
|
||||||
|
pid, err := strconv.Atoi(pidString)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "error parsing PID %q: %v", pidString, err)
|
||||||
|
return errors.Wrapf(err, "error parsing PID %q", pidString)
|
||||||
|
}
|
||||||
|
pidString = fmt.Sprintf("%d", pid)
|
||||||
|
|
||||||
|
// If we created a new user namespace, set any specified mappings.
|
||||||
|
if c.UnshareFlags&syscall.CLONE_NEWUSER != 0 {
|
||||||
|
// Always set "setgroups".
|
||||||
|
setgroups, err := os.OpenFile(fmt.Sprintf("/proc/%s/setgroups", pidString), os.O_TRUNC|os.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "error opening setgroups: %v", err)
|
||||||
|
return errors.Wrapf(err, "error opening /proc/%s/setgroups", pidString)
|
||||||
|
}
|
||||||
|
defer setgroups.Close()
|
||||||
|
if c.GidMappingsEnableSetgroups {
|
||||||
|
if _, err := fmt.Fprintf(setgroups, "allow"); err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "error writing \"allow\" to setgroups: %v", err)
|
||||||
|
return errors.Wrapf(err, "error opening \"allow\" to /proc/%s/setgroups", pidString)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err := fmt.Fprintf(setgroups, "deny"); err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "error writing \"deny\" to setgroups: %v", err)
|
||||||
|
return errors.Wrapf(err, "error writing \"deny\" to /proc/%s/setgroups", pidString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.UidMappings) == 0 || len(c.GidMappings) == 0 {
|
||||||
|
uidmap, gidmap, err := util.GetHostIDMappings("")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "error reading ID mappings in parent: %v", err)
|
||||||
|
return errors.Wrapf(err, "error reading ID mappings in parent")
|
||||||
|
}
|
||||||
|
if len(c.UidMappings) == 0 {
|
||||||
|
c.UidMappings = uidmap
|
||||||
|
for i := range c.UidMappings {
|
||||||
|
c.UidMappings[i].HostID = c.UidMappings[i].ContainerID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(c.GidMappings) == 0 {
|
||||||
|
c.GidMappings = gidmap
|
||||||
|
for i := range c.GidMappings {
|
||||||
|
c.GidMappings[i].HostID = c.GidMappings[i].ContainerID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.GidMappings) > 0 {
|
||||||
|
// Build the GID map, since writing to the proc file has to be done all at once.
|
||||||
|
g := new(bytes.Buffer)
|
||||||
|
for _, m := range c.GidMappings {
|
||||||
|
fmt.Fprintf(g, "%d %d %d\n", m.ContainerID, m.HostID, m.Size)
|
||||||
|
}
|
||||||
|
// Set the GID map.
|
||||||
|
if c.UseNewgidmap {
|
||||||
|
cmd := exec.Command("newgidmap", append([]string{pidString}, strings.Fields(strings.Replace(g.String(), "\n", " ", -1))...)...)
|
||||||
|
g.Reset()
|
||||||
|
cmd.Stdout = g
|
||||||
|
cmd.Stderr = g
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "error running newgidmap: %v: %s", err, g.String())
|
||||||
|
return errors.Wrapf(err, "error running newgidmap: %s", g.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gidmap, err := os.OpenFile(fmt.Sprintf("/proc/%s/gid_map", pidString), os.O_TRUNC|os.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "error opening /proc/%s/gid_map: %v", pidString, err)
|
||||||
|
return errors.Wrapf(err, "error opening /proc/%s/gid_map", pidString)
|
||||||
|
}
|
||||||
|
defer gidmap.Close()
|
||||||
|
if _, err := fmt.Fprintf(gidmap, "%s", g.String()); err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "error writing /proc/%s/gid_map: %v", pidString, err)
|
||||||
|
return errors.Wrapf(err, "error writing /proc/%s/gid_map", pidString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.UidMappings) > 0 {
|
||||||
|
// Build the UID map, since writing to the proc file has to be done all at once.
|
||||||
|
u := new(bytes.Buffer)
|
||||||
|
for _, m := range c.UidMappings {
|
||||||
|
fmt.Fprintf(u, "%d %d %d\n", m.ContainerID, m.HostID, m.Size)
|
||||||
|
}
|
||||||
|
// Set the GID map.
|
||||||
|
if c.UseNewuidmap {
|
||||||
|
cmd := exec.Command("newuidmap", append([]string{pidString}, strings.Fields(strings.Replace(u.String(), "\n", " ", -1))...)...)
|
||||||
|
u.Reset()
|
||||||
|
cmd.Stdout = u
|
||||||
|
cmd.Stderr = u
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "error running newuidmap: %v: %s", err, u.String())
|
||||||
|
return errors.Wrapf(err, "error running newuidmap: %s", u.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uidmap, err := os.OpenFile(fmt.Sprintf("/proc/%s/uid_map", pidString), os.O_TRUNC|os.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "error opening /proc/%s/uid_map: %v", pidString, err)
|
||||||
|
return errors.Wrapf(err, "error opening /proc/%s/uid_map", pidString)
|
||||||
|
}
|
||||||
|
defer uidmap.Close()
|
||||||
|
if _, err := fmt.Fprintf(uidmap, "%s", u.String()); err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "error writing /proc/%s/uid_map: %v", pidString, err)
|
||||||
|
return errors.Wrapf(err, "error writing /proc/%s/uid_map", pidString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust the process's OOM score.
|
||||||
|
oomScoreAdj, err := os.OpenFile(fmt.Sprintf("/proc/%s/oom_score_adj", pidString), os.O_TRUNC|os.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "error opening oom_score_adj: %v", err)
|
||||||
|
return errors.Wrapf(err, "error opening /proc/%s/oom_score_adj", pidString)
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(oomScoreAdj, "%d\n", c.OOMScoreAdj); err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "error writing \"%d\" to oom_score_adj: %v", c.OOMScoreAdj, err)
|
||||||
|
return errors.Wrapf(err, "error writing \"%d\" to /proc/%s/oom_score_adj", c.OOMScoreAdj)
|
||||||
|
}
|
||||||
|
defer oomScoreAdj.Close()
|
||||||
|
|
||||||
|
// Run any additional setup that we want to do before the child starts running proper.
|
||||||
|
if c.Hook != nil {
|
||||||
|
if err = c.Hook(pid); err != nil {
|
||||||
|
fmt.Fprintf(continueWrite, "hook error: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cmd) Run() error {
|
||||||
|
if err := c.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cmd) CombinedOutput() ([]byte, error) {
|
||||||
|
return nil, errors.New("unshare: CombinedOutput() not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cmd) Output() ([]byte, error) {
|
||||||
|
return nil, errors.New("unshare: Output() not implemented")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
// +build linux,cgo,!gccgo
|
||||||
|
|
||||||
|
package unshare
|
||||||
|
|
||||||
|
// #cgo CFLAGS: -Wall
|
||||||
|
// extern void _buildah_unshare(void);
|
||||||
|
// void __attribute__((constructor)) init(void) {
|
||||||
|
// _buildah_unshare();
|
||||||
|
// }
|
||||||
|
import "C"
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
// +build linux,cgo,gccgo
|
||||||
|
|
||||||
|
package unshare
|
||||||
|
|
||||||
|
// #cgo CFLAGS: -Wall -Wextra
|
||||||
|
// extern void _buildah_unshare(void);
|
||||||
|
// void __attribute__((constructor)) init(void) {
|
||||||
|
// _buildah_unshare();
|
||||||
|
// }
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// This next bit is straight out of libcontainer.
|
||||||
|
|
||||||
|
// AlwaysFalse is here to stay false
|
||||||
|
// (and be exported so the compiler doesn't optimize out its reference)
|
||||||
|
var AlwaysFalse bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if AlwaysFalse {
|
||||||
|
// by referencing this C init() in a noop test, it will ensure the compiler
|
||||||
|
// links in the C function.
|
||||||
|
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65134
|
||||||
|
C.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,255 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package unshare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containers/storage/pkg/reexec"
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/projectatomic/buildah/util"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
if reexec.Init() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
reexec.Register("report", report)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
CloneFlags = map[string]int{
|
||||||
|
"ipc": syscall.CLONE_NEWIPC,
|
||||||
|
"net": syscall.CLONE_NEWNET,
|
||||||
|
"mnt": syscall.CLONE_NEWNS,
|
||||||
|
"user": syscall.CLONE_NEWUSER,
|
||||||
|
"uts": syscall.CLONE_NEWUTS,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Report struct {
|
||||||
|
Namespaces map[string]string
|
||||||
|
UIDMappings []specs.LinuxIDMapping
|
||||||
|
GIDMappings []specs.LinuxIDMapping
|
||||||
|
Pgrp int
|
||||||
|
Sid int
|
||||||
|
OOMScoreAdj int
|
||||||
|
}
|
||||||
|
|
||||||
|
func report() {
|
||||||
|
var report Report
|
||||||
|
report.Namespaces = make(map[string]string)
|
||||||
|
|
||||||
|
for name := range CloneFlags {
|
||||||
|
linkTarget, err := os.Readlink("/proc/self/ns/" + name)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error reading link /proc/self/ns/%s: %v", name, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
report.Namespaces[name] = linkTarget
|
||||||
|
}
|
||||||
|
|
||||||
|
report.Pgrp = syscall.Getpgrp()
|
||||||
|
|
||||||
|
sid, err := unix.Getsid(unix.Getpid())
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error reading current session ID: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
report.Sid = sid
|
||||||
|
|
||||||
|
oomBytes, err := ioutil.ReadFile("/proc/self/oom_score_adj")
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error reading current oom_score_adj: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
oomFields := strings.Fields(string(oomBytes))
|
||||||
|
if len(oomFields) != 1 {
|
||||||
|
logrus.Errorf("error parsing current oom_score_adj %q: wrong number of fields", string(oomBytes))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
oom, err := strconv.Atoi(oomFields[0])
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error parsing current oom_score_adj %q: %v", oomFields[0], err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
report.OOMScoreAdj = oom
|
||||||
|
|
||||||
|
uidmap, gidmap, err := util.GetHostIDMappings("")
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error reading current ID mappings: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
for _, m := range uidmap {
|
||||||
|
report.UIDMappings = append(report.UIDMappings, m)
|
||||||
|
}
|
||||||
|
for _, m := range gidmap {
|
||||||
|
report.GIDMappings = append(report.GIDMappings, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewEncoder(os.Stdout).Encode(report)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnshareNamespaces(t *testing.T) {
|
||||||
|
for name, flag := range CloneFlags {
|
||||||
|
var report Report
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := Command("report")
|
||||||
|
cmd.UnshareFlags = syscall.CLONE_NEWUSER | flag
|
||||||
|
cmd.UidMappings = []specs.LinuxIDMapping{{HostID: uint32(syscall.Getuid()), ContainerID: 0, Size: 1}}
|
||||||
|
cmd.GidMappings = []specs.LinuxIDMapping{{HostID: uint32(syscall.Getgid()), ContainerID: 0, Size: 1}}
|
||||||
|
cmd.Stdout = buf
|
||||||
|
cmd.Stderr = buf
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("run %q: %v: %s", name, err, buf.String())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(buf.Bytes(), &report); err != nil {
|
||||||
|
t.Fatalf("error parsing results: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for ns := range CloneFlags {
|
||||||
|
linkTarget, err := os.Readlink("/proc/self/ns/" + ns)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error reading link /proc/self/ns/%s: %v", ns, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if ns == name || ns == "user" { // we always create a new user namespace
|
||||||
|
if report.Namespaces[ns] == linkTarget {
|
||||||
|
t.Fatalf("child is still in our %q namespace", name)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if report.Namespaces[ns] != linkTarget {
|
||||||
|
t.Fatalf("child is not in our %q namespace", name)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnsharePgrp(t *testing.T) {
|
||||||
|
for _, same := range []bool{false, true} {
|
||||||
|
var report Report
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := Command("report")
|
||||||
|
cmd.Setpgrp = !same
|
||||||
|
cmd.Stdout = buf
|
||||||
|
cmd.Stderr = buf
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("run: %v: %s", err, buf.String())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(buf.Bytes(), &report); err != nil {
|
||||||
|
t.Fatalf("error parsing results: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (report.Pgrp == syscall.Getpgrp()) != same {
|
||||||
|
t.Fatalf("expected %d == %d to be %v", report.Pgrp, syscall.Getpgrp(), same)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnshareSid(t *testing.T) {
|
||||||
|
sid, err := unix.Getsid(unix.Getpid())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error reading current session ID: %v", err)
|
||||||
|
}
|
||||||
|
for _, same := range []bool{false, true} {
|
||||||
|
var report Report
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := Command("report")
|
||||||
|
cmd.Setsid = !same
|
||||||
|
cmd.Stdout = buf
|
||||||
|
cmd.Stderr = buf
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("run: %v: %s", err, buf.String())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(buf.Bytes(), &report); err != nil {
|
||||||
|
t.Fatalf("error parsing results: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (report.Sid == sid) != same {
|
||||||
|
t.Fatalf("expected %d == %d to be %v", report.Sid, sid, same)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnshareOOMScoreAdj(t *testing.T) {
|
||||||
|
for _, adj := range []int{0, 1, 2, 3} {
|
||||||
|
var report Report
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := Command("report")
|
||||||
|
cmd.OOMScoreAdj = adj
|
||||||
|
cmd.Stdout = buf
|
||||||
|
cmd.Stderr = buf
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("run: %v: %s", err, buf.String())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(buf.Bytes(), &report); err != nil {
|
||||||
|
t.Fatalf("error parsing results: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if report.OOMScoreAdj != adj {
|
||||||
|
t.Fatalf("saw oom_score_adj %d to be %v", adj, report.OOMScoreAdj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnshareIDMappings(t *testing.T) {
|
||||||
|
var report Report
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
cmd := Command("report")
|
||||||
|
cmd.UnshareFlags = syscall.CLONE_NEWUSER
|
||||||
|
cmd.UidMappings = []specs.LinuxIDMapping{{HostID: uint32(syscall.Getuid()), ContainerID: 0, Size: 1}}
|
||||||
|
cmd.GidMappings = []specs.LinuxIDMapping{{HostID: uint32(syscall.Getgid()), ContainerID: 0, Size: 1}}
|
||||||
|
cmd.Stdout = buf
|
||||||
|
cmd.Stderr = buf
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("run: %v: %s", err, buf.String())
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(buf.Bytes(), &report); err != nil {
|
||||||
|
t.Fatalf("error parsing results: %v", err)
|
||||||
|
}
|
||||||
|
if len(cmd.UidMappings) != len(report.UIDMappings) {
|
||||||
|
t.Fatalf("set %d UID mappings, read %d instead", len(cmd.UidMappings), len(report.UIDMappings))
|
||||||
|
}
|
||||||
|
for i := range cmd.UidMappings {
|
||||||
|
if cmd.UidMappings[i].ContainerID != report.UIDMappings[i].ContainerID ||
|
||||||
|
cmd.UidMappings[i].HostID != report.UIDMappings[i].HostID ||
|
||||||
|
cmd.UidMappings[i].Size != report.UIDMappings[i].Size {
|
||||||
|
t.Fatalf("uid map entry %#v != %#v", cmd.UidMappings[i], report.UIDMappings[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(cmd.GidMappings) != len(report.GIDMappings) {
|
||||||
|
t.Fatalf("set %d GID mappings, read %d instead", len(cmd.GidMappings), len(report.GIDMappings))
|
||||||
|
}
|
||||||
|
for i := range cmd.GidMappings {
|
||||||
|
if cmd.GidMappings[i].ContainerID != report.GIDMappings[i].ContainerID ||
|
||||||
|
cmd.GidMappings[i].HostID != report.GIDMappings[i].HostID ||
|
||||||
|
cmd.GidMappings[i].Size != report.GIDMappings[i].Size {
|
||||||
|
t.Fatalf("gid map entry %#v != %#v", cmd.GidMappings[i], report.GIDMappings[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package unshare
|
||||||
38
util/util.go
38
util/util.go
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -330,7 +331,8 @@ func getHostIDMappings(path string) ([]specs.LinuxIDMapping, error) {
|
||||||
return mappings, nil
|
return mappings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHostIDMappings reads mappings for the current process from the kernel.
|
// GetHostIDMappings reads mappings for the specified process (or the current
|
||||||
|
// process if pid is "self" or an empty string) from the kernel.
|
||||||
func GetHostIDMappings(pid string) ([]specs.LinuxIDMapping, []specs.LinuxIDMapping, error) {
|
func GetHostIDMappings(pid string) ([]specs.LinuxIDMapping, []specs.LinuxIDMapping, error) {
|
||||||
if pid == "" {
|
if pid == "" {
|
||||||
pid = "self"
|
pid = "self"
|
||||||
|
|
@ -428,3 +430,37 @@ func ParseIDMappings(uidmap, gidmap []string) ([]idtools.IDMap, []idtools.IDMap,
|
||||||
}
|
}
|
||||||
return uid, gid, nil
|
return uid, gid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnsharedRootPath returns a location under ($XDG_DATA_HOME/containers/storage,
|
||||||
|
// or $HOME/.local/share/containers/storage, or
|
||||||
|
// (the user's home directory)/.local/share/containers/storage, or an error.
|
||||||
|
func UnsharedRootPath(homedir string) (string, error) {
|
||||||
|
// If $XDG_DATA_HOME is defined...
|
||||||
|
if envDataHome, haveDataHome := os.LookupEnv("XDG_DATA_HOME"); haveDataHome {
|
||||||
|
return filepath.Join(envDataHome, "containers", "storage"), nil
|
||||||
|
}
|
||||||
|
// If $XDG_DATA_HOME is not defined, but $HOME is defined...
|
||||||
|
if envHomedir, haveHomedir := os.LookupEnv("HOME"); haveHomedir {
|
||||||
|
// Default to the user's $HOME/.local/share/containers/storage subdirectory.
|
||||||
|
return filepath.Join(envHomedir, ".local", "share", "containers", "storage"), nil
|
||||||
|
}
|
||||||
|
// If we know where our home directory is...
|
||||||
|
if homedir != "" {
|
||||||
|
// Default to the user's homedir/.local/share/containers/storage subdirectory.
|
||||||
|
return filepath.Join(homedir, ".local", "share", "containers", "storage"), nil
|
||||||
|
}
|
||||||
|
return "", errors.New("unable to determine a --root location: neither $XDG_DATA_HOME nor $HOME is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsharedRunrootPath returns $XDG_RUNTIME_DIR/run, /var/run/user/(the user's UID)/run, or an error.
|
||||||
|
func UnsharedRunrootPath(uid string) (string, error) {
|
||||||
|
// If $XDG_RUNTIME_DIR is defined...
|
||||||
|
if envRuntimeDir, haveRuntimeDir := os.LookupEnv("XDG_RUNTIME_DIR"); haveRuntimeDir {
|
||||||
|
return filepath.Join(envRuntimeDir, "run"), nil
|
||||||
|
}
|
||||||
|
// If $XDG_RUNTIME_DIR is not defined, but we know our UID...
|
||||||
|
if uid != "" {
|
||||||
|
return filepath.Join("/var/run/user", uid, "run"), nil
|
||||||
|
}
|
||||||
|
return "", errors.New("unable to determine a --runroot location: $XDG_RUNTIME_DIR is not set, and we don't know our UID")
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue