diff --git a/libfuse/include/fuse.h b/libfuse/include/fuse.h index d65db8b6..0b4d7f22 100644 --- a/libfuse/include/fuse.h +++ b/libfuse/include/fuse.h @@ -505,8 +505,13 @@ struct fuse_operations { * * Introduced in version 2.8 */ - int (*ioctl) (const char *, int cmd, void *arg, - struct fuse_file_info *, unsigned int flags, void *data); + int (*ioctl) (const char *fusepath, + int cmd, + void *arg, + struct fuse_file_info *ffi, + unsigned int flags, + void *data, + uint32_t *out_bufsz); /** * Poll for IO readiness events @@ -884,7 +889,8 @@ int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize, uint64_t *idx); int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd, void *arg, - struct fuse_file_info *fi, unsigned int flags, void *data); + struct fuse_file_info *fi, unsigned int flags, + void *data, uint32_t *out_bufsz); int fuse_fs_poll(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi, struct fuse_pollhandle *ph, unsigned *reventsp); diff --git a/libfuse/lib/fuse.c b/libfuse/lib/fuse.c index 91df7756..f48f5341 100644 --- a/libfuse/lib/fuse.c +++ b/libfuse/lib/fuse.c @@ -2202,7 +2202,8 @@ int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name) } int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd, void *arg, - struct fuse_file_info *fi, unsigned int flags, void *data) + struct fuse_file_info *fi, unsigned int flags, + void *data, uint32_t *out_size) { fuse_get_context()->private_data = fs->user_data; if (fs->op.ioctl) { @@ -2210,7 +2211,7 @@ int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd, void *arg, fprintf(stderr, "ioctl[%llu] 0x%x flags: 0x%x\n", (unsigned long long) fi->fh, cmd, flags); - return fs->op.ioctl(path, cmd, arg, fi, flags, data); + return fs->op.ioctl(path, cmd, arg, fi, flags, data, out_size); } else return -ENOSYS; } @@ -3896,13 +3897,14 @@ static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize, static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, int cmd, void *arg, struct fuse_file_info *llfi, unsigned int flags, const void *in_buf, size_t in_bufsz, - size_t out_bufsz) + size_t out_bufsz_) { struct fuse *f = req_fuse_prepare(req); struct fuse_intr_data d; struct fuse_file_info fi; char *path, *out_buf = NULL; int err; + uint32_t out_bufsz = out_bufsz_; err = -EPERM; if (flags & FUSE_IOCTL_UNRESTRICTED) @@ -3931,7 +3933,7 @@ static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, int cmd, void *arg, fuse_prepare_interrupt(f, req, &d); err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags, - out_buf ?: (void *)in_buf); + out_buf ?: (void *)in_buf, &out_bufsz); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); diff --git a/src/endian.hpp b/src/endian.hpp new file mode 100644 index 00000000..d294dba8 --- /dev/null +++ b/src/endian.hpp @@ -0,0 +1,32 @@ +/* + ISC License + + Copyright (c) 2019, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#pragma once + +namespace endian +{ + static + inline + bool + is_big(void) + { + const union { uint32_t i; char c[4]; } u = { 0x01000000 }; + + return u.c[0]; + } +}; diff --git a/src/fs_base_ioctl.hpp b/src/fs_base_ioctl.hpp index ba322fec..cdb74e2b 100644 --- a/src/fs_base_ioctl.hpp +++ b/src/fs_base_ioctl.hpp @@ -22,6 +22,15 @@ namespace fs { + static + inline + int + ioctl(const int fd_, + const unsigned long request_) + { + return ::ioctl(fd_,request_); + } + static inline int diff --git a/src/fuse_ioctl.cpp b/src/fuse_ioctl.cpp index 084a7b14..cc049133 100644 --- a/src/fuse_ioctl.cpp +++ b/src/fuse_ioctl.cpp @@ -16,6 +16,7 @@ #include "config.hpp" #include "dirinfo.hpp" +#include "endian.hpp" #include "errno.hpp" #include "fileinfo.hpp" #include "fs_base_close.hpp" @@ -35,30 +36,81 @@ using std::string; using std::vector; +#ifndef FS_IOC_GETFLAGS +# define FS_IOC_GETFLAGS _IOR('f',1,long) +#endif + +#ifndef FS_IOC_SETFLAGS +# define FS_IOC_SETFLAGS _IOW('f',2,long) +#endif + +#ifndef FS_IOC_GETVERSION +# define FS_IOC_GETVERSION _IOR('v',1,long) +#endif + +#ifndef FS_IOC_SETVERSION +# define FS_IOC_SETVERSION _IOW('v',2,long) +#endif + +/* + There is a bug with FUSE and these ioctl commands. The regular + libfuse high level API assumes the output buffer size based on the + command and gives no control over this. FS_IOC_GETFLAGS and + FS_IOC_SETFLAGS however are defined as `long` when in fact it is an + `int`. On 64bit systems where long is 8 bytes this can lead to + libfuse telling the kernel to write 8 bytes and if the user only + allocated an integer then it will overwrite the 4 bytes after the + variable which could result in data corruption and/or crashes. + + I've modified the API to allow changing of the output buffer + size. This fixes the issue on little endian systems because the + lower 4 bytes are the same regardless of what the user + allocated. However, on big endian systems thats not the case and it + is not possible to safely handle the situation. + + https://lwn.net/Articles/575846/ + */ + namespace l { static int - ioctl(const int fd_, - const unsigned long cmd_, - void *data_) + ioctl(const int fd_, + const int cmd_, + void *data_, + uint32_t *out_bufsz_) { int rv; - rv = fs::ioctl(fd_,cmd_,data_); + switch(cmd_) + { + case FS_IOC_GETFLAGS: + case FS_IOC_SETFLAGS: + case FS_IOC_GETVERSION: + case FS_IOC_SETVERSION: + if(endian::is_big() && (sizeof(long) != sizeof(int))) + return -ENOTTY; + *out_bufsz_ = 4; + break; + } + + rv = ((_IOC_DIR(cmd_) == _IOC_NONE) ? + fs::ioctl(fd_,cmd_) : + fs::ioctl(fd_,cmd_,data_)); return ((rv == -1) ? -errno : rv); } static int - ioctl_file(fuse_file_info *ffi_, - const unsigned long cmd_, - void *data_) + ioctl_file(fuse_file_info *ffi_, + const int cmd_, + void *data_, + uint32_t *out_bufsz_) { FileInfo *fi = reinterpret_cast(ffi_->fh); - return l::ioctl(fi->fd,cmd_,data_); + return l::ioctl(fi->fd,cmd_,data_,out_bufsz_); } @@ -72,8 +124,9 @@ namespace l const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_, - const unsigned long cmd_, - void *data_) + const int cmd_, + void *data_, + uint32_t *out_bufsz_) { int fd; int rv; @@ -91,7 +144,7 @@ namespace l if(fd == -1) return -errno; - rv = l::ioctl(fd,cmd_,data_); + rv = l::ioctl(fd,cmd_,data_,out_bufsz_); fs::close(fd); @@ -100,9 +153,10 @@ namespace l static int - ioctl_dir(fuse_file_info *ffi_, - const unsigned long cmd_, - void *data_) + ioctl_dir(fuse_file_info *ffi_, + const int cmd_, + void *data_, + uint32_t *out_bufsz_) { DirInfo *di = reinterpret_cast(ffi_->fh); const fuse_context *fc = fuse_get_context(); @@ -115,7 +169,8 @@ namespace l config.minfreespace, di->fusepath.c_str(), cmd_, - data_); + data_, + out_bufsz_); } } @@ -127,12 +182,12 @@ namespace FUSE void *arg_, fuse_file_info *ffi_, unsigned int flags_, - void *data_) + void *data_, + uint32_t *out_bufsz_) { if(flags_ & FUSE_IOCTL_DIR) - return l::ioctl_dir(ffi_,cmd_,data_); - + return l::ioctl_dir(ffi_,cmd_,data_,out_bufsz_); - return l::ioctl_file(ffi_,cmd_,data_); + return l::ioctl_file(ffi_,cmd_,data_,out_bufsz_); } } diff --git a/src/fuse_ioctl.hpp b/src/fuse_ioctl.hpp index ec540cb8..224ebfc3 100644 --- a/src/fuse_ioctl.hpp +++ b/src/fuse_ioctl.hpp @@ -26,5 +26,6 @@ namespace FUSE void *arg_, fuse_file_info *ffi_, unsigned int flags_, - void *data_); + void *data_, + uint32_t *out_bufsz_); }