]> Cypherpunks.ru repositories - gostls13.git/commitdiff
[release-branch.go1.20] syscall: Faccessat: check for CAP_DAC_OVERRIDE on Linux
authorKir Kolyshkin <kolyshkin@gmail.com>
Thu, 16 Feb 2023 00:47:40 +0000 (16:47 -0800)
committerGopher Robot <gobot@golang.org>
Tue, 28 Feb 2023 12:45:13 +0000 (12:45 +0000)
CL 416115 added using faccessat2(2) from syscall.Faccessat on Linux
(which is the only true way to implement AT_EACCESS flag handing),
if available. If not available, it uses some heuristics to mimic the
kernel behavior, mostly taken from glibc (see CL 126415).

Next, CL 414824 added using the above call (via unix.Eaccess) to
exec.LookPath in order to check if the binary can really be executed.

As a result, in a very specific scenario, described below,
syscall.Faccessat (and thus exec.LookPath) mistakenly tells that the
binary can not be executed, while in reality it can be. This makes
this bug a regression in Go 1.20.

This scenario involves all these conditions:
 - no faccessat2 support available (i.e. either Linux kernel < 5.8,
   or a seccomp set up to disable faccessat2);
 - the current user is not root (i.e. geteuid() != 0);
 - CAP_DAC_OVERRIDE capability is set for the current process;
 - the file to be executed does not have executable permission
   bit set for either the current EUID or EGID;
 - the file to be executed have at least one executable bit set.

Unfortunately, this set of conditions was observed in the wild -- a
container run as a non-root user with the binary file owned by root with
executable permission set for a user only [1]. Essentially it means it
is not as rare as it may seem.

Now, CAP_DAC_OVERRIDE essentially makes the kernel bypass most of the
checks, so execve(2) and friends work the same was as for root user,
i.e. if at least one executable bit it set, the permission to execute
is granted (see generic_permission() function in the Linux kernel).

Modify the code to check for CAP_DAC_OVERRIDE and mimic the kernel
behavior for permission checks.

[1] https://github.com/opencontainers/runc/issues/3715

For #58552.
Fixes #58624.

Change-Id: I82a7e757ab3fd3d0193690a65c3b48fee46ff067
Reviewed-on: https://go-review.googlesource.com/c/go/+/468735
Reviewed-by: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
(cherry picked from commit 031401a7905a38498fc399fc10cd0c1e885f7fc9)
Reviewed-on: https://go-review.googlesource.com/c/go/+/469956
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
Run-TryBot: Than McIntosh <thanm@google.com>
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
src/syscall/syscall_linux.go

index d4cc34bdee0b2f1c5c9f128e76e2a14fd65838f1..f337388a749e372113b9cd84e23c8c8e8016dd7d 100644 (file)
@@ -138,6 +138,16 @@ func isGroupMember(gid int) bool {
        return false
 }
 
+func isCapDacOverrideSet() bool {
+       const _CAP_DAC_OVERRIDE = 1
+       var c caps
+       c.hdr.version = _LINUX_CAPABILITY_VERSION_3
+
+       _, _, err := RawSyscall(SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(&c.data[0])), 0)
+
+       return err == 0 && c.data[0].effective&capToMask(_CAP_DAC_OVERRIDE) != 0
+}
+
 //sys  faccessat(dirfd int, path string, mode uint32) (err error)
 //sys  faccessat2(dirfd int, path string, mode uint32, flags int) (err error) = _SYS_faccessat2
 
@@ -179,9 +189,16 @@ func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) {
                return nil
        }
 
+       // Fallback to checking permission bits.
        var uid int
        if flags&_AT_EACCESS != 0 {
                uid = Geteuid()
+               if uid != 0 && isCapDacOverrideSet() {
+                       // If CAP_DAC_OVERRIDE is set, file access check is
+                       // done by the kernel in the same way as for root
+                       // (see generic_permission() in the Linux sources).
+                       uid = 0
+               }
        } else {
                uid = Getuid()
        }