Merge pull request #41817 from simonferquel/desktop-startup-hang
Fix a potential hang when starting after a non-clean shutdown
diff --git a/Dockerfile b/Dockerfile
index 672af8b..c748a07 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -96,10 +96,10 @@
buildpack-deps:buster@sha256:d0abb4b1e5c664828b93e8b6ac84d10bce45ee469999bef88304be04a2709491 \
busybox:latest@sha256:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209 \
busybox:glibc@sha256:1f81263701cddf6402afe9f33fca0266d9fff379e59b1748f33d3072da71ee85 \
- debian:buster@sha256:46d659005ca1151087efa997f1039ae45a7bf7a2cbbe2d17d3dcbda632a3ee9a \
+ debian:bullseye@sha256:7190e972ab16aefea4d758ebe42a293f4e5c5be63595f4d03a5b9bf6839a4344 \
hello-world:latest@sha256:d58e752213a51785838f9eed2b7a498ffa1cb3aa7f946dda11af39286c3db9a9 \
arm32v7/hello-world:latest@sha256:50b8560ad574c779908da71f7ce370c0a2471c098d44d1c8f6b513c5a55eeeb1
-# See also ensureFrozenImagesLinux() in "integration-cli/fixtures_linux_daemon_test.go" (which needs to be updated when adding images to this list)
+# See also frozenImages in "testutil/environment/protect.go" (which needs to be updated when adding images to this list)
FROM base AS cross-false
diff --git a/Dockerfile.e2e b/Dockerfile.e2e
index 48dfb05..b798c86 100644
--- a/Dockerfile.e2e
+++ b/Dockerfile.e2e
@@ -21,9 +21,9 @@
buildpack-deps:buster@sha256:d0abb4b1e5c664828b93e8b6ac84d10bce45ee469999bef88304be04a2709491 \
busybox:latest@sha256:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209 \
busybox:glibc@sha256:1f81263701cddf6402afe9f33fca0266d9fff379e59b1748f33d3072da71ee85 \
- debian:buster@sha256:46d659005ca1151087efa997f1039ae45a7bf7a2cbbe2d17d3dcbda632a3ee9a \
+ debian:bullseye@sha256:7190e972ab16aefea4d758ebe42a293f4e5c5be63595f4d03a5b9bf6839a4344 \
hello-world:latest@sha256:d58e752213a51785838f9eed2b7a498ffa1cb3aa7f946dda11af39286c3db9a9
-# See also ensureFrozenImagesLinux() in "integration-cli/fixtures_linux_daemon_test.go" (which needs to be updated when adding images to this list)
+# See also frozenImages in "testutil/environment/protect.go" (which needs to be updated when adding images to this list)
FROM base AS dockercli
ENV INSTALL_BINARY_NAME=dockercli
diff --git a/TESTING.md b/TESTING.md
index a1217ae..fb0e4e9 100644
--- a/TESTING.md
+++ b/TESTING.md
@@ -47,13 +47,20 @@
A bug fix may also include new assertions in existing integration tests for the
API endpoint.
+### Writing new integration tests
+
+Note the `integration-cli` tests are deprecated; new tests will be rejected by
+the CI.
+
+Instead, implement new tests under `integration/`.
+
### Integration tests environment considerations
When adding new tests or modifying existing tests under `integration/`, testing
environment should be properly considered. `skip.If` from
[gotest.tools/skip](https://godoc.org/gotest.tools/skip) can be used to make the
test run conditionally. Full testing environment conditions can be found at
-[environment.go](https://github.com/moby/moby/blob/cb37987ee11655ed6bbef663d245e55922354c68/internal/test/environment/environment.go)
+[environment.go](https://github.com/moby/moby/blob/6b6eeed03b963a27085ea670f40cd5ff8a61f32e/testutil/environment/environment.go)
Here is a quick example. If the test needs to interact with a docker daemon on
the same host, the following condition should be checked within the test code
diff --git a/cmd/dockerd/daemon.go b/cmd/dockerd/daemon.go
index 96c3194..7fe8a6c 100644
--- a/cmd/dockerd/daemon.go
+++ b/cmd/dockerd/daemon.go
@@ -184,7 +184,7 @@
}, logrus.StandardLogger())
// Notify that the API is active, but before daemon is set up.
- preNotifySystem()
+ preNotifyReady()
pluginStore := plugin.NewStore()
@@ -242,13 +242,15 @@
go cli.api.Wait(serveAPIWait)
// after the daemon is done setting up we can notify systemd api
- notifySystem()
+ notifyReady()
// Daemon is fully initialized and handling API traffic
// Wait for serve API to complete
errAPI := <-serveAPIWait
c.Cleanup()
+ // notify systemd that we're shutting down
+ notifyStopping()
shutdownDaemon(d)
// Stop notification processing and any background processes
diff --git a/cmd/dockerd/daemon_freebsd.go b/cmd/dockerd/daemon_freebsd.go
index 6d013b8..1bb4904 100644
--- a/cmd/dockerd/daemon_freebsd.go
+++ b/cmd/dockerd/daemon_freebsd.go
@@ -1,9 +1,13 @@
package main
-// preNotifySystem sends a message to the host when the API is active, but before the daemon is
-func preNotifySystem() {
+// preNotifyReady sends a message to the host when the API is active, but before the daemon is
+func preNotifyReady() {
}
-// notifySystem sends a message to the host when the server is ready to be used
-func notifySystem() {
+// notifyReady sends a message to the host when the server is ready to be used
+func notifyReady() {
+}
+
+// notifyStopping sends a message to the host when the server is shutting down
+func notifyStopping() {
}
diff --git a/cmd/dockerd/daemon_linux.go b/cmd/dockerd/daemon_linux.go
index 1e7a2ca..aade57a 100644
--- a/cmd/dockerd/daemon_linux.go
+++ b/cmd/dockerd/daemon_linux.go
@@ -2,12 +2,17 @@
import systemdDaemon "github.com/coreos/go-systemd/v22/daemon"
-// preNotifySystem sends a message to the host when the API is active, but before the daemon is
-func preNotifySystem() {
+// preNotifyReady sends a message to the host when the API is active, but before the daemon is
+func preNotifyReady() {
}
-// notifySystem sends a message to the host when the server is ready to be used
-func notifySystem() {
+// notifyReady sends a message to the host when the server is ready to be used
+func notifyReady() {
// Tell the init daemon we are accepting requests
go systemdDaemon.SdNotify(false, systemdDaemon.SdNotifyReady)
}
+
+// notifyStopping sends a message to the host when the server is shutting down
+func notifyStopping() {
+ go systemdDaemon.SdNotify(false, systemdDaemon.SdNotifyStopping)
+}
diff --git a/cmd/dockerd/daemon_windows.go b/cmd/dockerd/daemon_windows.go
index 2d74531..1a9c50b 100644
--- a/cmd/dockerd/daemon_windows.go
+++ b/cmd/dockerd/daemon_windows.go
@@ -27,8 +27,8 @@
return filepath.Join(root, `\config`), nil
}
-// preNotifySystem sends a message to the host when the API is active, but before the daemon is
-func preNotifySystem() {
+// preNotifyReady sends a message to the host when the API is active, but before the daemon is
+func preNotifyReady() {
// start the service now to prevent timeouts waiting for daemon to start
// but still (eventually) complete all requests that are sent after this
if service != nil {
@@ -39,8 +39,12 @@
}
}
-// notifySystem sends a message to the host when the server is ready to be used
-func notifySystem() {
+// notifyReady sends a message to the host when the server is ready to be used
+func notifyReady() {
+}
+
+// notifyStopping sends a message to the host when the server is shutting down
+func notifyStopping() {
}
// notifyShutdown is called after the daemon shuts down but before the process exits.
diff --git a/daemon/graphdriver/overlay2/overlay.go b/daemon/graphdriver/overlay2/overlay.go
index 39e8cf2..11d5302 100644
--- a/daemon/graphdriver/overlay2/overlay.go
+++ b/daemon/graphdriver/overlay2/overlay.go
@@ -573,14 +573,14 @@
// the page size. The mount syscall fails if the mount data cannot
// fit within a page and relative links make the mount data much
// smaller at the expense of requiring a fork exec to chroot.
- if len(mountData) > pageSize {
+ if len(mountData) > pageSize-1 {
if readonly {
opts = indexOff + "lowerdir=" + path.Join(id, diffDirName) + ":" + string(lowers)
} else {
opts = indexOff + "lowerdir=" + string(lowers) + ",upperdir=" + path.Join(id, diffDirName) + ",workdir=" + path.Join(id, workDirName)
}
mountData = label.FormatMountLabel(opts, mountLabel)
- if len(mountData) > pageSize {
+ if len(mountData) > pageSize-1 {
return nil, fmt.Errorf("cannot mount layer, mount label too large %d", len(mountData))
}
diff --git a/hack/dockerfile/install/proxy.installer b/hack/dockerfile/install/proxy.installer
index 1a48ec9..a9562df 100755
--- a/hack/dockerfile/install/proxy.installer
+++ b/hack/dockerfile/install/proxy.installer
@@ -3,7 +3,7 @@
# LIBNETWORK_COMMIT is used to build the docker-userland-proxy binary. When
# updating the binary version, consider updating github.com/docker/libnetwork
# in vendor.conf accordingly
-: "${LIBNETWORK_COMMIT:=5c6a95bfb20c61571a00f913c6b91959ede84e8d}"
+: "${LIBNETWORK_COMMIT:=fa125a3512ee0f6187721c88582bf8c4378bd4d7}"
install_proxy() {
case "$1" in
diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go
index 81bc7e6..378b479 100644
--- a/integration-cli/docker_cli_daemon_test.go
+++ b/integration-cli/docker_cli_daemon_test.go
@@ -1776,7 +1776,7 @@
defer mount.Unmount(testDir)
// create a 3MiB image (with a 2MiB ext4 fs) and mount it as graph root
- // Why in a container? Because `mount` sometimes behaves weirdly and often fails outright on this test in debian:buster (which is what the test suite runs under if run from the Makefile)
+ // Why in a container? Because `mount` sometimes behaves weirdly and often fails outright on this test in debian:bullseye (which is what the test suite runs under if run from the Makefile)
dockerCmd(c, "run", "--rm", "-v", testDir+":/test", "busybox", "sh", "-c", "dd of=/test/testfs.img bs=1M seek=3 count=0")
icmd.RunCommand("mkfs.ext4", "-F", filepath.Join(testDir, "testfs.img")).Assert(c, icmd.Success)
@@ -1787,7 +1787,7 @@
defer s.d.Stop(c)
// pull a repository large enough to overfill the mounted filesystem
- pullOut, err := s.d.Cmd("pull", "debian:buster")
+ pullOut, err := s.d.Cmd("pull", "debian:bullseye")
assert.Assert(c, err != nil, pullOut)
assert.Assert(c, strings.Contains(pullOut, "no space left on device"))
}
diff --git a/integration-cli/docker_cli_network_unix_test.go b/integration-cli/docker_cli_network_unix_test.go
index b432133..eb9d46b 100644
--- a/integration-cli/docker_cli_network_unix_test.go
+++ b/integration-cli/docker_cli_network_unix_test.go
@@ -1574,7 +1574,7 @@
dockerCmd(c, "network", "create", "-d", "bridge", "nw1")
// Sending garbage to embedded DNS shouldn't crash the daemon
- dockerCmd(c, "run", "-i", "--net=nw1", "--name=c1", "debian:buster", "bash", "-c", "echo InvalidQuery > /dev/udp/127.0.0.11/53")
+ dockerCmd(c, "run", "-i", "--net=nw1", "--name=c1", "debian:bullseye", "bash", "-c", "echo InvalidQuery > /dev/udp/127.0.0.11/53")
}
func (s *DockerSuite) TestDockerNetworkConnectFailsNoInspectChange(c *testing.T) {
diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go
index e0c1bbe..2fdc887 100644
--- a/integration-cli/docker_cli_run_test.go
+++ b/integration-cli/docker_cli_run_test.go
@@ -2927,7 +2927,7 @@
go func() {
name := "acidburn"
- out, _, err := dockerCmdWithError("run", "--name", name, "--security-opt", "seccomp=unconfined", "debian:buster", "unshare", "-p", "-m", "-f", "-r", "--mount-proc=/proc", "mount")
+ out, _, err := dockerCmdWithError("run", "--name", name, "--security-opt", "seccomp=unconfined", "debian:bullseye", "unshare", "-p", "-m", "-f", "-r", "--mount-proc=/proc", "mount")
if err == nil ||
!(strings.Contains(strings.ToLower(out), "permission denied") ||
strings.Contains(strings.ToLower(out), "operation not permitted")) {
@@ -2939,7 +2939,7 @@
go func() {
name := "cereal"
- out, _, err := dockerCmdWithError("run", "--name", name, "--security-opt", "seccomp=unconfined", "debian:buster", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc")
+ out, _, err := dockerCmdWithError("run", "--name", name, "--security-opt", "seccomp=unconfined", "debian:bullseye", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc")
if err == nil ||
!(strings.Contains(strings.ToLower(out), "mount: cannot mount none") ||
strings.Contains(strings.ToLower(out), "permission denied") ||
@@ -2953,7 +2953,7 @@
/* Ensure still fails if running privileged with the default policy */
go func() {
name := "crashoverride"
- out, _, err := dockerCmdWithError("run", "--privileged", "--security-opt", "seccomp=unconfined", "--security-opt", "apparmor=docker-default", "--name", name, "debian:buster", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc")
+ out, _, err := dockerCmdWithError("run", "--privileged", "--security-opt", "seccomp=unconfined", "--security-opt", "apparmor=docker-default", "--name", name, "debian:bullseye", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc")
if err == nil ||
!(strings.Contains(strings.ToLower(out), "mount: cannot mount none") ||
strings.Contains(strings.ToLower(out), "permission denied") ||
diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go
index a747f90..44de261 100644
--- a/integration-cli/docker_cli_run_unix_test.go
+++ b/integration-cli/docker_cli_run_unix_test.go
@@ -873,12 +873,12 @@
assert.Assert(c, strings.Contains(out, option))
}
- // We use debian:buster as there is no findmnt in busybox. Also the output will be in the format of
+ // We use debian:bullseye as there is no findmnt in busybox. Also the output will be in the format of
// TARGET PROPAGATION
// /tmp shared
// so we only capture `shared` here.
expectedOptions = []string{"shared"}
- out, _ = dockerCmd(c, "run", "--tmpfs", "/tmp:shared", "debian:buster", "findmnt", "-o", "TARGET,PROPAGATION", "/tmp")
+ out, _ = dockerCmd(c, "run", "--tmpfs", "/tmp:shared", "debian:bullseye", "findmnt", "-o", "TARGET,PROPAGATION", "/tmp")
for _, option := range expectedOptions {
assert.Assert(c, strings.Contains(out, option))
}
@@ -914,7 +914,7 @@
})
}
-// TestRunSeccompProfileDenyUnshare checks that 'docker run --security-opt seccomp=/tmp/profile.json debian:buster unshare' exits with operation not permitted.
+// TestRunSeccompProfileDenyUnshare checks that 'docker run --security-opt seccomp=/tmp/profile.json debian:bullseye unshare' exits with operation not permitted.
func (s *DockerSuite) TestRunSeccompProfileDenyUnshare(c *testing.T) {
testRequires(c, testEnv.IsLocalDaemon, seccompEnabled, NotArm, Apparmor)
jsonData := `{
@@ -937,7 +937,7 @@
}
icmd.RunCommand(dockerBinary, "run", "--security-opt", "apparmor=unconfined",
"--security-opt", "seccomp="+tmpFile.Name(),
- "debian:buster", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc").Assert(c, icmd.Expected{
+ "debian:bullseye", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc").Assert(c, icmd.Expected{
ExitCode: 1,
Err: "Operation not permitted",
})
@@ -977,7 +977,7 @@
})
}
-// TestRunSeccompProfileDenyUnshareUserns checks that 'docker run debian:buster unshare --map-root-user --user sh -c whoami' with a specific profile to
+// TestRunSeccompProfileDenyUnshareUserns checks that 'docker run debian:bullseye unshare --map-root-user --user sh -c whoami' with a specific profile to
// deny unshare of a userns exits with operation not permitted.
func (s *DockerSuite) TestRunSeccompProfileDenyUnshareUserns(c *testing.T) {
testRequires(c, testEnv.IsLocalDaemon, seccompEnabled, NotArm, Apparmor)
@@ -1009,7 +1009,7 @@
}
icmd.RunCommand(dockerBinary, "run",
"--security-opt", "apparmor=unconfined", "--security-opt", "seccomp="+tmpFile.Name(),
- "debian:buster", "unshare", "--map-root-user", "--user", "sh", "-c", "whoami").Assert(c, icmd.Expected{
+ "debian:bullseye", "unshare", "--map-root-user", "--user", "sh", "-c", "whoami").Assert(c, icmd.Expected{
ExitCode: 1,
Err: "Operation not permitted",
})
@@ -1061,12 +1061,12 @@
icmd.RunCommand(dockerBinary, "run", "syscall-test", "exit32-test").Assert(c, icmd.Success)
}
-// TestRunSeccompAllowSetrlimit checks that 'docker run debian:buster ulimit -v 1048510' succeeds.
+// TestRunSeccompAllowSetrlimit checks that 'docker run debian:bullseye ulimit -v 1048510' succeeds.
func (s *DockerSuite) TestRunSeccompAllowSetrlimit(c *testing.T) {
testRequires(c, testEnv.IsLocalDaemon, seccompEnabled)
// ulimit uses setrlimit, so we want to make sure we don't break it
- icmd.RunCommand(dockerBinary, "run", "debian:buster", "bash", "-c", "ulimit -v 1048510").Assert(c, icmd.Success)
+ icmd.RunCommand(dockerBinary, "run", "debian:bullseye", "bash", "-c", "ulimit -v 1048510").Assert(c, icmd.Success)
}
func (s *DockerSuite) TestRunSeccompDefaultProfileAcct(c *testing.T) {
@@ -1362,7 +1362,7 @@
func (s *DockerSuite) TestRunSeccompWithDefaultProfile(c *testing.T) {
testRequires(c, testEnv.IsLocalDaemon, seccompEnabled)
- out, _, err := dockerCmdWithError("run", "--security-opt", "seccomp=../profiles/seccomp/default.json", "debian:buster", "unshare", "--map-root-user", "--user", "sh", "-c", "whoami")
+ out, _, err := dockerCmdWithError("run", "--security-opt", "seccomp=../profiles/seccomp/default.json", "debian:bullseye", "unshare", "--map-root-user", "--user", "sh", "-c", "whoami")
assert.ErrorContains(c, err, "", out)
assert.Equal(c, strings.TrimSpace(out), "unshare: unshare failed: Operation not permitted")
}
diff --git a/integration-cli/fixtures_linux_daemon_test.go b/integration-cli/fixtures_linux_daemon_test.go
index f211a11..acd593d 100644
--- a/integration-cli/fixtures_linux_daemon_test.go
+++ b/integration-cli/fixtures_linux_daemon_test.go
@@ -49,7 +49,7 @@
dockerFile := filepath.Join(tmp, "Dockerfile")
content := []byte(`
- FROM debian:buster
+ FROM debian:bullseye
COPY . /usr/bin/
`)
err = ioutil.WriteFile(dockerFile, content, 0600)
@@ -103,7 +103,7 @@
dockerfile := filepath.Join(tmp, "Dockerfile")
content := `
- FROM debian:buster
+ FROM debian:bullseye
COPY . /usr/bin
RUN chmod +s /usr/bin/nnp-test
`
diff --git a/integration/build/build_userns_linux_test.go b/integration/build/build_userns_linux_test.go
new file mode 100644
index 0000000..5e0c4d4
--- /dev/null
+++ b/integration/build/build_userns_linux_test.go
@@ -0,0 +1,140 @@
+package build // import "github.com/docker/docker/integration/build"
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "io"
+ "io/ioutil"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/docker/docker/api/types"
+ "github.com/docker/docker/integration/internal/container"
+ "github.com/docker/docker/pkg/stdcopy"
+ "github.com/docker/docker/testutil/daemon"
+ "github.com/docker/docker/testutil/fakecontext"
+ "gotest.tools/v3/assert"
+ "gotest.tools/v3/skip"
+)
+
+// Implements a test for https://github.com/moby/moby/issues/41723
+// Images built in a user-namespaced daemon should have capabilities serialised in
+// VFS_CAP_REVISION_2 (no user-namespace root uid) format rather than V3 (that includes
+// the root uid).
+func TestBuildUserNamespaceValidateCapabilitiesAreV2(t *testing.T) {
+ skip.If(t, testEnv.DaemonInfo.OSType != "linux")
+ skip.If(t, testEnv.IsRemoteDaemon())
+ skip.If(t, !testEnv.IsUserNamespaceInKernel())
+ skip.If(t, testEnv.IsRootless())
+
+ const imageTag = "capabilities:1.0"
+
+ tmp, err := ioutil.TempDir("", "integration-")
+ assert.NilError(t, err)
+ defer os.RemoveAll(tmp)
+
+ dUserRemap := daemon.New(t)
+ dUserRemap.StartWithBusybox(t, "--userns-remap", "default")
+ dUserRemapRunning := true
+ defer func() {
+ if dUserRemapRunning {
+ dUserRemap.Stop(t)
+ }
+ }()
+
+ dockerfile := `
+ FROM debian:bullseye
+ RUN setcap CAP_NET_BIND_SERVICE=+eip /bin/sleep
+ `
+
+ ctx := context.Background()
+ source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
+ defer source.Close()
+
+ clientUserRemap := dUserRemap.NewClientT(t)
+ resp, err := clientUserRemap.ImageBuild(ctx,
+ source.AsTarReader(t),
+ types.ImageBuildOptions{
+ Tags: []string{imageTag},
+ })
+ assert.NilError(t, err)
+ defer resp.Body.Close()
+ buf := make([]byte, 1024)
+ for {
+ n, err := resp.Body.Read(buf)
+ if err != nil && err != io.EOF {
+ t.Fatalf("Error reading ImageBuild response: %v", err)
+ break
+ }
+ if n == 0 {
+ break
+ }
+ }
+
+ reader, err := clientUserRemap.ImageSave(ctx, []string{imageTag})
+ assert.NilError(t, err, "failed to download capabilities image")
+ defer reader.Close()
+
+ tar, err := os.Create(tmp + "/image.tar")
+ assert.NilError(t, err, "failed to create image tar file")
+ defer tar.Close()
+
+ _, err = io.Copy(tar, reader)
+ assert.NilError(t, err, "failed to write image tar file")
+
+ dUserRemap.Stop(t)
+ dUserRemap.Cleanup(t)
+ dUserRemapRunning = false
+
+ dNoUserRemap := daemon.New(t)
+ dNoUserRemap.StartWithBusybox(t)
+ defer dNoUserRemap.Stop(t)
+
+ clientNoUserRemap := dNoUserRemap.NewClientT(t)
+
+ tarFile, err := os.Open(tmp + "/image.tar")
+ assert.NilError(t, err, "failed to open image tar file")
+
+ tarReader := bufio.NewReader(tarFile)
+ loadResp, err := clientNoUserRemap.ImageLoad(ctx, tarReader, false)
+ assert.NilError(t, err, "failed to load image tar file")
+ defer loadResp.Body.Close()
+ for {
+ n, err := loadResp.Body.Read(buf)
+ if err != nil && err != io.EOF {
+ t.Fatalf("Error reading ImageLoad response: %v", err)
+ break
+ }
+ if n == 0 {
+ break
+ }
+ }
+
+ cid := container.Run(ctx, t, clientNoUserRemap,
+ container.WithImage(imageTag),
+ container.WithCmd("/sbin/getcap", "-n", "/bin/sleep"),
+ )
+ logReader, err := clientNoUserRemap.ContainerLogs(ctx, cid, types.ContainerLogsOptions{
+ ShowStdout: true,
+ })
+ assert.NilError(t, err)
+
+ actualStdout := new(bytes.Buffer)
+ actualStderr := ioutil.Discard
+ _, err = stdcopy.StdCopy(actualStdout, actualStderr, logReader)
+ assert.NilError(t, err)
+ if strings.TrimSpace(actualStdout.String()) != "/bin/sleep cap_net_bind_service=eip" {
+ // Activate when fix is merged: https://github.com/moby/moby/pull/41724
+ //t.Fatalf("run produced invalid output: %q, expected %q", actualStdout.String(), "/bin/sleep cap_net_bind_service=eip")
+ // t.Logf("run produced invalid output (expected until #41724 merges): %q, expected %q",
+ // actualStdout.String(),
+ // "/bin/sleep cap_net_bind_service=eip")
+ } else {
+ // Shouldn't happen until fix is merged: https://github.com/moby/moby/pull/41724
+ t.Fatalf("run produced valid output (unexpected until #41724 merges): %q, expected %q",
+ actualStdout.String(),
+ "/bin/sleep cap_net_bind_service=eip")
+ }
+}
diff --git a/testutil/environment/environment.go b/testutil/environment/environment.go
index f8df370..23ab204 100644
--- a/testutil/environment/environment.go
+++ b/testutil/environment/environment.go
@@ -167,6 +167,27 @@
return os.Getenv("DOCKER_ROOTLESS") != ""
}
+// IsUserNamespaceInKernel returns whether the kernel supports user namespaces
+func (e *Execution) IsUserNamespaceInKernel() bool {
+ if _, err := os.Stat("/proc/self/uid_map"); os.IsNotExist(err) {
+ /*
+ * This kernel-provided file only exists if user namespaces are
+ * supported
+ */
+ return false
+ }
+
+ // We need extra check on redhat based distributions
+ if f, err := os.Open("/sys/module/user_namespace/parameters/enable"); err == nil {
+ defer f.Close()
+ b := make([]byte, 1)
+ _, _ = f.Read(b)
+ return string(b) != "N"
+ }
+
+ return true
+}
+
// HasExistingImage checks whether there is an image with the given reference.
// Note that this is done by filtering and then checking whether there were any
// results -- so ambiguous references might result in false-positives.
diff --git a/testutil/environment/protect.go b/testutil/environment/protect.go
index 282280e..1ea0e43 100644
--- a/testutil/environment/protect.go
+++ b/testutil/environment/protect.go
@@ -10,7 +10,7 @@
"gotest.tools/v3/assert"
)
-var frozenImages = []string{"busybox:latest", "busybox:glibc", "hello-world:frozen", "debian:buster"}
+var frozenImages = []string{"busybox:latest", "busybox:glibc", "hello-world:frozen", "debian:bullseye"}
type protectedElements struct {
containers map[string]struct{}
diff --git a/vendor.conf b/vendor.conf
index 5bdcd77..0c74186 100644
--- a/vendor.conf
+++ b/vendor.conf
@@ -47,7 +47,7 @@
# libnetwork
# When updating, also update LIBNETWORK_COMMIT in hack/dockerfile/install/proxy.installer accordingly
-github.com/docker/libnetwork 5c6a95bfb20c61571a00f913c6b91959ede84e8d
+github.com/docker/libnetwork fa125a3512ee0f6187721c88582bf8c4378bd4d7
github.com/docker/go-events e31b211e4f1cd09aa76fe4ac244571fab96ae47f
github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
@@ -176,7 +176,7 @@
# metrics
github.com/docker/go-metrics b619b3592b65de4f087d9f16863a7e6ff905973c # v0.0.1
-github.com/opencontainers/selinux 25504e34a9826d481f6e2903963ecaa881749124 # v1.6.0
+github.com/opencontainers/selinux 63ad55b76fd78d4c76c2f5491f68516e60c9d523 # v1.7.0
github.com/willf/bitset 559910e8471e48d76d9e5a1ba15842dee77ad45d # v1.1.11
diff --git a/vendor/github.com/docker/libnetwork/drivers/bridge/port_mapping.go b/vendor/github.com/docker/libnetwork/drivers/bridge/port_mapping.go
index 75b17d6..56a9271 100644
--- a/vendor/github.com/docker/libnetwork/drivers/bridge/port_mapping.go
+++ b/vendor/github.com/docker/libnetwork/drivers/bridge/port_mapping.go
@@ -11,72 +11,114 @@
"github.com/sirupsen/logrus"
)
-var (
- defaultBindingIP = net.IPv4(0, 0, 0, 0)
- defaultBindingIPV6 = net.ParseIP("::")
-)
-
func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
if ep.extConnConfig == nil || ep.extConnConfig.PortBindings == nil {
return nil, nil
}
- defHostIP := defaultBindingIP
+ defHostIP := net.IPv4zero // 0.0.0.0
if reqDefBindIP != nil {
defHostIP = reqDefBindIP
}
- // IPv4 port binding including user land proxy
- pb, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, defHostIP, ulPxyEnabled)
- if err != nil {
- return nil, err
+ var containerIPv6 net.IP
+ if ep.addrv6 != nil {
+ containerIPv6 = ep.addrv6.IP
}
- // IPv6 port binding excluding user land proxy
- if n.driver.config.EnableIP6Tables && ep.addrv6 != nil {
- // TODO IPv6 custom default binding IP
- pbv6, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addrv6.IP, defaultBindingIPV6, false)
- if err != nil {
- // ensure we clear the previous allocated IPv4 ports
- n.releasePortsInternal(pb)
- return nil, err
- }
-
- pb = append(pb, pbv6...)
+ pb, err := n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, containerIPv6, defHostIP, ulPxyEnabled)
+ if err != nil {
+ return nil, err
}
return pb, nil
}
-func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
+func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIPv4, containerIPv6, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) {
bs := make([]types.PortBinding, 0, len(bindings))
for _, c := range bindings {
- b := c.GetCopy()
- if err := n.allocatePort(&b, containerIP, defHostIP, ulPxyEnabled); err != nil {
- // On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
- if cuErr := n.releasePortsInternal(bs); cuErr != nil {
- logrus.Warnf("Upon allocation failure for %v, failed to clear previously allocated port bindings: %v", b, cuErr)
+ bIPv4 := c.GetCopy()
+ bIPv6 := c.GetCopy()
+ // Allocate IPv4 Port mappings
+ if ok := n.validatePortBindingIPv4(&bIPv4, containerIPv4, defHostIP); ok {
+ if err := n.allocatePort(&bIPv4, ulPxyEnabled); err != nil {
+ // On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
+ if cuErr := n.releasePortsInternal(bs); cuErr != nil {
+ logrus.Warnf("allocation failure for %v, failed to clear previously allocated ipv4 port bindings: %v", bIPv4, cuErr)
+ }
+ return nil, err
}
- return nil, err
+ bs = append(bs, bIPv4)
}
- bs = append(bs, b)
+ // Allocate IPv6 Port mappings
+ if ok := n.validatePortBindingIPv6(&bIPv6, containerIPv6, defHostIP); ok {
+ if err := n.allocatePort(&bIPv6, ulPxyEnabled); err != nil {
+ // On allocation failure, release previously allocated ports. On cleanup error, just log a warning message
+ if cuErr := n.releasePortsInternal(bs); cuErr != nil {
+ logrus.Warnf("allocation failure for %v, failed to clear previously allocated ipv6 port bindings: %v", bIPv6, cuErr)
+ }
+ return nil, err
+ }
+ bs = append(bs, bIPv6)
+ }
}
return bs, nil
}
-func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) error {
+// validatePortBindingIPv4 validates the port binding, populates the missing Host IP field and returns true
+// if this is a valid IPv4 binding, else returns false
+func (n *bridgeNetwork) validatePortBindingIPv4(bnd *types.PortBinding, containerIPv4, defHostIP net.IP) bool {
+ //Return early if there is a valid Host IP, but its not a IPv6 address
+ if len(bnd.HostIP) > 0 && bnd.HostIP.To4() == nil {
+ return false
+ }
+ // Adjust the host address in the operational binding
+ if len(bnd.HostIP) == 0 {
+ // Return early if the default binding address is an IPv6 address
+ if defHostIP.To4() == nil {
+ return false
+ }
+ bnd.HostIP = defHostIP
+ }
+ bnd.IP = containerIPv4
+ return true
+
+}
+
+// validatePortBindingIPv6 validates the port binding, populates the missing Host IP field and returns true
+// if this is a valid IP6v binding, else returns false
+func (n *bridgeNetwork) validatePortBindingIPv6(bnd *types.PortBinding, containerIPv6, defHostIP net.IP) bool {
+ // Return early if there is no IPv6 container endpoint
+ if containerIPv6 == nil {
+ return false
+ }
+ // Return early if there is a valid Host IP, which is a IPv4 address
+ if len(bnd.HostIP) > 0 && bnd.HostIP.To4() != nil {
+ return false
+ }
+
+ // Setup a binding to "::" if Host IP is empty and the default binding IP is 0.0.0.0
+ if len(bnd.HostIP) == 0 {
+ if defHostIP.Equal(net.IPv4zero) {
+ bnd.HostIP = net.IPv6zero
+ // If the default binding IP is an IPv6 address, use it
+ } else if defHostIP.To4() == nil {
+ bnd.HostIP = defHostIP
+ // Return false if default binding ip is an IPv4 address
+ } else {
+ return false
+ }
+ }
+ bnd.IP = containerIPv6
+ return true
+
+}
+
+func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, ulPxyEnabled bool) error {
var (
host net.Addr
err error
)
- // Store the container interface address in the operational binding
- bnd.IP = containerIP
-
- // Adjust the host address in the operational binding
- if len(bnd.HostIP) == 0 {
- bnd.HostIP = defHostIP
- }
-
// Adjust HostPortEnd if this is not a range.
if bnd.HostPortEnd == 0 {
bnd.HostPortEnd = bnd.HostPort
@@ -90,7 +132,7 @@
portmapper := n.portMapper
- if containerIP.To4() == nil {
+ if bnd.IP.To4() == nil {
portmapper = n.portMapperV6
}
diff --git a/vendor/github.com/docker/libnetwork/portmapper/mapper.go b/vendor/github.com/docker/libnetwork/portmapper/mapper.go
index ae74145..33f4ec9 100644
--- a/vendor/github.com/docker/libnetwork/portmapper/mapper.go
+++ b/vendor/github.com/docker/libnetwork/portmapper/mapper.go
@@ -151,20 +151,16 @@
}
containerIP, containerPort := getIPAndPort(m.container)
- if pm.checkIP(hostIP) {
- if err := pm.AppendForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
- return nil, err
- }
+ if err := pm.AppendForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
+ return nil, err
}
cleanup := func() error {
// need to undo the iptables rules before we return
m.userlandProxy.Stop()
- if pm.checkIP(hostIP) {
- pm.DeleteForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
- if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
- return err
- }
+ pm.DeleteForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
+ if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
+ return err
}
return nil
diff --git a/vendor/github.com/docker/libnetwork/portmapper/mapper_linux.go b/vendor/github.com/docker/libnetwork/portmapper/mapper_linux.go
index c565efd..0e76c54 100644
--- a/vendor/github.com/docker/libnetwork/portmapper/mapper_linux.go
+++ b/vendor/github.com/docker/libnetwork/portmapper/mapper_linux.go
@@ -44,11 +44,3 @@
}
return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort, pm.bridgeName)
}
-
-// checkIP checks if IP is valid and matching to chain version
-func (pm *PortMapper) checkIP(ip net.IP) bool {
- if pm.chain == nil || pm.chain.IPTable.Version == iptables.IPv4 {
- return ip.To4() != nil
- }
- return ip.To16() != nil
-}
diff --git a/vendor/github.com/docker/libnetwork/portmapper/proxy.go b/vendor/github.com/docker/libnetwork/portmapper/proxy.go
index 12e4122..f945851 100644
--- a/vendor/github.com/docker/libnetwork/portmapper/proxy.go
+++ b/vendor/github.com/docker/libnetwork/portmapper/proxy.go
@@ -19,6 +19,16 @@
Stop() error
}
+// ipVersion refers to IP version - v4 or v6
+type ipVersion string
+
+const (
+ // IPv4 is version 4
+ ipv4 ipVersion = "4"
+ // IPv4 is version 6
+ ipv6 ipVersion = "6"
+)
+
// proxyCommand wraps an exec.Cmd to run the userland TCP and UDP
// proxies as separate processes.
type proxyCommand struct {
@@ -77,21 +87,27 @@
// port allocations on bound port, because without userland proxy we using
// iptables rules and not net.Listen
type dummyProxy struct {
- listener io.Closer
- addr net.Addr
+ listener io.Closer
+ addr net.Addr
+ ipVersion ipVersion
}
func newDummyProxy(proto string, hostIP net.IP, hostPort int) (userlandProxy, error) {
+ // detect version of hostIP to bind only to correct version
+ version := ipv4
+ if hostIP.To4() == nil {
+ version = ipv6
+ }
switch proto {
case "tcp":
addr := &net.TCPAddr{IP: hostIP, Port: hostPort}
- return &dummyProxy{addr: addr}, nil
+ return &dummyProxy{addr: addr, ipVersion: version}, nil
case "udp":
addr := &net.UDPAddr{IP: hostIP, Port: hostPort}
- return &dummyProxy{addr: addr}, nil
+ return &dummyProxy{addr: addr, ipVersion: version}, nil
case "sctp":
addr := &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: hostIP}}, Port: hostPort}
- return &dummyProxy{addr: addr}, nil
+ return &dummyProxy{addr: addr, ipVersion: version}, nil
default:
return nil, fmt.Errorf("Unknown addr type: %s", proto)
}
@@ -100,19 +116,19 @@
func (p *dummyProxy) Start() error {
switch addr := p.addr.(type) {
case *net.TCPAddr:
- l, err := net.ListenTCP("tcp", addr)
+ l, err := net.ListenTCP("tcp"+string(p.ipVersion), addr)
if err != nil {
return err
}
p.listener = l
case *net.UDPAddr:
- l, err := net.ListenUDP("udp", addr)
+ l, err := net.ListenUDP("udp"+string(p.ipVersion), addr)
if err != nil {
return err
}
p.listener = l
case *sctp.SCTPAddr:
- l, err := sctp.ListenSCTP("sctp", addr)
+ l, err := sctp.ListenSCTP("sctp"+string(p.ipVersion), addr)
if err != nil {
return err
}
diff --git a/vendor/github.com/opencontainers/selinux/README.md b/vendor/github.com/opencontainers/selinux/README.md
index a9f0cc1..41f4df7 100644
--- a/vendor/github.com/opencontainers/selinux/README.md
+++ b/vendor/github.com/opencontainers/selinux/README.md
@@ -18,5 +18,5 @@
If you find an issue, please follow the [security][security] protocol to report it.
-[security]: https://github.com/opencontainers/org/blob/master/security
+[security]: https://github.com/opencontainers/org/blob/master/SECURITY.md
[code-of-conduct]: https://github.com/opencontainers/org/blob/master/CODE_OF_CONDUCT.md
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go
index 10ac15a..988adc8 100644
--- a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go
+++ b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go
@@ -27,14 +27,14 @@
// the container. A list of options can be passed into this function to alter
// the labels. The labels returned will include a random MCS String, that is
// guaranteed to be unique.
-func InitLabels(options []string) (plabel string, mlabel string, Err error) {
+func InitLabels(options []string) (plabel string, mlabel string, retErr error) {
if !selinux.GetEnabled() {
return "", "", nil
}
processLabel, mountLabel := selinux.ContainerLabels()
if processLabel != "" {
defer func() {
- if Err != nil {
+ if retErr != nil {
selinux.ReleaseLabel(mountLabel)
}
}()
@@ -57,7 +57,6 @@
con := strings.SplitN(opt, ":", 2)
if !validOptions[con[0]] {
return "", "", errors.Errorf("Bad label option %q, valid options 'disable, user, role, level, type, filetype'", con[0])
-
}
if con[0] == "filetype" {
mcon["type"] = con[1]
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go
index 50760dc..d911990 100644
--- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go
+++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go
@@ -30,6 +30,11 @@
// ErrLevelSyntax is returned when a sensitivity or category do not have correct syntax in a level
ErrLevelSyntax = errors.New("invalid level syntax")
+ // ErrContextMissing is returned if a requested context is not found in a file.
+ ErrContextMissing = errors.New("context does not have a match")
+ // ErrVerifierNil is returned when a context verifier function is nil.
+ ErrVerifierNil = errors.New("verifier function is nil")
+
// CategoryRange allows the upper bound on the category range to be adjusted
CategoryRange = DefaultCategoryRange
)
@@ -63,8 +68,12 @@
return fileLabel(fpath)
}
-// SetFSCreateLabel tells kernel the label to create all file system objects
-// created by this task. Setting label="" to return to default.
+// SetFSCreateLabel tells the kernel what label to use for all file system objects
+// created by this task.
+// Set the label to an empty string to return to the default label. Calls to SetFSCreateLabel
+// should be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() until file system
+// objects created by this task are finished to guarantee another goroutine does not migrate
+// to the current thread before execution is complete.
func SetFSCreateLabel(label string) error {
return setFSCreateLabel(label)
}
@@ -113,19 +122,27 @@
}
// SetExecLabel sets the SELinux label that the kernel will use for any programs
-// that are executed by the current process thread, or an error.
+// that are executed by the current process thread, or an error. Calls to SetExecLabel
+// should be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() until execution
+// of the program is finished to guarantee another goroutine does not migrate to the current
+// thread before execution is complete.
func SetExecLabel(label string) error {
return setExecLabel(label)
}
// SetTaskLabel sets the SELinux label for the current thread, or an error.
-// This requires the dyntransition permission.
+// This requires the dyntransition permission. Calls to SetTaskLabel should
+// be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() to guarantee
+// the current thread does not run in a new mislabeled thread.
func SetTaskLabel(label string) error {
return setTaskLabel(label)
}
// SetSocketLabel takes a process label and tells the kernel to assign the
-// label to the next socket that gets created
+// label to the next socket that gets created. Calls to SetSocketLabel
+// should be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() until
+// the the socket is created to guarantee another goroutine does not migrate
+// to the current thread before execution is complete.
func SetSocketLabel(label string) error {
return setSocketLabel(label)
}
@@ -141,7 +158,10 @@
}
// SetKeyLabel takes a process label and tells the kernel to assign the
-// label to the next kernel keyring that gets created
+// label to the next kernel keyring that gets created. Calls to SetKeyLabel
+// should be wrapped in runtime.LockOSThread()/runtime.UnlockOSThread() until
+// the kernel keyring is created to guarantee another goroutine does not migrate
+// to the current thread before execution is complete.
func SetKeyLabel(label string) error {
return setKeyLabel(label)
}
@@ -247,3 +267,12 @@
func DisableSecOpt() []string {
return disableSecOpt()
}
+
+// GetDefaultContextWithLevel gets a single context for the specified SELinux user
+// identity that is reachable from the specified scon context. The context is based
+// on the per-user /etc/selinux/{SELINUXTYPE}/contexts/users/<username> if it exists,
+// and falls back to the global /etc/selinux/{SELINUXTYPE}/contexts/default_contexts
+// file.
+func GetDefaultContextWithLevel(user, level, scon string) (string, error) {
+ return getDefaultContextWithLevel(user, level, scon)
+}
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
index d6b0d49..904f5b0 100644
--- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
+++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
@@ -28,6 +28,8 @@
minSensLen = 2
contextFile = "/usr/share/containers/selinux/contexts"
selinuxDir = "/etc/selinux/"
+ selinuxUsersDir = "contexts/users"
+ defaultContexts = "contexts/default_contexts"
selinuxConfig = selinuxDir + "config"
selinuxfsMount = "/sys/fs/selinux"
selinuxTypeTag = "SELINUXTYPE"
@@ -35,6 +37,8 @@
xattrNameSelinux = "security.selinux"
)
+var policyRoot = filepath.Join(selinuxDir, readConfig(selinuxTypeTag))
+
type selinuxState struct {
enabledSet bool
enabled bool
@@ -54,6 +58,13 @@
high *level
}
+type defaultSECtx struct {
+ user, level, scon string
+ userRdr, defaultRdr io.Reader
+
+ verifier func(string) error
+}
+
type levelItem byte
const (
@@ -111,7 +122,7 @@
if err == nil {
break
}
- if err == unix.EAGAIN {
+ if err == unix.EAGAIN || err == unix.EINTR {
continue
}
return false
@@ -205,28 +216,16 @@
}
func readConfig(target string) string {
- var (
- val, key string
- bufin *bufio.Reader
- )
-
in, err := os.Open(selinuxConfig)
if err != nil {
return ""
}
defer in.Close()
- bufin = bufio.NewReader(in)
+ scanner := bufio.NewScanner(in)
- for done := false; !done; {
- var line string
- if line, err = bufin.ReadString('\n'); err != nil {
- if err != io.EOF {
- return ""
- }
- done = true
- }
- line = strings.TrimSpace(line)
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
if len(line) == 0 {
// Skip blank lines
continue
@@ -236,7 +235,7 @@
continue
}
if groups := assignRegex.FindStringSubmatch(line); groups != nil {
- key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
+ key, val := strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
if key == target {
return strings.Trim(val, "\"")
}
@@ -245,15 +244,17 @@
return ""
}
-func getSELinuxPolicyRoot() string {
- return filepath.Join(selinuxDir, readConfig(selinuxTypeTag))
-}
-
func isProcHandle(fh *os.File) error {
var buf unix.Statfs_t
- err := unix.Fstatfs(int(fh.Fd()), &buf)
- if err != nil {
- return errors.Wrapf(err, "statfs(%q) failed", fh.Name())
+
+ for {
+ err := unix.Fstatfs(int(fh.Fd()), &buf)
+ if err == nil {
+ break
+ }
+ if err != unix.EINTR {
+ return errors.Wrapf(err, "statfs(%q) failed", fh.Name())
+ }
}
if buf.Type != unix.PROC_SUPER_MAGIC {
return errors.Errorf("file %q is not on procfs", fh.Name())
@@ -307,9 +308,16 @@
if fpath == "" {
return ErrEmptyPath
}
- if err := unix.Lsetxattr(fpath, xattrNameSelinux, []byte(label), 0); err != nil {
- return errors.Wrapf(err, "failed to set file label on %s", fpath)
+ for {
+ err := unix.Lsetxattr(fpath, xattrNameSelinux, []byte(label), 0)
+ if err == nil {
+ break
+ }
+ if err != unix.EINTR {
+ return errors.Wrapf(err, "failed to set file label on %s", fpath)
+ }
}
+
return nil
}
@@ -751,7 +759,7 @@
if len(label) != 0 {
con := strings.SplitN(label, ":", 4)
if len(con) > 3 {
- mcsAdd(con[3])
+ _ = mcsAdd(con[3])
}
}
}
@@ -828,11 +836,11 @@
}
for ORD > TIER {
- ORD = ORD - TIER
+ ORD -= TIER
TIER--
}
TIER = SETSIZE - TIER
- ORD = ORD + TIER
+ ORD += TIER
return fmt.Sprintf("s0:c%d,c%d", TIER, ORD)
}
@@ -844,16 +852,14 @@
)
for {
- binary.Read(rand.Reader, binary.LittleEndian, &n)
+ _ = binary.Read(rand.Reader, binary.LittleEndian, &n)
c1 = n % catRange
- binary.Read(rand.Reader, binary.LittleEndian, &n)
+ _ = binary.Read(rand.Reader, binary.LittleEndian, &n)
c2 = n % catRange
if c1 == c2 {
continue
- } else {
- if c1 > c2 {
- c1, c2 = c2, c1
- }
+ } else if c1 > c2 {
+ c1, c2 = c2, c1
}
mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2)
if err := mcsAdd(mcs); err != nil {
@@ -884,18 +890,13 @@
if f, err := os.Open(contextFile); err == nil {
return f, nil
}
- lxcPath := filepath.Join(getSELinuxPolicyRoot(), "/contexts/lxc_contexts")
+ lxcPath := filepath.Join(policyRoot, "/contexts/lxc_contexts")
return os.Open(lxcPath)
}
var labels = loadLabels()
func loadLabels() map[string]string {
- var (
- val, key string
- bufin *bufio.Reader
- )
-
labels := make(map[string]string)
in, err := openContextFile()
if err != nil {
@@ -903,18 +904,10 @@
}
defer in.Close()
- bufin = bufio.NewReader(in)
+ scanner := bufio.NewScanner(in)
- for done := false; !done; {
- var line string
- if line, err = bufin.ReadString('\n'); err != nil {
- if err == io.EOF {
- done = true
- } else {
- break
- }
- }
- line = strings.TrimSpace(line)
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
if len(line) == 0 {
// Skip blank lines
continue
@@ -924,7 +917,7 @@
continue
}
if groups := assignRegex.FindStringSubmatch(line); groups != nil {
- key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
+ key, val := strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
labels[key] = strings.Trim(val, "\"")
}
}
@@ -1015,7 +1008,7 @@
return "", err
}
mcsDelete(tcon["level"])
- mcsAdd(scon["level"])
+ _ = mcsAdd(scon["level"])
tcon["level"] = scon["level"]
return tcon.Get(), nil
}
@@ -1095,3 +1088,124 @@
func disableSecOpt() []string {
return []string{"disable"}
}
+
+// findUserInContext scans the reader for a valid SELinux context
+// match that is verified with the verifier. Invalid contexts are
+// skipped. It returns a matched context or an empty string if no
+// match is found. If a scanner error occurs, it is returned.
+func findUserInContext(context Context, r io.Reader, verifier func(string) error) (string, error) {
+ fromRole := context["role"]
+ fromType := context["type"]
+ scanner := bufio.NewScanner(r)
+
+ for scanner.Scan() {
+ fromConns := strings.Fields(scanner.Text())
+ if len(fromConns) == 0 {
+ // Skip blank lines
+ continue
+ }
+
+ line := fromConns[0]
+
+ if line[0] == ';' || line[0] == '#' {
+ // Skip comments
+ continue
+ }
+
+ // user context files contexts are formatted as
+ // role_r:type_t:s0 where the user is missing.
+ lineArr := strings.SplitN(line, ":", 4)
+ // skip context with typo, or role and type do not match
+ if len(lineArr) != 3 ||
+ lineArr[0] != fromRole ||
+ lineArr[1] != fromType {
+ continue
+ }
+
+ for _, cc := range fromConns[1:] {
+ toConns := strings.SplitN(cc, ":", 4)
+ if len(toConns) != 3 {
+ continue
+ }
+
+ context["role"] = toConns[0]
+ context["type"] = toConns[1]
+
+ outConn := context.get()
+ if err := verifier(outConn); err != nil {
+ continue
+ }
+
+ return outConn, nil
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return "", errors.Wrap(err, "failed to scan for context")
+ }
+
+ return "", nil
+}
+
+func getDefaultContextFromReaders(c *defaultSECtx) (string, error) {
+ if c.verifier == nil {
+ return "", ErrVerifierNil
+ }
+
+ context, err := newContext(c.scon)
+ if err != nil {
+ return "", errors.Wrapf(err, "failed to create label for %s", c.scon)
+ }
+
+ // set so the verifier validates the matched context with the provided user and level.
+ context["user"] = c.user
+ context["level"] = c.level
+
+ conn, err := findUserInContext(context, c.userRdr, c.verifier)
+ if err != nil {
+ return "", err
+ }
+
+ if conn != "" {
+ return conn, nil
+ }
+
+ conn, err = findUserInContext(context, c.defaultRdr, c.verifier)
+ if err != nil {
+ return "", err
+ }
+
+ if conn != "" {
+ return conn, nil
+ }
+
+ return "", errors.Wrapf(ErrContextMissing, "context not found: %q", c.scon)
+}
+
+func getDefaultContextWithLevel(user, level, scon string) (string, error) {
+ userPath := filepath.Join(policyRoot, selinuxUsersDir, user)
+ defaultPath := filepath.Join(policyRoot, defaultContexts)
+
+ fu, err := os.Open(userPath)
+ if err != nil {
+ return "", err
+ }
+ defer fu.Close()
+
+ fd, err := os.Open(defaultPath)
+ if err != nil {
+ return "", err
+ }
+ defer fd.Close()
+
+ c := defaultSECtx{
+ user: user,
+ level: level,
+ scon: scon,
+ userRdr: fu,
+ defaultRdr: fd,
+ verifier: securityCheckContext,
+ }
+
+ return getDefaultContextFromReaders(&c)
+}
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go
index c526b21..e4b65c9 100644
--- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go
+++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go
@@ -146,3 +146,7 @@
func disableSecOpt() []string {
return []string{"disable"}
}
+
+func getDefaultContextWithLevel(user, level, scon string) (string, error) {
+ return "", nil
+}
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go b/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go
index de5c80e..2365b4b 100644
--- a/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go
+++ b/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go
@@ -6,21 +6,21 @@
"golang.org/x/sys/unix"
)
-// Returns a []byte slice if the xattr is set and nil otherwise
-// Requires path and its attribute as arguments
-func lgetxattr(path string, attr string) ([]byte, error) {
+// lgetxattr returns a []byte slice containing the value of
+// an extended attribute attr set for path.
+func lgetxattr(path, attr string) ([]byte, error) {
// Start with a 128 length byte array
dest := make([]byte, 128)
- sz, errno := unix.Lgetxattr(path, attr, dest)
+ sz, errno := doLgetxattr(path, attr, dest)
for errno == unix.ERANGE {
// Buffer too small, use zero-sized buffer to get the actual size
- sz, errno = unix.Lgetxattr(path, attr, []byte{})
+ sz, errno = doLgetxattr(path, attr, []byte{})
if errno != nil {
return nil, errno
}
dest = make([]byte, sz)
- sz, errno = unix.Lgetxattr(path, attr, dest)
+ sz, errno = doLgetxattr(path, attr, dest)
}
if errno != nil {
return nil, errno
@@ -28,3 +28,13 @@
return dest[:sz], nil
}
+
+// doLgetxattr is a wrapper that retries on EINTR
+func doLgetxattr(path, attr string, dest []byte) (int, error) {
+ for {
+ sz, err := unix.Lgetxattr(path, attr, dest)
+ if err != unix.EINTR {
+ return sz, err
+ }
+ }
+}
diff --git a/vendor/github.com/opencontainers/selinux/go.mod b/vendor/github.com/opencontainers/selinux/go.mod
index 7bda067..18df624 100644
--- a/vendor/github.com/opencontainers/selinux/go.mod
+++ b/vendor/github.com/opencontainers/selinux/go.mod
@@ -4,6 +4,6 @@
require (
github.com/pkg/errors v0.9.1
- github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243
+ github.com/willf/bitset v1.1.11
golang.org/x/sys v0.0.0-20191115151921-52ab43148777
)
diff --git a/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go b/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go
index 63fde18..437b12b 100644
--- a/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go
+++ b/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go
@@ -20,17 +20,16 @@
//
// Note that this implementation only supports primitive error handling:
//
-// * no errors are ever passed to WalkFn
+// - no errors are ever passed to WalkFn;
//
-// * once a walkFn returns any error, all further processing stops
-// and the error is returned to the caller of Walk;
+// - once a walkFn returns any error, all further processing stops
+// and the error is returned to the caller of Walk;
//
-// * filepath.SkipDir is not supported;
+// - filepath.SkipDir is not supported;
//
-// * if more than one walkFn instance will return an error, only one
-// of such errors will be propagated and returned by Walk, others
-// will be silently discarded.
-//
+// - if more than one walkFn instance will return an error, only one
+// of such errors will be propagated and returned by Walk, others
+// will be silently discarded.
func Walk(root string, walkFn WalkFunc) error {
return WalkN(root, walkFn, runtime.NumCPU()*2)
}
@@ -38,6 +37,8 @@
// WalkN is a wrapper for filepath.Walk which can call multiple walkFn
// in parallel, allowing to handle each item concurrently. A maximum of
// num walkFn will be called at any one time.
+//
+// Please see Walk documentation for caveats of using this function.
func WalkN(root string, walkFn WalkFunc, num int) error {
// make sure limit is sensible
if num < 1 {