mirror of
https://github.com/opencontainers/runc.git
synced 2025-12-24 11:50:58 +08:00
init: write sysctls using safe procfs API
sysctls could in principle also be used as a write gadget for arbitrary procfs files. As this requires getting a non-subset=pid /proc handle we amortise this by only allocating a single procfs handle for all sysctl writes. Fixes: GHSA-cgrx-mc8f-2prm CVE-2025-52881 Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
This commit is contained in:
54
internal/sys/sysctl_linux.go
Normal file
54
internal/sys/sysctl_linux.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite"
|
||||
"github.com/cyphar/filepath-securejoin/pathrs-lite/procfs"
|
||||
)
|
||||
|
||||
func procfsOpenRoot(proc *procfs.Handle, subpath string, flags int) (*os.File, error) {
|
||||
handle, err := proc.OpenRoot(subpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer handle.Close()
|
||||
|
||||
return pathrs.Reopen(handle, flags)
|
||||
}
|
||||
|
||||
// WriteSysctls sets the given sysctls to the requested values.
|
||||
func WriteSysctls(sysctls map[string]string) error {
|
||||
// We are going to write multiple sysctls, which require writing to an
|
||||
// unmasked procfs which is not going to be cached. To avoid creating a new
|
||||
// procfs instance for each one, just allocate one handle for all of them.
|
||||
proc, err := procfs.OpenUnsafeProcRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer proc.Close()
|
||||
|
||||
for key, value := range sysctls {
|
||||
keyPath := strings.ReplaceAll(key, ".", "/")
|
||||
|
||||
sysctlFile, err := procfsOpenRoot(proc, "sys/"+keyPath, unix.O_WRONLY|unix.O_TRUNC|unix.O_CLOEXEC)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open sysctl %s file: %w", key, err)
|
||||
}
|
||||
defer sysctlFile.Close()
|
||||
|
||||
n, err := io.WriteString(sysctlFile, value)
|
||||
if n != len(value) && err == nil {
|
||||
err = fmt.Errorf("short write to file (%d bytes != %d bytes)", n, len(value))
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write sysctl %s = %q: %w", key, value, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -1351,13 +1350,6 @@ func maskPaths(paths []string, mountLabel string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeSystemProperty writes the value to a path under /proc/sys as determined from the key.
|
||||
// For e.g. net.ipv4.ip_forward translated to /proc/sys/net/ipv4/ip_forward.
|
||||
func writeSystemProperty(key, value string) error {
|
||||
keyPath := strings.ReplaceAll(key, ".", "/")
|
||||
return os.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0o644)
|
||||
}
|
||||
|
||||
// Do the mount operation followed by additional mounts required to take care
|
||||
// of propagation flags. This will always be scoped inside the container rootfs.
|
||||
func mountPropagate(m mountEntry, rootfs string, mountLabel string) error {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/opencontainers/runc/internal/linux"
|
||||
"github.com/opencontainers/runc/internal/pathrs"
|
||||
"github.com/opencontainers/runc/internal/sys"
|
||||
"github.com/opencontainers/runc/libcontainer/apparmor"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/keys"
|
||||
@@ -132,10 +133,8 @@ func (l *linuxStandardInit) Init() error {
|
||||
return fmt.Errorf("unable to apply apparmor profile: %w", err)
|
||||
}
|
||||
|
||||
for key, value := range l.config.Config.Sysctl {
|
||||
if err := writeSystemProperty(key, value); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sys.WriteSysctls(l.config.Config.Sysctl); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, path := range l.config.Config.ReadonlyPaths {
|
||||
if err := readonlyPath(path); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user