fusefs: don't panic if FUSE_GETATTR fails durint VOP_GETPAGES

During VOP_GETPAGES, fusefs needs to determine the file's length, which
could require a FUSE_GETATTR operation.  If that fails, it's better to
SIGBUS than panic.

MFC after:	1 week
Sponsored by:	Axcient
Reviewed by: 	markj, kib
Differential Revision: https://reviews.freebsd.org/D31994
This commit is contained in:
Alan Somers 2021-09-16 13:19:21 -06:00
parent bcdc599dc2
commit 4f917847c9
2 changed files with 165 additions and 11 deletions

View File

@ -2199,25 +2199,23 @@ fuse_gbp_getblkno(struct vnode *vp, vm_ooffset_t off)
}
static int
fuse_gbp_getblksz(struct vnode *vp, daddr_t lbn, long *sz)
fuse_gbp_getblksz(struct vnode *vp, daddr_t lbn, long *blksz)
{
off_t filesize;
int blksz, err;
int err;
const int biosize = fuse_iosize(vp);
err = fuse_vnode_size(vp, &filesize, NULL, NULL);
KASSERT(err == 0, ("vfs_bio_getpages can't handle errors here"));
if (err)
return biosize;
if ((off_t)lbn * biosize >= filesize) {
blksz = 0;
if (err) {
/* This will turn into a SIGBUS */
return (EIO);
} else if ((off_t)lbn * biosize >= filesize) {
*blksz = 0;
} else if ((off_t)(lbn + 1) * biosize > filesize) {
blksz = filesize - (off_t)lbn *biosize;
*blksz = filesize - (off_t)lbn *biosize;
} else {
blksz = biosize;
*blksz = biosize;
}
*sz = blksz;
return (0);
}

View File

@ -40,6 +40,8 @@ extern "C" {
#include <aio.h>
#include <fcntl.h>
#include <semaphore.h>
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
}
@ -103,6 +105,33 @@ class ReadAhead: public Read,
}
};
class ReadSigbus: public Read
{
public:
static jmp_buf s_jmpbuf;
static sig_atomic_t s_si_addr;
void TearDown() {
struct sigaction sa;
bzero(&sa, sizeof(sa));
sa.sa_handler = SIG_DFL;
sigaction(SIGBUS, &sa, NULL);
FuseTest::TearDown();
}
};
static void
handle_sigbus(int signo __unused, siginfo_t *info, void *uap __unused) {
ReadSigbus::s_si_addr = (sig_atomic_t)info->si_addr;
longjmp(ReadSigbus::s_jmpbuf, 1);
}
jmp_buf ReadSigbus::s_jmpbuf;
sig_atomic_t ReadSigbus::s_si_addr;
/* AIO reads need to set the header's pid field correctly */
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */
TEST_F(AioRead, aio_read)
@ -584,6 +613,56 @@ TEST_F(Read, mmap)
leak(fd);
}
/* Read of an mmap()ed file fails */
TEST_F(ReadSigbus, mmap_eio)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
struct sigaction sa;
uint64_t ino = 42;
int fd;
ssize_t len;
size_t bufsize = strlen(CONTENTS);
void *p;
len = getpagesize();
expect_lookup(RELPATH, ino, bufsize);
expect_open(ino, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ &&
in.header.nodeid == ino &&
in.body.read.fh == Read::FH);
}, Eq(true)),
_)
).WillRepeatedly(Invoke(ReturnErrno(EIO)));
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
ASSERT_NE(MAP_FAILED, p) << strerror(errno);
/* Accessing the mapped page should return SIGBUS. */
bzero(&sa, sizeof(sa));
sa.sa_handler = SIG_DFL;
sa.sa_sigaction = handle_sigbus;
sa.sa_flags = SA_RESETHAND | SA_SIGINFO;
ASSERT_EQ(0, sigaction(SIGBUS, &sa, NULL)) << strerror(errno);
if (setjmp(ReadSigbus::s_jmpbuf) == 0) {
atomic_signal_fence(std::memory_order::memory_order_seq_cst);
volatile char x __unused = *(volatile char*)p;
FAIL() << "shouldn't get here";
}
ASSERT_EQ(p, (void*)ReadSigbus::s_si_addr);
ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
leak(fd);
}
/*
* A read via mmap comes up short, indicating that the file was truncated
* server-side.
@ -634,6 +713,83 @@ TEST_F(Read, mmap_eof)
leak(fd);
}
/*
* During VOP_GETPAGES, the FUSE server fails a FUSE_GETATTR operation. This
* almost certainly indicates a buggy FUSE server, and our goal should be not
* to panic. Instead, generate SIGBUS.
*/
TEST_F(ReadSigbus, mmap_getblksz_fail)
{
const char FULLPATH[] = "mountpoint/some_file.txt";
const char RELPATH[] = "some_file.txt";
const char *CONTENTS = "abcdefgh";
struct sigaction sa;
Sequence seq;
uint64_t ino = 42;
int fd;
ssize_t len;
size_t bufsize = strlen(CONTENTS);
mode_t mode = S_IFREG | 0644;
void *p;
len = getpagesize();
FuseTest::expect_lookup(RELPATH, ino, mode, bufsize, 1, 0);
/* Expect two GETATTR calls that succeed, followed by one that fail. */
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_GETATTR &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).Times(2)
.InSequence(seq)
.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
SET_OUT_HEADER_LEN(out, attr);
out.body.attr.attr.ino = ino;
out.body.attr.attr.mode = mode;
out.body.attr.attr.size = bufsize;
out.body.attr.attr_valid = 0;
})));
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_GETATTR &&
in.header.nodeid == ino);
}, Eq(true)),
_)
).InSequence(seq)
.WillRepeatedly(Invoke(ReturnErrno(EIO)));
expect_open(ino, 0, 1);
EXPECT_CALL(*m_mock, process(
ResultOf([=](auto in) {
return (in.header.opcode == FUSE_READ);
}, Eq(true)),
_)
).Times(0);
fd = open(FULLPATH, O_RDONLY);
ASSERT_LE(0, fd) << strerror(errno);
p = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
ASSERT_NE(MAP_FAILED, p) << strerror(errno);
/* Accessing the mapped page should return SIGBUS. */
bzero(&sa, sizeof(sa));
sa.sa_handler = SIG_DFL;
sa.sa_sigaction = handle_sigbus;
sa.sa_flags = SA_RESETHAND | SA_SIGINFO;
ASSERT_EQ(0, sigaction(SIGBUS, &sa, NULL)) << strerror(errno);
if (setjmp(ReadSigbus::s_jmpbuf) == 0) {
atomic_signal_fence(std::memory_order::memory_order_seq_cst);
volatile char x __unused = *(volatile char*)p;
FAIL() << "shouldn't get here";
}
ASSERT_EQ(p, (void*)ReadSigbus::s_si_addr);
ASSERT_EQ(0, munmap(p, len)) << strerror(errno);
leak(fd);
}
/*
* Just as when FOPEN_DIRECT_IO is used, reads with O_DIRECT should bypass
* cache and to straight to the daemon