Skip to content
  • David Howells's avatar
    CRED: Fix regression in cap_capable() as shown up by sys_faccessat() [ver #2] · 14eaddc9
    David Howells authored
    Fix a regression in cap_capable() due to:
    
    	commit 5ff7711e635b32f0a1e558227d030c7e45b4a465
    	Author: David Howells <dhowells@redhat.com>
    	Date:   Wed Dec 31 02:52:28 2008 +0000
    
    	    CRED: Differentiate objective and effective subjective credentials on a task
    
    The problem is that the above patch allows a process to have two sets of
    credentials, and for the most part uses the subjective credentials when
    accessing current's creds.
    
    There is, however, one exception: cap_capable(), and thus capable(), uses the
    real/objective credentials of the target task, whether or not it is the current
    task.
    
    Ordinarily this doesn't matter, since usually the two cred pointers in current
    point to the same set of creds.  However, sys_faccessat() makes use of this
    facility to override the credentials of the calling process to make its test,
    without affecting the creds as seen from other processes.
    
    One of the things sys_faccessat() does is to make an adjustment to the
    effective capabilities mask, which cap_capable(), as it stands, then ignores.
    
    The affected capability check is in generic_permission():
    
    	if (!(mask & MAY_EXEC) || execute_ok(inode))
    		if (capable(CAP_DAC_OVERRIDE))
    			return 0;
    
    This change splits capable() from has_capability() down into the commoncap and
    SELinux code.  The capable() security op now only deals with the current
    process, and uses the current process's subjective creds.  A new security op -
    task_capable() - is introduced that can check any task's objective creds.
    
    strictly the capable() security op is superfluous with the presence of the
    task_capable() op, however it should be faster to call the capable() op since
    two fewer arguments need be passed down through the various layers.
    
    This can be tested by compiling the following program from the XFS testsuite:
    
    /*
     *  t_access_root.c - trivial test program to show permission bug.
     *
     *  Written by Michael Kerrisk - copyright ownership not pursued.
     *  Sourced from: http://linux.derkeiler.com/Mailing-Lists/Kernel/2003-10/6030.html
    
    
     */
    #include <limits.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    
    #define UID 500
    #define GID 100
    #define PERM 0
    #define TESTPATH "/tmp/t_access"
    
    static void
    errExit(char *msg)
    {
        perror(msg);
        exit(EXIT_FAILURE);
    } /* errExit */
    
    static void
    accessTest(char *file, int mask, char *mstr)
    {
        printf("access(%s, %s) returns %d\n", file, mstr, access(file, mask));
    } /* accessTest */
    
    int
    main(int argc, char *argv[])
    {
        int fd, perm, uid, gid;
        char *testpath;
        char cmd[PATH_MAX + 20];
    
        testpath = (argc > 1) ? argv[1] : TESTPATH;
        perm = (argc > 2) ? strtoul(argv[2], NULL, 8) : PERM;
        uid = (argc > 3) ? atoi(argv[3]) : UID;
        gid = (argc > 4) ? atoi(argv[4]) : GID;
    
        unlink(testpath);
    
        fd = open(testpath, O_RDWR | O_CREAT, 0);
        if (fd == -1) errExit("open");
    
        if (fchown(fd, uid, gid) == -1) errExit("fchown");
        if (fchmod(fd, perm) == -1) errExit("fchmod");
        close(fd);
    
        snprintf(cmd, sizeof(cmd), "ls -l %s", testpath);
        system(cmd);
    
        if (seteuid(uid) == -1) errExit("seteuid");
    
        accessTest(testpath, 0, "0");
        accessTest(testpath, R_OK, "R_OK");
        accessTest(testpath, W_OK, "W_OK");
        accessTest(testpath, X_OK, "X_OK");
        accessTest(testpath, R_OK | W_OK, "R_OK | W_OK");
        accessTest(testpath, R_OK | X_OK, "R_OK | X_OK");
        accessTest(testpath, W_OK | X_OK, "W_OK | X_OK");
        accessTest(testpath, R_OK | W_OK | X_OK, "R_OK | W_OK | X_OK");
    
        exit(EXIT_SUCCESS);
    } /* main */
    
    This can be run against an Ext3 filesystem as well as against an XFS
    filesystem.  If successful, it will show:
    
    	[root@andromeda src]# ./t_access_root /tmp/xxx 0 4043 4043
    	---------- 1 dhowells dhowells 0 2008-12-31 03:00 /tmp/xxx
    	access(/tmp/xxx, 0) returns 0
    	access(/tmp/xxx, R_OK) returns 0
    	access(/tmp/xxx, W_OK) returns 0
    	access(/tmp/xxx, X_OK) returns -1
    	access(/tmp/xxx, R_OK | W_OK) returns 0
    	access(/tmp/xxx, R_OK | X_OK) returns -1
    	access(/tmp/xxx, W_OK | X_OK) returns -1
    	access(/tmp/xxx, R_OK | W_OK | X_OK) returns -1
    
    If unsuccessful, it will show:
    
    	[root@andromeda src]# ./t_access_root /tmp/xxx 0 4043 4043
    	---------- 1 dhowells dhowells 0 2008-12-31 02:56 /tmp/xxx
    	access(/tmp/xxx, 0) returns 0
    	access(/tmp/xxx, R_OK) returns -1
    	access(/tmp/xxx, W_OK) returns -1
    	access(/tmp/xxx, X_OK) returns -1
    	access(/tmp/xxx, R_OK | W_OK) returns -1
    	access(/tmp/xxx, R_OK | X_OK) returns -1
    	access(/tmp/xxx, W_OK | X_OK) returns -1
    	access(/tmp/xxx, R_OK | W_OK | X_OK) returns -1
    
    I've also tested the fix with the SELinux and syscalls LTP testsuites.
    
    Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
    Signed-off-by: default avatarJames Morris <jmorris@namei.org>
    14eaddc9