From f0ea41ad1fe3fe04a7a649f1d348889249d16ebd Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Wed, 4 Mar 2026 22:58:39 +1100 Subject: [PATCH 1/6] CHANGELOG: add notice for removed libct/utils APIs Ref: a412bd93e9ee ("libct/utils: remove Deprecated functions") Signed-off-by: Aleksa Sarai --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa47bc838..ef5c261b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### libcontainer API ### +- The following deprecated Go APIs have been removed: + - `CleanPath`, `StripRoot`, and `WithProcfd` from `libcontainer/utils`. Note + that `WithProcfdFile` has not been removed (due to import cycle issues) but + is instead marked as internal in its godoc comment. (#5051) + ## [1.4.0] - 2025-11-27 > 路漫漫其修远兮,吾将上下而求索! From 8fd8e433f86c7512a93dc20e937c867ed61ec7e1 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Wed, 4 Mar 2026 20:38:53 +1100 Subject: [PATCH 2/6] libct: config: remove deprecated cgroup types These were all marked deprecated in commit a75076b4a413 ("Switch to opencontainers/cgroups") when we switched maintenance of our cgroup code to opencontainers/cgroups. Users have had ample time to switch to opencontainers/cgroups themselves, so we can finally remove this. Signed-off-by: Aleksa Sarai --- CHANGELOG.md | 16 +++++++++++++ libcontainer/configs/cgroup_deprecated.go | 29 ----------------------- 2 files changed, 16 insertions(+), 29 deletions(-) delete mode 100644 libcontainer/configs/cgroup_deprecated.go diff --git a/CHANGELOG.md b/CHANGELOG.md index ef5c261b7..69c726b29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `CleanPath`, `StripRoot`, and `WithProcfd` from `libcontainer/utils`. Note that `WithProcfdFile` has not been removed (due to import cycle issues) but is instead marked as internal in its godoc comment. (#5051) + - All of the cgroup-related types and functions from `libcontainer/configs` + which are now maintained in `github.com/opencontainers/cgroups` (#5141): + - `libcontainer/configs.Cgroup` + - `libcontainer/configs.Resources` + - `libcontainer/configs.FreezerState` + - `libcontainer/configs.LinuxRdma` + - `libcontainer/configs.BlockIODevice` + - `libcontainer/configs.WeightDevice` + - `libcontainer/configs.ThrottleDevice` + - `libcontainer/configs.HugepageLimit` + - `libcontainer/configs.IfPrioMap` + - `libcontainer/configs.Undefined` + - `libcontainer/configs.Frozen` + - `libcontainer/configs.Thawed` + - `libcontainer/configs.NewWeightDevice` + - `libcontainer/configs.NewThrottleDevice` ## [1.4.0] - 2025-11-27 diff --git a/libcontainer/configs/cgroup_deprecated.go b/libcontainer/configs/cgroup_deprecated.go deleted file mode 100644 index 7836b240a..000000000 --- a/libcontainer/configs/cgroup_deprecated.go +++ /dev/null @@ -1,29 +0,0 @@ -package configs - -import "github.com/opencontainers/cgroups" - -// Deprecated: use [github.com/opencontainers/cgroups]. -type ( - Cgroup = cgroups.Cgroup - Resources = cgroups.Resources - FreezerState = cgroups.FreezerState - LinuxRdma = cgroups.LinuxRdma - BlockIODevice = cgroups.BlockIODevice - WeightDevice = cgroups.WeightDevice - ThrottleDevice = cgroups.ThrottleDevice - HugepageLimit = cgroups.HugepageLimit - IfPrioMap = cgroups.IfPrioMap -) - -// Deprecated: use [github.com/opencontainers/cgroups]. -const ( - Undefined = cgroups.Undefined - Frozen = cgroups.Frozen - Thawed = cgroups.Thawed -) - -// Deprecated: use [github.com/opencontainers/cgroups]. -var ( - NewWeightDevice = cgroups.NewWeightDevice - NewThrottleDevice = cgroups.NewThrottleDevice -) From 87b0804345c8fe8b63c64bee4db7cd8d304f6162 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Wed, 4 Mar 2026 20:42:35 +1100 Subject: [PATCH 3/6] libct: remove deprecated HooksList.RunHooks This was deprecated in commit e6a4870e4ac40 ("libct: better errors for hooks"), and users have had ample time to migrate to Hooks.Run since. Signed-off-by: Aleksa Sarai --- CHANGELOG.md | 1 + libcontainer/configs/config.go | 11 ----------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69c726b29..4fdb2cb92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `libcontainer/configs.Thawed` - `libcontainer/configs.NewWeightDevice` - `libcontainer/configs.NewThrottleDevice` + - `libcontainer/configs.HookList.RunHooks` (#5141) ## [1.4.0] - 2025-11-27 diff --git a/libcontainer/configs/config.go b/libcontainer/configs/config.go index 6141c018f..b8f2321c2 100644 --- a/libcontainer/configs/config.go +++ b/libcontainer/configs/config.go @@ -466,17 +466,6 @@ type Capabilities struct { Ambient []string `json:"Ambient,omitempty"` } -// Deprecated: use [Hooks.Run] instead. -func (hooks HookList) RunHooks(state *specs.State) error { - for i, h := range hooks { - if err := h.Run(state); err != nil { - return fmt.Errorf("error running hook #%d: %w", i, err) - } - } - - return nil -} - func (hooks *Hooks) UnmarshalJSON(b []byte) error { var state map[HookName][]CommandHook From 6a77ee7864770d2e81142dfd63e14314e832666f Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Wed, 4 Mar 2026 20:47:29 +1100 Subject: [PATCH 4/6] libct: remove deprecated MPOL_* constants These were inadvertently added to our exported APIs by commit eeda7bdf80cca ("Add memory policy support"). We couldn't remove them from runc 1.4.x, but we deprecated them in commit 3741f9186d21 ("libct/configs: mark MPOL_* constants as deprecated") and marked them for removal in runc 1.5. Users should never have used these in the first place. Signed-off-by: Aleksa Sarai --- CHANGELOG.md | 3 ++- libcontainer/configs/memorypolicy.go | 19 ------------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fdb2cb92..3759fb51d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `libcontainer/configs.Thawed` - `libcontainer/configs.NewWeightDevice` - `libcontainer/configs.NewThrottleDevice` - - `libcontainer/configs.HookList.RunHooks` (#5141) + - `libcontainer/configs.HookList.RunHooks`. (#5141) + - `libcontainer/configs.MPOL_*` (#5414) ## [1.4.0] - 2025-11-27 diff --git a/libcontainer/configs/memorypolicy.go b/libcontainer/configs/memorypolicy.go index b7b9c6bce..81bff0d10 100644 --- a/libcontainer/configs/memorypolicy.go +++ b/libcontainer/configs/memorypolicy.go @@ -2,25 +2,6 @@ package configs import "golang.org/x/sys/unix" -// Memory policy modes and flags as defined in /usr/include/linux/mempolicy.h -// -// Deprecated: use constants from [unix] instead. -// -//nolint:revive,staticcheck,nolintlint // ignore ALL_CAPS errors in consts from numaif.h, will match unix.* in the future -const ( - MPOL_DEFAULT = unix.MPOL_DEFAULT - MPOL_PREFERRED = unix.MPOL_PREFERRED - MPOL_BIND = unix.MPOL_BIND - MPOL_INTERLEAVE = unix.MPOL_INTERLEAVE - MPOL_LOCAL = unix.MPOL_LOCAL - MPOL_PREFERRED_MANY = unix.MPOL_PREFERRED_MANY - MPOL_WEIGHTED_INTERLEAVE = unix.MPOL_WEIGHTED_INTERLEAVE - - MPOL_F_STATIC_NODES = unix.MPOL_F_STATIC_NODES - MPOL_F_RELATIVE_NODES = unix.MPOL_F_RELATIVE_NODES - MPOL_F_NUMA_BALANCING = unix.MPOL_F_NUMA_BALANCING -) - // LinuxMemoryPolicy contains memory policy configuration. type LinuxMemoryPolicy struct { // Mode specifies memory policy mode without mode flags. See From 625ef531b7c0bb27c0664da6ca33367d8e610a7b Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Wed, 4 Mar 2026 22:13:05 +1100 Subject: [PATCH 5/6] libct: devices: drop deprecated cgroup types These were all marked deprecated in commit a75076b4a413 ("Switch to opencontainers/cgroups") when we switched maintenance of our cgroup code to opencontainers/cgroups. Users have had ample time to switch to opencontainers/cgroups themselves, so we can finally remove this. Note that the whole libcontainer/devices package will be moved to moby/sys in the near future, so this whole package will be marked deprecated soon. Signed-off-by: Aleksa Sarai --- CHANGELOG.md | 11 ++++++++++ libcontainer/devices/device_deprecated.go | 20 ------------------ libcontainer/devices/device_unix.go | 25 ++++++++++++----------- libcontainer/devices/device_unix_test.go | 5 +++-- 4 files changed, 27 insertions(+), 34 deletions(-) delete mode 100644 libcontainer/devices/device_deprecated.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 3759fb51d..3d6bb6208 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `libcontainer/configs.NewThrottleDevice` - `libcontainer/configs.HookList.RunHooks`. (#5141) - `libcontainer/configs.MPOL_*` (#5414) + - All of the types in `libcontainer/devices` which are now maintained in + `github.com/opencontainers/cgroups/devices/config` (#5141): + - `libcontainer/devices.Wildcard` + - `libcontainer/devices.WildcardDevice` + - `libcontainer/devices.BlockDevice` + - `libcontainer/devices.CharDevice` + - `libcontainer/devices.FifoDevice` + - `libcontainer/devices.Device` + - `libcontainer/devices.Permissions` + - `libcontainer/devices.Type` + - `libcontainer/devices.Rule` ## [1.4.0] - 2025-11-27 diff --git a/libcontainer/devices/device_deprecated.go b/libcontainer/devices/device_deprecated.go deleted file mode 100644 index 9483f054d..000000000 --- a/libcontainer/devices/device_deprecated.go +++ /dev/null @@ -1,20 +0,0 @@ -package devices - -import "github.com/opencontainers/cgroups/devices/config" - -// Deprecated: use [github.com/opencontainers/cgroups/devices/config]. -const ( - Wildcard = config.Wildcard - WildcardDevice = config.WildcardDevice - BlockDevice = config.BlockDevice - CharDevice = config.CharDevice - FifoDevice = config.FifoDevice -) - -// Deprecated: use [github.com/opencontainers/cgroups/devices/config]. -type ( - Device = config.Device - Permissions = config.Permissions - Type = config.Type - Rule = config.Rule -) diff --git a/libcontainer/devices/device_unix.go b/libcontainer/devices/device_unix.go index 409e58e96..4489663b2 100644 --- a/libcontainer/devices/device_unix.go +++ b/libcontainer/devices/device_unix.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" + "github.com/opencontainers/cgroups/devices/config" "golang.org/x/sys/unix" ) @@ -22,7 +23,7 @@ var ( // DeviceFromPath takes the path to a device and its cgroup_permissions (which // cannot be easily queried) to look up the information about a linux device // and returns that information as a Device struct. -func DeviceFromPath(path, permissions string) (*Device, error) { +func DeviceFromPath(path, permissions string) (*config.Device, error) { var stat unix.Stat_t err := unixLstat(path, &stat) if err != nil { @@ -30,7 +31,7 @@ func DeviceFromPath(path, permissions string) (*Device, error) { } var ( - devType Type + devType config.Type mode = stat.Mode devNumber = uint64(stat.Rdev) //nolint:unconvert // Rdev is uint32 on e.g. MIPS. major = unix.Major(devNumber) @@ -38,20 +39,20 @@ func DeviceFromPath(path, permissions string) (*Device, error) { ) switch mode & unix.S_IFMT { case unix.S_IFBLK: - devType = BlockDevice + devType = config.BlockDevice case unix.S_IFCHR: - devType = CharDevice + devType = config.CharDevice case unix.S_IFIFO: - devType = FifoDevice + devType = config.FifoDevice default: return nil, ErrNotADevice } - return &Device{ - Rule: Rule{ + return &config.Device{ + Rule: config.Rule{ Type: devType, Major: int64(major), Minor: int64(minor), - Permissions: Permissions(permissions), + Permissions: config.Permissions(permissions), }, Path: path, FileMode: os.FileMode(mode &^ unix.S_IFMT), @@ -61,18 +62,18 @@ func DeviceFromPath(path, permissions string) (*Device, error) { } // HostDevices returns all devices that can be found under /dev directory. -func HostDevices() ([]*Device, error) { +func HostDevices() ([]*config.Device, error) { return GetDevices("/dev") } // GetDevices recursively traverses a directory specified by path // and returns all devices found there. -func GetDevices(path string) ([]*Device, error) { +func GetDevices(path string) ([]*config.Device, error) { files, err := osReadDir(path) if err != nil { return nil, err } - var out []*Device + var out []*config.Device for _, f := range files { switch { case f.IsDir(): @@ -103,7 +104,7 @@ func GetDevices(path string) ([]*Device, error) { } return nil, err } - if device.Type == FifoDevice { + if device.Type == config.FifoDevice { continue } out = append(out, device) diff --git a/libcontainer/devices/device_unix_test.go b/libcontainer/devices/device_unix_test.go index 2e00847ac..2b5860532 100644 --- a/libcontainer/devices/device_unix_test.go +++ b/libcontainer/devices/device_unix_test.go @@ -8,6 +8,7 @@ import ( "os" "testing" + "github.com/opencontainers/cgroups/devices/config" "golang.org/x/sys/unix" ) @@ -85,8 +86,8 @@ func TestHostDevicesAllValid(t *testing.T) { t.Errorf("device entry %+v has zero major number", device) } switch device.Type { - case BlockDevice, CharDevice: - case FifoDevice: + case config.BlockDevice, config.CharDevice: + case config.FifoDevice: t.Logf("fifo devices shouldn't show up from HostDevices") fallthrough default: From e67725c0874557eab5bf7ad61cf4ba7f6673f778 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Wed, 4 Mar 2026 22:34:49 +1100 Subject: [PATCH 6/6] contrib: remove deprecated memfd-bind binary This was a really ugly hack to try to reduce the impact of our original set of CVE-2019-5736 mitigations, but unfortunately had too many caveats to its use to ever be really useful. In addition, it was completely obsoleted by the migration to using an detached overlayfs mount in commit 515f09f7b1db ("dmz: use overlayfs to write-protect /proc/self/exe if possible"). Signed-off-by: Aleksa Sarai --- .gitignore | 1 - CHANGELOG.md | 5 + Makefile | 7 +- contrib/cmd/memfd-bind/README.md | 69 ------ contrib/cmd/memfd-bind/memfd-bind.go | 251 --------------------- contrib/cmd/memfd-bind/memfd-bind@.service | 11 - 6 files changed, 6 insertions(+), 338 deletions(-) delete mode 100644 contrib/cmd/memfd-bind/README.md delete mode 100644 contrib/cmd/memfd-bind/memfd-bind.go delete mode 100644 contrib/cmd/memfd-bind/memfd-bind@.service diff --git a/.gitignore b/.gitignore index 6991faf08..26914f91d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ vendor/pkg /runc /runc-* -/contrib/cmd/memfd-bind/memfd-bind /tests/cmd/_bin man/man8 release diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d6bb6208..1cc3328b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `libcontainer/devices.Type` - `libcontainer/devices.Rule` +### Removed ### +- The `memfd-bind` helper binary has been removed, as it has never been + particularly useful and was completely obsoleted by the changes to + `/proc/self/exe` sealing we introduced in runc [1.2.0][]. (#5141) + ## [1.4.0] - 2025-11-27 > 路漫漫其修远兮,吾将上下而求索! diff --git a/Makefile b/Makefile index deaec2566..0a00e6607 100644 --- a/Makefile +++ b/Makefile @@ -77,11 +77,7 @@ runc-bin: $(GO_BUILD) -o runc . .PHONY: all -all: runc memfd-bind - -.PHONY: memfd-bind -memfd-bind: - $(GO_BUILD) -o contrib/cmd/$@/$@ ./contrib/cmd/$@ +all: runc TESTBINDIR := tests/cmd/_bin $(TESTBINDIR): @@ -96,7 +92,6 @@ $(TESTBINS): $(TESTBINDIR) .PHONY: clean clean: rm -f runc runc-* - rm -f contrib/cmd/memfd-bind/memfd-bind rm -fr $(TESTBINDIR) sudo rm -rf release rm -rf man/man8 diff --git a/contrib/cmd/memfd-bind/README.md b/contrib/cmd/memfd-bind/README.md deleted file mode 100644 index 932292502..000000000 --- a/contrib/cmd/memfd-bind/README.md +++ /dev/null @@ -1,69 +0,0 @@ -## memfd-bind ## - -> **NOTE**: Since runc 1.2.0, runc will now use a private overlayfs mount to -> protect the runc binary (if you are on Linux 5.1 or later). This protection -> is far more light-weight than memfd-bind, and for most users this should -> obviate the need for `memfd-bind` entirely. Rootless containers will still -> make a memfd copy (unless you are using `runc` itself inside a user namespace -> -- a-la [`rootlesskit`][rootlesskit] -- and are on Linux 5.11 or later), but -> `memfd-bind` is not particularly useful for rootless container users anyway -> (see [Caveats](#Caveats) for more details). - -`runc` sometimes has to make a binary copy of itself when constructing a -container process in order to defend against certain container runtime attacks -such as CVE-2019-5736. - -This cloned binary only exists until the container process starts (this means -for `runc run` and `runc exec`, it only exists for a few hundred milliseconds --- for `runc create` it exists until `runc start` is called). However, because -the clone is done using a memfd (or by creating files in directories that are -likely to be a `tmpfs`), this can lead to temporary increases in *host* memory -usage. Unless you are running on a cgroupv1 system with the cgroupv1 memory -controller enabled and the (deprecated) `memory.move_charge_at_immigrate` -enabled, there is no effect on the container's memory. - -However, for certain configurations this can still be undesirable. This daemon -allows you to create a sealed memfd copy of the `runc` binary, which will cause -`runc` to skip all binary copying, resulting in no additional memory usage for -each container process (instead there is a single in-memory copy of the -binary). It should be noted that (strictly speaking) this is slightly less -secure if you are concerned about Dirty Cow-like 0-day kernel vulnerabilities, -but for most users the security benefit is identical. - -The provided `memfd-bind@.service` file can be used to get systemd to manage -this daemon. You can supply the path like so: - -```bash -systemctl start memfd-bind@$(systemd-escape -p /usr/bin/runc) -``` - -Thus, there are three ways of protecting against CVE-2019-5736, in order of how -much memory usage they can use: - -* `memfd-bind` only creates a single in-memory copy of the `runc` binary (about - 10MB), regardless of how many containers are running. - -* The classic method of making a copy of the entire `runc` binary during - container process setup takes up about 10MB per process spawned inside the - container by runc (both pid1 and `runc exec`). - -[rootlesskit]: https://github.com/rootless-containers/rootlesskit - -### Caveats ### - -There are several downsides with using `memfd-bind` on the `runc` binary: - -* The `memfd-bind` process needs to continue to run indefinitely in order for - the memfd reference to stay alive. If the process is forcefully killed, the - bind-mount on top of the `runc` binary will become stale and nobody will be - able to execute it (you can use `memfd-bind --cleanup` to clean up the stale - mount). - -* Only root can execute the cloned binary due to permission restrictions on - accessing other process's files. More specifically, only users with ptrace - privileges over the memfd-bind daemon can access the file (but in practice - this is usually only root). - -* When updating `runc`, the daemon needs to be stopped before the update (so - the package manager can access the underlying file) and then restarted after - the update. diff --git a/contrib/cmd/memfd-bind/memfd-bind.go b/contrib/cmd/memfd-bind/memfd-bind.go deleted file mode 100644 index 7bc57b0b3..000000000 --- a/contrib/cmd/memfd-bind/memfd-bind.go +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (c) 2023 SUSE LLC - * Copyright (c) 2023 Aleksa Sarai - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// memfd-bind is a command-line tool to construct a persistent -// sealed-memfd-copy of a binary, to allow administrators to amortise the cost -// of memfd cloning for runc. runc will not make its own copy of the binary if -// it detects that the binary is already a sealed-memfd-copy. -// -// Usage of this tool has a lot of caveats -- see this package's README for -// more details on what restrictions apply when using this tool. -// -// Deprecated: runc 1.2 and later use a different mechanism for protecting the -// runc binary that obviates the need for this tool. Unless you are on an old -// kernel or need to use an older runc version, this tool is no longer needed. -package main - -import ( - "errors" - "fmt" - "io" - "os" - "os/signal" - "runtime" - "strings" - "time" - - "github.com/opencontainers/runc/libcontainer/exeseal" - - "github.com/sirupsen/logrus" - "github.com/urfave/cli" - "golang.org/x/sys/unix" -) - -// version will be populated by the Makefile, read from -// VERSION file of the source code. -var version = "" - -// gitCommit will be the hash that the binary was built from -// and will be populated by the Makefile. -var gitCommit = "" - -const ( - usage = `Open Container Initiative contrib/cmd/memfd-bind - -In order to protect against certain container attacks, every runc invocation -that involves creating or joining a container will cause runc to make a copy of -the runc binary in memory (usually to a memfd). While "runc init" is very -short-lived, this extra memory usage can cause problems for containers with -very small memory limits (or containers that have many "runc exec" invocations -applied to them at the same time). - -memfd-bind is a tool to create a persistent memfd-sealed-copy of the runc binary, -which will cause runc to not make its own copy. This means you can get the -benefits of using a sealed memfd as runc's binary (even in a container breakout -attack to get write access to the runc binary, neither the underlying binary -nor the memfd copy can be changed). - -To use memfd-bind, just specify which path you want to create a socket path at -which you want to receive terminals: - - $ sudo memfd-bind /usr/bin/runc - -Note that (due to kernel restrictions on bind-mounts), this program must remain -running on the host in order for the binary to be readable (it is recommended -you use a systemd unit to keep this process around). - -If this program dies, there will be a leftover mountpoint that always returns --EINVAL when attempting to access it. You need to use memfd-bind --cleanup on the -path in order to unmount the path (regular umount(8) will not work): - - $ sudo memfd-bind --cleanup /usr/bin/runc - -Note that (due to restrictions on /proc/$pid/fd/$fd magic-link resolution), -only privileged users (specifically, those that have ptrace privileges over the -memfd-bind daemon) can access the memfd bind-mount. This means that using this -tool to harden your /usr/bin/runc binary would result in unprivileged users -being unable to execute the binary. If this is an issue, you could make all -privileged process use a different copy of runc (by making a copy in somewhere -like /usr/sbin/runc) and only using memfd-bind for the version used by -privileged users. -` -) - -func cleanup(path string) error { - file, err := os.OpenFile(path, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) - if err != nil { - return fmt.Errorf("cleanup: failed to open runc binary path: %w", err) - } - defer file.Close() - fdPath := fmt.Sprintf("/proc/self/fd/%d", file.Fd()) - - // Keep umounting until we hit a umount error. - for unix.Unmount(fdPath, unix.MNT_DETACH) == nil { - // loop... - logrus.Debugf("memfd-bind: path %q unmount succeeded...", path) - } - logrus.Infof("memfd-bind: path %q has been cleared of all old bind-mounts", path) - return nil -} - -// memfdClone is a memfd-only implementation of exeseal.CloneBinary. -func memfdClone(path string) (*os.File, error) { - binFile, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("failed to open runc binary path: %w", err) - } - defer binFile.Close() - stat, err := binFile.Stat() - if err != nil { - return nil, fmt.Errorf("checking %s size: %w", path, err) - } - size := stat.Size() - memfd, sealFn, err := exeseal.Memfd("/proc/self/exe") - if err != nil { - return nil, fmt.Errorf("creating memfd failed: %w", err) - } - copied, err := io.Copy(memfd, binFile) - if err != nil { - return nil, fmt.Errorf("copy binary: %w", err) - } else if copied != size { - return nil, fmt.Errorf("copied binary size mismatch: %d != %d", copied, size) - } - if err := sealFn(&memfd); err != nil { - return nil, fmt.Errorf("could not seal fd: %w", err) - } - if !exeseal.IsCloned(memfd) { - return nil, fmt.Errorf("cloned memfd is not properly sealed") - } - return memfd, nil -} - -func mount(path string) error { - memfdFile, err := memfdClone(path) - if err != nil { - return fmt.Errorf("memfd clone: %w", err) - } - defer memfdFile.Close() - memfdPath := fmt.Sprintf("/proc/self/fd/%d", memfdFile.Fd()) - - // We have to open an O_NOFOLLOW|O_PATH to the memfd magic-link because we - // cannot bind-mount the memfd itself (it's in the internal kernel mount - // namespace and cross-mount-namespace bind-mounts are not allowed). This - // also requires that this program stay alive continuously for the - // magic-link to stay alive... - memfdLink, err := os.OpenFile(memfdPath, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) - if err != nil { - return fmt.Errorf("mount: failed to /proc/self/fd magic-link for memfd: %w", err) - } - defer memfdLink.Close() - memfdLinkFdPath := fmt.Sprintf("/proc/self/fd/%d", memfdLink.Fd()) - - exeFile, err := os.OpenFile(path, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) - if err != nil { - return fmt.Errorf("mount: failed to open target runc binary path: %w", err) - } - defer exeFile.Close() - exeFdPath := fmt.Sprintf("/proc/self/fd/%d", exeFile.Fd()) - - err = unix.Mount(memfdLinkFdPath, exeFdPath, "", unix.MS_BIND, "") - if err != nil { - return fmt.Errorf("mount: failed to mount memfd on top of runc binary path target: %w", err) - } - - // If there is a signal we want to do cleanup. - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, os.Interrupt, unix.SIGTERM, unix.SIGINT) - go func() { - <-sigCh - logrus.Infof("memfd-bind: exit signal caught! cleaning up the bind-mount on %q...", path) - _ = cleanup(path) - os.Exit(0) - }() - - // Clean up things we don't need... - _ = exeFile.Close() - _ = memfdLink.Close() - - // We now have to stay alive to keep the magic-link alive... - logrus.Infof("memfd-bind: bind-mount of memfd over %q created -- looping forever!", path) - for { - // loop forever... - time.Sleep(time.Duration(1<<63 - 1)) - // make sure the memfd isn't gc'd - runtime.KeepAlive(memfdFile) - } -} - -func main() { - app := cli.NewApp() - app.Name = "memfd-bind" - app.Usage = usage - - // Set version to be the same as runC. - var v []string - if version != "" { - v = append(v, version) - } - if gitCommit != "" { - v = append(v, "commit: "+gitCommit) - } - app.Version = strings.Join(v, "\n") - - // Set the flags. - app.Flags = []cli.Flag{ - cli.BoolFlag{ - Name: "cleanup", - Usage: "Do not create a new memfd-sealed file, only clean up an existing one at .", - }, - cli.BoolFlag{ - Name: "debug", - Usage: "Enable debug logging.", - }, - } - - app.Action = func(ctx *cli.Context) error { - args := ctx.Args() - if len(args) != 1 { - return errors.New("need to specify a single path to the runc binary") - } - path := ctx.Args()[0] - - if ctx.Bool("debug") { - logrus.SetLevel(logrus.DebugLevel) - } - - err := cleanup(path) - // We only care about cleanup errors when doing --cleanup. - if ctx.Bool("cleanup") { - return err - } - return mount(path) - } - if err := app.Run(os.Args); err != nil { - fmt.Fprintf(os.Stderr, "memfd-bind: %v\n", err) - os.Exit(1) - } -} diff --git a/contrib/cmd/memfd-bind/memfd-bind@.service b/contrib/cmd/memfd-bind/memfd-bind@.service deleted file mode 100644 index 890869026..000000000 --- a/contrib/cmd/memfd-bind/memfd-bind@.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Manage memfd-bind of %f -Documentation=https://github.com/opencontainers/runc/blob/main/contrib/cmd/memfd-bind/README.md - -[Service] -Type=simple -ExecStart=memfd-bind "%f" -ExecStop=memfd-bind --cleanup "%f" - -[Install] -WantedBy=multi-user.target