Skip to content

Commit f3df262

Browse files
authored
Merge pull request #4697 from kolyshkin/eintr
Introduce/use internal/linux pkg to handle EINTR and error wrapping
2 parents 1dc89f7 + 491326c commit f3df262

File tree

14 files changed

+141
-72
lines changed

14 files changed

+141
-72
lines changed

internal/linux/eintr.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package linux
2+
3+
import (
4+
"errors"
5+
6+
"golang.org/x/sys/unix"
7+
)
8+
9+
// retryOnEINTR takes a function that returns an error and calls it
10+
// until the error returned is not EINTR.
11+
func retryOnEINTR(fn func() error) error {
12+
for {
13+
err := fn()
14+
if !errors.Is(err, unix.EINTR) {
15+
return err
16+
}
17+
}
18+
}
19+
20+
// retryOnEINTR2 is like retryOnEINTR, but it returns 2 values.
21+
func retryOnEINTR2[T any](fn func() (T, error)) (T, error) {
22+
for {
23+
val, err := fn()
24+
if !errors.Is(err, unix.EINTR) {
25+
return val, err
26+
}
27+
}
28+
}

internal/linux/linux.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package linux
2+
3+
import (
4+
"os"
5+
6+
"golang.org/x/sys/unix"
7+
)
8+
9+
// Dup3 wraps [unix.Dup3].
10+
func Dup3(oldfd, newfd, flags int) error {
11+
err := retryOnEINTR(func() error {
12+
return unix.Dup3(oldfd, newfd, flags)
13+
})
14+
return os.NewSyscallError("dup3", err)
15+
}
16+
17+
// Exec wraps [unix.Exec].
18+
func Exec(cmd string, args []string, env []string) error {
19+
err := retryOnEINTR(func() error {
20+
return unix.Exec(cmd, args, env)
21+
})
22+
if err != nil {
23+
return &os.PathError{Op: "exec", Path: cmd, Err: err}
24+
}
25+
return nil
26+
}
27+
28+
// Getwd wraps [unix.Getwd].
29+
func Getwd() (wd string, err error) {
30+
wd, err = retryOnEINTR2(unix.Getwd)
31+
return wd, os.NewSyscallError("getwd", err)
32+
}
33+
34+
// Open wraps [unix.Open].
35+
func Open(path string, mode int, perm uint32) (fd int, err error) {
36+
fd, err = retryOnEINTR2(func() (int, error) {
37+
return unix.Open(path, mode, perm)
38+
})
39+
if err != nil {
40+
return -1, &os.PathError{Op: "open", Path: path, Err: err}
41+
}
42+
return fd, nil
43+
}
44+
45+
// Openat wraps [unix.Openat].
46+
func Openat(dirfd int, path string, mode int, perm uint32) (fd int, err error) {
47+
fd, err = retryOnEINTR2(func() (int, error) {
48+
return unix.Openat(dirfd, path, mode, perm)
49+
})
50+
if err != nil {
51+
return -1, &os.PathError{Op: "openat", Path: path, Err: err}
52+
}
53+
return fd, nil
54+
}
55+
56+
// Recvfrom wraps [unix.Recvfrom].
57+
func Recvfrom(fd int, p []byte, flags int) (n int, from unix.Sockaddr, err error) {
58+
err = retryOnEINTR(func() error {
59+
n, from, err = unix.Recvfrom(fd, p, flags)
60+
return err
61+
})
62+
if err != nil {
63+
return 0, nil, os.NewSyscallError("recvfrom", err)
64+
}
65+
return n, from, err
66+
}
67+
68+
// Sendmsg wraps [unix.Sendmsg].
69+
func Sendmsg(fd int, p, oob []byte, to unix.Sockaddr, flags int) error {
70+
err := retryOnEINTR(func() error {
71+
return unix.Sendmsg(fd, p, oob, to, flags)
72+
})
73+
return os.NewSyscallError("sendmsg", err)
74+
}

libcontainer/console_linux.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package libcontainer
33
import (
44
"os"
55

6+
"github.com/opencontainers/runc/internal/linux"
67
"golang.org/x/sys/unix"
78
)
89

@@ -26,16 +27,12 @@ func mountConsole(slavePath string) error {
2627
// dupStdio opens the slavePath for the console and dups the fds to the current
2728
// processes stdio, fd 0,1,2.
2829
func dupStdio(slavePath string) error {
29-
fd, err := unix.Open(slavePath, unix.O_RDWR, 0)
30+
fd, err := linux.Open(slavePath, unix.O_RDWR, 0)
3031
if err != nil {
31-
return &os.PathError{
32-
Op: "open",
33-
Path: slavePath,
34-
Err: err,
35-
}
32+
return err
3633
}
3734
for _, i := range []int{0, 1, 2} {
38-
if err := unix.Dup3(fd, i, 0); err != nil {
35+
if err := linux.Dup3(fd, i, 0); err != nil {
3936
return err
4037
}
4138
}

libcontainer/init_linux.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"golang.org/x/sys/unix"
2121

2222
"github.com/opencontainers/cgroups"
23+
"github.com/opencontainers/runc/internal/linux"
2324
"github.com/opencontainers/runc/libcontainer/capabilities"
2425
"github.com/opencontainers/runc/libcontainer/configs"
2526
"github.com/opencontainers/runc/libcontainer/system"
@@ -280,10 +281,10 @@ func verifyCwd() error {
280281
// details, and CVE-2024-21626 for the security issue that motivated this
281282
// check.
282283
//
283-
// We have to use unix.Getwd() here because os.Getwd() has a workaround for
284+
// We do not use os.Getwd() here because it has a workaround for
284285
// $PWD which involves doing stat(.), which can fail if the current
285286
// directory is inaccessible to the container process.
286-
if wd, err := unix.Getwd(); errors.Is(err, unix.ENOENT) {
287+
if wd, err := linux.Getwd(); errors.Is(err, unix.ENOENT) {
287288
return errors.New("current working directory is outside of container mount namespace root -- possible container breakout detected")
288289
} else if err != nil {
289290
return fmt.Errorf("failed to verify if current working directory is safe: %w", err)

libcontainer/rootfs_linux.go

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/opencontainers/cgroups"
2525
devices "github.com/opencontainers/cgroups/devices/config"
2626
"github.com/opencontainers/cgroups/fs2"
27+
"github.com/opencontainers/runc/internal/linux"
2728
"github.com/opencontainers/runc/libcontainer/configs"
2829
"github.com/opencontainers/runc/libcontainer/utils"
2930
)
@@ -883,12 +884,8 @@ func reOpenDevNull() error {
883884
}
884885
if stat.Rdev == devNullStat.Rdev {
885886
// Close and re-open the fd.
886-
if err := unix.Dup3(int(file.Fd()), fd, 0); err != nil {
887-
return &os.PathError{
888-
Op: "dup3",
889-
Path: "fd " + strconv.Itoa(int(file.Fd())),
890-
Err: err,
891-
}
887+
if err := linux.Dup3(int(file.Fd()), fd, 0); err != nil {
888+
return err
892889
}
893890
}
894891
}
@@ -1063,15 +1060,15 @@ func pivotRoot(rootfs string) error {
10631060
// with pivot_root this allows us to pivot without creating directories in
10641061
// the rootfs. Shout-outs to the LXC developers for giving us this idea.
10651062

1066-
oldroot, err := unix.Open("/", unix.O_DIRECTORY|unix.O_RDONLY, 0)
1063+
oldroot, err := linux.Open("/", unix.O_DIRECTORY|unix.O_RDONLY, 0)
10671064
if err != nil {
1068-
return &os.PathError{Op: "open", Path: "/", Err: err}
1065+
return err
10691066
}
10701067
defer unix.Close(oldroot)
10711068

1072-
newroot, err := unix.Open(rootfs, unix.O_DIRECTORY|unix.O_RDONLY, 0)
1069+
newroot, err := linux.Open(rootfs, unix.O_DIRECTORY|unix.O_RDONLY, 0)
10731070
if err != nil {
1074-
return &os.PathError{Op: "open", Path: rootfs, Err: err}
1071+
return err
10751072
}
10761073
defer unix.Close(newroot)
10771074

libcontainer/setns_init_linux.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/sirupsen/logrus"
1111
"golang.org/x/sys/unix"
1212

13+
"github.com/opencontainers/runc/internal/linux"
1314
"github.com/opencontainers/runc/libcontainer/apparmor"
1415
"github.com/opencontainers/runc/libcontainer/keys"
1516
"github.com/opencontainers/runc/libcontainer/seccomp"
@@ -156,5 +157,5 @@ func (l *linuxSetnsInit) Init() error {
156157
if err := utils.UnsafeCloseFrom(l.config.PassedFilesCount + 3); err != nil {
157158
return err
158159
}
159-
return system.Exec(name, l.config.Args, l.config.Env)
160+
return linux.Exec(name, l.config.Args, l.config.Env)
160161
}

libcontainer/specconv/spec_linux.go

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
dbus "github.com/godbus/dbus/v5"
1717
"github.com/opencontainers/cgroups"
1818
devices "github.com/opencontainers/cgroups/devices/config"
19+
"github.com/opencontainers/runc/internal/linux"
1920
"github.com/opencontainers/runc/libcontainer/configs"
2021
"github.com/opencontainers/runc/libcontainer/internal/userns"
2122
"github.com/opencontainers/runc/libcontainer/seccomp"
@@ -344,24 +345,13 @@ type CreateOpts struct {
344345
RootlessCgroups bool
345346
}
346347

347-
// getwd is a wrapper similar to os.Getwd, except it always gets
348-
// the value from the kernel, which guarantees the returned value
349-
// to be absolute and clean.
350-
func getwd() (wd string, err error) {
351-
for {
352-
wd, err = unix.Getwd()
353-
if err != unix.EINTR {
354-
break
355-
}
356-
}
357-
return wd, os.NewSyscallError("getwd", err)
358-
}
359-
360348
// CreateLibcontainerConfig creates a new libcontainer configuration from a
361349
// given specification and a cgroup name
362350
func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
363-
// runc's cwd will always be the bundle path
364-
cwd, err := getwd()
351+
// Runc's cwd will always be the bundle path.
352+
// Use the value from the kernel, which guarantees the returned value
353+
// to be absolute and clean.
354+
cwd, err := linux.Getwd()
365355
if err != nil {
366356
return nil, err
367357
}

libcontainer/standard_init_linux.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/sirupsen/logrus"
1212
"golang.org/x/sys/unix"
1313

14+
"github.com/opencontainers/runc/internal/linux"
1415
"github.com/opencontainers/runc/libcontainer/apparmor"
1516
"github.com/opencontainers/runc/libcontainer/configs"
1617
"github.com/opencontainers/runc/libcontainer/keys"
@@ -261,9 +262,9 @@ func (l *linuxStandardInit) Init() error {
261262
// user process. We open it through /proc/self/fd/$fd, because the fd that
262263
// was given to us was an O_PATH fd to the fifo itself. Linux allows us to
263264
// re-open an O_PATH fd through /proc.
264-
fd, err := unix.Open(fifoPath, unix.O_WRONLY|unix.O_CLOEXEC, 0)
265+
fd, err := linux.Open(fifoPath, unix.O_WRONLY|unix.O_CLOEXEC, 0)
265266
if err != nil {
266-
return &os.PathError{Op: "open exec fifo", Path: fifoPath, Err: err}
267+
return err
267268
}
268269
if _, err := unix.Write(fd, []byte("0")); err != nil {
269270
return &os.PathError{Op: "write exec fifo", Path: fifoPath, Err: err}
@@ -298,5 +299,5 @@ func (l *linuxStandardInit) Init() error {
298299
if err := utils.UnsafeCloseFrom(l.config.PassedFilesCount + 3); err != nil {
299300
return err
300301
}
301-
return system.Exec(name, l.config.Args, l.config.Env)
302+
return linux.Exec(name, l.config.Args, l.config.Env)
302303
}

libcontainer/sync_unix.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"sync/atomic"
88

9+
"github.com/opencontainers/runc/internal/linux"
910
"golang.org/x/sys/unix"
1011
)
1112

@@ -42,20 +43,9 @@ func (s *syncSocket) WritePacket(b []byte) (int, error) {
4243
}
4344

4445
func (s *syncSocket) ReadPacket() ([]byte, error) {
45-
var (
46-
size int
47-
err error
48-
)
49-
50-
for {
51-
size, _, err = unix.Recvfrom(int(s.f.Fd()), nil, unix.MSG_TRUNC|unix.MSG_PEEK)
52-
if err != unix.EINTR { //nolint:errorlint // unix errors are bare
53-
break
54-
}
55-
}
56-
46+
size, _, err := linux.Recvfrom(int(s.f.Fd()), nil, unix.MSG_TRUNC|unix.MSG_PEEK)
5747
if err != nil {
58-
return nil, fmt.Errorf("fetch packet length from socket: %w", os.NewSyscallError("recvfrom", err))
48+
return nil, fmt.Errorf("fetch packet length from socket: %w", err)
5949
}
6050
// We will only get a zero size if the socket has been closed from the
6151
// other end (otherwise recvfrom(2) will block until a packet is ready). In

libcontainer/system/linux.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,6 @@ func (p ParentDeathSignal) Set() error {
3232
return SetParentDeathSignal(uintptr(p))
3333
}
3434

35-
func Exec(cmd string, args []string, env []string) error {
36-
for {
37-
err := unix.Exec(cmd, args, env)
38-
if err != unix.EINTR {
39-
return &os.PathError{Op: "exec", Path: cmd, Err: err}
40-
}
41-
}
42-
}
43-
4435
func SetParentDeathSignal(sig uintptr) error {
4536
if err := unix.Prctl(unix.PR_SET_PDEATHSIG, sig, 0, 0, 0); err != nil {
4637
return err

0 commit comments

Comments
 (0)