FUSE filesystems look at saved UID/GID?

While attempting to create a program that reads some configuration before launching programs as a normal user and then as the root user, I noticed this odd behavior. I can’t seem to find mention of it anywhere else. Normal filesystems use the effective UID/GID for access checks, but it looks like FUSE seem to check all three of the effective, real, and saved(!!) UID/GID for access. I had initially just dropped the effective uid so that I could recover it later, but this kept me getting permissions errors until I realized what was going on.

Why is this this case? Why does FUSE care about the saved uid/gid?

(I’m aware I can set allow_root on FUSE and avoid this, that isn’t what this question is about)

Example C code to demonstrate:

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#define measure() getresuid(&ruid, &euid, &suid);   getresgid(&rgid, &egid, &sgid); printf("UID: %4d, %4d, %4d. GID: %4d, %4d, %4d tt", ruid, euid, suid, rgid, egid, sgid); fflush(stdout)

#define set(r,e,s) if (setresuid(0,0,0 ) != 0) return 1; if (setresgid(r,e,s ) != 0) return 1; if (setresuid(r, e, s) != 0) return 1;

#define attempt(r,e,s) set(r,e,s); measure(); test(argv[1])

void test(char* arg)
{
    struct stat sb;
    if (stat(arg, &sb) == -1)
        perror("fail");
    else
        printf("Successn");
}

int main(int argc, char *argv[])
{
    uid_t ruid, euid, suid; gid_t rgid, egid, sgid;

    measure();
    printf("nn");

    attempt(1000,0,0); // Expect: Fail. Actual: Fail
    attempt(0, 1000,0); // Expect: ok. Actual: Fail
    attempt(0, 0, 1000); // Expect: Fail. Actual: Fail
    attempt(1000,1000,0); // Expect: ok. Actual: Fail
    attempt(1000,0,1000); // Expect: Fail. Actual: Fail
    attempt(0,1000,1000); // Expect: ok. Actual: Fail
    attempt(1000,1000,1000); // Expect: ok. Actual: ok

    return 0;
}

Output:

$ sshfs some-other-machine:/ /tmp/testit # I think any FUSE filesystem should "work" 
$ gcc test.c -o test
$ sudo ./test /tmp/testit
UID:    0,    0,    0. GID:    0,    0,    0 

UID: 1000,    0,    0. GID: 1000,    0,    0            fail: Permission denied
UID:    0, 1000,    0. GID:    0, 1000,    0            fail: Permission denied
UID:    0,    0, 1000. GID:    0,    0, 1000            fail: Permission denied
UID: 1000, 1000,    0. GID: 1000, 1000,    0            fail: Permission denied
UID: 1000,    0, 1000. GID: 1000,    0, 1000            fail: Permission denied
UID:    0, 1000, 1000. GID:    0, 1000, 1000            fail: Permission denied
UID: 1000, 1000, 1000. GID: 1000, 1000, 1000            Success
$ 
Asked By: byteit101

||

As you have noticed, without the allow_root/allow_other options, other processes are not allowed to access the filesystem. This is not meant to protect your filesystem, but to protect the other processes. For this reason, if the accessing process has a shred of another identity, the access can’t be allowed.

That’s the relevant code in the kernel for this behavior (fs/fuse/dir.c):

/*
 * Calling into a user-controlled filesystem gives the filesystem
 * daemon ptrace-like capabilities over the current process.  This
 * means, that the filesystem daemon is able to record the exact
 * filesystem operations performed, and can also control the behavior
 * of the requester process in otherwise impossible ways.  For example
 * it can delay the operation for arbitrary length of time allowing
 * DoS against the requester.
 *
 * For this reason only those processes can call into the filesystem,
 * for which the owner of the mount has ptrace privilege.  This
 * excludes processes started by other users, suid or sgid processes.
 */
int fuse_allow_current_process(struct fuse_conn *fc)
{
    const struct cred *cred;

    if (fc->allow_other)
        return current_in_userns(fc->user_ns);

    cred = current_cred();
    if (uid_eq(cred->euid, fc->user_id) &&
        uid_eq(cred->suid, fc->user_id) &&
        uid_eq(cred->uid,  fc->user_id) &&
        gid_eq(cred->egid, fc->group_id) &&
        gid_eq(cred->sgid, fc->group_id) &&
        gid_eq(cred->gid,  fc->group_id))
        return 1;

    return 0;
}
Answered By: ariel marcovitch