clinuxlinux-kernelsystem-callslinux-capabilities

why does creating a file fail after CAP_DAC_OVERRIDE is dropped?


When compiled with gcc and run via sudo ./a.out, the following program outputs

a.out: open 13: Permission denied

and returns EXIT_FAILURE. Why does it fail?

When run without sudo the program succeeds and creates the file test.lock. The current working directory does not seem to make any difference.

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/capability.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>

int main(void) {
        /* get capabilities */
        struct __user_cap_header_struct hdr = {
                .pid = 0,
                .version = _LINUX_CAPABILITY_VERSION_3,
        };
        struct __user_cap_data_struct data[_LINUX_CAPABILITY_U32S_3];
        if (syscall(SYS_capget, &hdr, data) == -1)
                err(EXIT_FAILURE, "SYS_capget %d", errno);

        /* update and set capabilities */
        data[0].effective &= ~(1 << CAP_DAC_OVERRIDE);
        if (syscall(SYS_capset, &hdr, data) == -1)
                err(EXIT_FAILURE, "SYS_capset %d", errno);

        /* open file */
        if (open("test.lock", O_CREAT|O_RDONLY|O_CLOEXEC, S_IRUSR|S_IWUSR) == -1)
                err(EXIT_FAILURE, "open %d", errno);

        return EXIT_SUCCESS;
}

Additional environment info:

$ uname --kernel-release
4.18.0-477.27.1.el8_8.x86_64
$ gcc --version
gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-18)
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ rpm --query glibc
glibc-2.28-225.el8_8.6.x86_64
glibc-2.28-225.el8_8.6.i686
$ cat /etc/os-release
NAME="Red Hat Enterprise Linux"
VERSION="8.9 (Ootpa)"
ID="rhel"
ID_LIKE="fedora"
VERSION_ID="8.9"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Red Hat Enterprise Linux 8.9 (Ootpa)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:redhat:enterprise_linux:8::baseos"
HOME_URL="https://www.redhat.com/"
DOCUMENTATION_URL="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8"
BUG_REPORT_URL="https://bugzilla.redhat.com/"

REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 8"
REDHAT_BUGZILLA_PRODUCT_VERSION=8.9
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
REDHAT_SUPPORT_PRODUCT_VERSION="8.9"

Solution

  • When you run the program as yourself, it does not need the CAP_DAC_OVERRIDE capability to create a file in a directory that is writable to you anyway, or to open an existing file for reading that is readable to you anyway. And indeed, unless you assigned capabilities to the binary via setcap, the process will not have that capability in the first place. This is not remarkable, nor is the fact that the program fails if you run it in a directory that is not writable to you.

    When you use sudo to run the program, it runs as user 0 (root). By virtue of that, it starts with all capabilities in its effective set. It is this, not uid 0 itself, that is the basis for the process being able to bypass discretionary access controls to, for example, create a file in a directory owned by a different user and group and that is not world-writable. Such as (very likely) your home directory and all its subdirectories. If you turn off CAP_DAC_OVERRIDE then the process can no longer do this.

    You wrote that

    The current working directory does not seem to make any difference.

    ... but I'm inclined to think the working directory in fact does make a difference. I expect that if you run the program via sudo in a directory that is writable to root without exercise of CAP_DAC_OVERRIDE then it will succeed. You shouldn't have many world-writable directories on your system, and it would be unwise to test in most directories owned by root (especially those that you can access as yourself), but /tmp would be a reasonable directory to test in. If you test both ways then be sure to remove the lock file before testing with sudo, lest the permissions on that file, rather than those on the directory, be responsible for blocking access.