Skip to content

Commit 77d217c

Browse files
committed
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 <[email protected]>
1 parent b3dd1bc commit 77d217c

File tree

3 files changed

+57
-12
lines changed

3 files changed

+57
-12
lines changed

internal/sys/sysctl_linux.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package sys
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"strings"
8+
9+
"golang.org/x/sys/unix"
10+
11+
"github.com/cyphar/filepath-securejoin/pathrs-lite"
12+
"github.com/cyphar/filepath-securejoin/pathrs-lite/procfs"
13+
)
14+
15+
func procfsOpenRoot(proc *procfs.Handle, subpath string, flags int) (*os.File, error) {
16+
handle, err := proc.OpenRoot(subpath)
17+
if err != nil {
18+
return nil, err
19+
}
20+
defer handle.Close()
21+
22+
return pathrs.Reopen(handle, flags)
23+
}
24+
25+
// WriteSysctls sets the given sysctls to the requested values.
26+
func WriteSysctls(sysctls map[string]string) error {
27+
// We are going to write multiple sysctls, which require writing to an
28+
// unmasked procfs which is not going to be cached. To avoid creating a new
29+
// procfs instance for each one, just allocate one handle for all of them.
30+
proc, err := procfs.OpenUnsafeProcRoot()
31+
if err != nil {
32+
return err
33+
}
34+
defer proc.Close()
35+
36+
for key, value := range sysctls {
37+
keyPath := strings.ReplaceAll(key, ".", "/")
38+
39+
sysctlFile, err := procfsOpenRoot(proc, "sys/"+keyPath, unix.O_WRONLY|unix.O_TRUNC|unix.O_CLOEXEC)
40+
if err != nil {
41+
return fmt.Errorf("open sysctl %s file: %w", key, err)
42+
}
43+
defer sysctlFile.Close()
44+
45+
n, err := io.WriteString(sysctlFile, value)
46+
if n != len(value) && err == nil {
47+
err = fmt.Errorf("short write to file (%d bytes != %d bytes)", n, len(value))
48+
}
49+
if err != nil {
50+
return fmt.Errorf("failed to write sysctl %s = %q: %w", key, value, err)
51+
}
52+
}
53+
return nil
54+
}

libcontainer/rootfs_linux.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"errors"
66
"fmt"
77
"os"
8-
"path"
98
"path/filepath"
109
"runtime"
1110
"strconv"
@@ -1351,13 +1350,6 @@ func maskPaths(paths []string, mountLabel string) error {
13511350
return nil
13521351
}
13531352

1354-
// writeSystemProperty writes the value to a path under /proc/sys as determined from the key.
1355-
// For e.g. net.ipv4.ip_forward translated to /proc/sys/net/ipv4/ip_forward.
1356-
func writeSystemProperty(key, value string) error {
1357-
keyPath := strings.ReplaceAll(key, ".", "/")
1358-
return os.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0o644)
1359-
}
1360-
13611353
// Do the mount operation followed by additional mounts required to take care
13621354
// of propagation flags. This will always be scoped inside the container rootfs.
13631355
func mountPropagate(m mountEntry, rootfs string, mountLabel string) error {

libcontainer/standard_init_linux.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/opencontainers/runc/internal/linux"
1515
"github.com/opencontainers/runc/internal/pathrs"
16+
"github.com/opencontainers/runc/internal/sys"
1617
"github.com/opencontainers/runc/libcontainer/apparmor"
1718
"github.com/opencontainers/runc/libcontainer/configs"
1819
"github.com/opencontainers/runc/libcontainer/keys"
@@ -132,10 +133,8 @@ func (l *linuxStandardInit) Init() error {
132133
return fmt.Errorf("unable to apply apparmor profile: %w", err)
133134
}
134135

135-
for key, value := range l.config.Config.Sysctl {
136-
if err := writeSystemProperty(key, value); err != nil {
137-
return err
138-
}
136+
if err := sys.WriteSysctls(l.config.Config.Sysctl); err != nil {
137+
return err
139138
}
140139
for _, path := range l.config.Config.ReadonlyPaths {
141140
if err := readonlyPath(path); err != nil {

0 commit comments

Comments
 (0)