fix FS_IOC_{GET,SET}{FLAGS,VERSION} ioctl calls

This commit is contained in:
Antonio SJ Musumeci 2019-02-11 19:58:16 -05:00
parent a80f9a0b29
commit 7bf607bb6b
6 changed files with 132 additions and 27 deletions

View file

@ -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<FileInfo*>(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<DirInfo*>(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_);
}
}