Skip to content

Commit

Permalink
Fix runc run "permission denied" when rootless
Browse files Browse the repository at this point in the history
Since commit 957d97b was made to fix issue [7],
a few things happened:

- a similar functionality appeared in go 1.20 [1], so the issue
  mentioned in the comment (being removed) is no longer true;
- a bug in runc was found [2], which also affects go [3];
- the bug was fixed in go 1.21 [4] and 1.20.2 [5];
- a similar fix was made to x/sys/unix.Faccessat [6].

The essense of [2] is, even if a (non-root) user that the container is
run as does not have execute permission bit set for the executable, it
should still work in case runc has the CAP_DAC_OVERRIDE capability set.

To fix this [2] without reintroducing the older bug [7]:
- drop own Eaccess implementation;
- use the one from x/sys/unix for Go 1.19 (depends on [6]);
- do not use anything when Go 1.20+ is used.

NOTE it is virtually impossible to fix the bug [2] when Go 1.20 or Go
1.20.1 is used because of [3].

A test case is added by a separate commit.

Fixes: opencontainers#3715.

[1] https://go-review.googlesource.com/c/go/+/414824
[2] opencontainers#3715
[3] https://go.dev/issue/58552
[4] https://go-review.googlesource.com/c/go/+/468735
[5] https://go-review.googlesource.com/c/go/+/469956
[6] https://go-review.googlesource.com/c/sys/+/468877
[7] opencontainers#3520

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
(cherry picked from commit 8491d33)
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
  • Loading branch information
kolyshkin committed Apr 6, 2023
1 parent add2f54 commit bd6fc3e
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 24 deletions.
17 changes: 17 additions & 0 deletions libcontainer/eaccess_go119.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build !go1.20
// +build !go1.20

package libcontainer

import "golang.org/x/sys/unix"

func eaccess(path string) error {
// This check is similar to access(2) with X_OK except for
// setuid/setgid binaries where it checks against the effective
// (rather than real) uid and gid. It is not needed in go 1.20
// and beyond and will be removed later.

// Relies on code added in https://go-review.googlesource.com/c/sys/+/468877
// and older CLs linked from there.
return unix.Faccessat(unix.AT_FDCWD, path, unix.X_OK, unix.AT_EACCESS)
}
10 changes: 10 additions & 0 deletions libcontainer/eaccess_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//go:build go1.20

package libcontainer

func eaccess(path string) error {
// Not needed in Go 1.20+ as the functionality is already in there
// (added by https://go.dev/cl/416115, https://go.dev/cl/414824,
// and fixed in Go 1.20.2 by https://go.dev/cl/469956).
return nil
}
11 changes: 6 additions & 5 deletions libcontainer/standard_init_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,12 @@ func (l *linuxStandardInit) Init() error {
if err != nil {
return err
}
// exec.LookPath might return no error for an executable residing on a
// file system mounted with noexec flag, so perform this extra check
// now while we can still return a proper error.
if err := system.Eaccess(name); err != nil {
return &os.PathError{Op: "exec", Path: name, Err: err}
// exec.LookPath in Go < 1.20 might return no error for an executable
// residing on a file system mounted with noexec flag, so perform this
// extra check now while we can still return a proper error.
// TODO: remove this once go < 1.20 is not supported.
if err := eaccess(name); err != nil {
return &os.PathError{Op: "eaccess", Path: name, Err: err}
}

// Set seccomp as close to execve as possible, so as few syscalls take
Expand Down
19 changes: 0 additions & 19 deletions libcontainer/system/linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,6 @@ func (p ParentDeathSignal) Set() error {
return SetParentDeathSignal(uintptr(p))
}

// Eaccess is similar to unix.Access except for setuid/setgid binaries
// it checks against the effective (rather than real) uid and gid.
func Eaccess(path string) error {
err := unix.Faccessat2(unix.AT_FDCWD, path, unix.X_OK, unix.AT_EACCESS)
if err != unix.ENOSYS && err != unix.EPERM { //nolint:errorlint // unix errors are bare
return err
}

// Faccessat2() not available; check if we are a set[ug]id binary.
if os.Getuid() == os.Geteuid() && os.Getgid() == os.Getegid() {
// For a non-set[ug]id binary, use access(2).
return unix.Access(path, unix.X_OK)
}

// For a setuid/setgid binary, there is no fallback way
// so assume we can execute the binary.
return nil
}

func Execv(cmd string, args []string, env []string) error {
name, err := exec.LookPath(cmd)
if err != nil {
Expand Down

0 comments on commit bd6fc3e

Please sign in to comment.