aboutsummaryrefslogtreecommitdiffstats
path: root/executor
diff options
context:
space:
mode:
authorStefano Duo <stefanoduo@google.com>2020-07-22 13:47:01 +0000
committerDmitry Vyukov <dvyukov@google.com>2020-08-14 18:55:11 +0200
commit19b6584f719a7fd28b8d0851c4e9b5cb20be35df (patch)
tree761dbdd47e30aa1182bdce59220df778c9d808dd /executor
parent3d9b8afae8832eb188d0ae71e1f73383f12d3944 (diff)
executor/common_linux.h: add syz_fuse_handle_req()
At the moment syzkaller is able to respond to FUSE with a syntactically correct response using the specific write$FUSE_*() syscalls, but most of the times these responses are not related to the type of request that was received. With this pseudo-syscall we are able to provide the correct response type while still allowing the fuzzer to fuzz its content. This is done by requiring each type of response as an input parameter and then choosing the correct one based on the request opcode. Notice that the fuzzer is still free to mix write$FUSE_*() and syz_fuse_handle_req() syscalls, so it is not losing any degree of freedom. syz_fuse_handle_req() retrieves the FUSE request and resource fuse_unique internally (by performing a read() on the /dev/fuse file descriptor provided as input). For this reason, a new template argument has been added to fuse_out (renamed to _fuse_out) so that the unique field can be both an int64 (used by syz_fuse_handle_req()) and a fuse_unique resource (used by the write$FUSE_*() syscalls) without any code duplication.
Diffstat (limited to 'executor')
-rw-r--r--executor/common_linux.h181
1 files changed, 181 insertions, 0 deletions
diff --git a/executor/common_linux.h b/executor/common_linux.h
index d0a5c0741..6b4d96c25 100644
--- a/executor/common_linux.h
+++ b/executor/common_linux.h
@@ -4259,3 +4259,184 @@ static void setup_usb()
// The macro is used in generated C code.
#define CAST(f) ({void* p = (void*)f; p; })
#endif
+
+#if SYZ_EXECUTOR || __NR_syz_fuse_handle_req
+#include <fcntl.h>
+#include <linux/fuse.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+// Struct shared between syz_fuse_handle_req() and the fuzzer. Used to provide
+// a fuzzed response for each request type.
+struct syz_fuse_req_out {
+ struct fuse_out_header* init;
+ struct fuse_out_header* lseek;
+ struct fuse_out_header* bmap;
+ struct fuse_out_header* poll;
+ struct fuse_out_header* getxattr;
+ struct fuse_out_header* lk;
+ struct fuse_out_header* statfs;
+ struct fuse_out_header* write;
+ struct fuse_out_header* read;
+ struct fuse_out_header* open;
+ struct fuse_out_header* attr;
+ struct fuse_out_header* entry;
+ struct fuse_out_header* dirent;
+ struct fuse_out_header* direntplus;
+ struct fuse_out_header* create_open;
+ struct fuse_out_header* ioctl;
+};
+
+// Link the reponse to the request and send it to /dev/fuse.
+static int
+fuse_send_response(int fd,
+ const struct fuse_in_header* in_hdr,
+ struct fuse_out_header* out_hdr)
+{
+ if (!out_hdr) {
+ debug("fuse_send_response: received a NULL out_hdr\n");
+ return -1;
+ }
+
+ out_hdr->unique = in_hdr->unique;
+ if (write(fd, out_hdr, out_hdr->len) == -1) {
+ debug("fuse_send_response > write failed: %d\n", errno);
+ return -1;
+ }
+
+ return 0;
+}
+
+// This function reads a request from /dev/fuse and tries to pick the correct
+// response from the input struct syz_fuse_req_out (a3). Responses are still
+// generated by the fuzzer.
+static volatile long syz_fuse_handle_req(volatile long a0, // /dev/fuse fd.
+ volatile long a1, // Read buffer.
+ volatile long a2, // Buffer len.
+ volatile long a3) // syz_fuse_req_out.
+{
+ struct syz_fuse_req_out* req_out = (struct syz_fuse_req_out*)a3;
+ struct fuse_out_header* out_hdr = NULL;
+ char* buf = (char*)a1;
+ int buf_len = (int)a2;
+ int fd = (int)a0;
+
+ if (!req_out) {
+ debug("syz_fuse_handle_req: received a NULL syz_fuse_req_out\n");
+ return -1;
+ }
+ if (buf_len < FUSE_MIN_READ_BUFFER) {
+ debug("FUSE requires the read buffer to be at least %u\n", FUSE_MIN_READ_BUFFER);
+ return -1;
+ }
+
+ int ret = read(fd, buf, buf_len);
+ if (ret == -1) {
+ debug("syz_fuse_handle_req > read failed: %d\n", errno);
+ return -1;
+ }
+ // Safe to do because ret > 0 (!= -1) and < FUSE_MIN_READ_BUFFER (= 8192).
+ if ((size_t)ret < sizeof(struct fuse_in_header)) {
+ debug("syz_fuse_handle_req: received a truncated FUSE header\n");
+ return -1;
+ }
+
+ const struct fuse_in_header* in_hdr = (const struct fuse_in_header*)buf;
+ debug("syz_fuse_handle_req: received opcode %d\n", in_hdr->opcode);
+ if (in_hdr->len > (uint32)ret) {
+ debug("syz_fuse_handle_req: received a truncated message\n");
+ return -1;
+ }
+
+ switch (in_hdr->opcode) {
+ case FUSE_GETATTR:
+ case FUSE_SETATTR:
+ out_hdr = req_out->attr;
+ break;
+ case FUSE_LOOKUP:
+ case FUSE_SYMLINK:
+ case FUSE_LINK:
+ case FUSE_MKNOD:
+ case FUSE_MKDIR:
+ out_hdr = req_out->entry;
+ break;
+ case FUSE_OPEN:
+ case FUSE_OPENDIR:
+ out_hdr = req_out->open;
+ break;
+ case FUSE_STATFS:
+ out_hdr = req_out->statfs;
+ break;
+ case FUSE_RMDIR:
+ case FUSE_RENAME:
+ case FUSE_RENAME2:
+ case FUSE_FALLOCATE:
+ case FUSE_SETXATTR:
+ case FUSE_REMOVEXATTR:
+ case FUSE_FSYNCDIR:
+ case FUSE_FSYNC:
+ case FUSE_SETLKW:
+ case FUSE_SETLK:
+ case FUSE_ACCESS:
+ case FUSE_FLUSH:
+ case FUSE_RELEASE:
+ case FUSE_RELEASEDIR:
+ // These opcodes do not have any reply data. Hence, we pick
+ // another response and only use the shared header.
+ out_hdr = req_out->init;
+ if (!out_hdr) {
+ debug("syz_fuse_handle_req: received a NULL out_hdr\n");
+ return -1;
+ }
+ out_hdr->len = sizeof(struct fuse_out_header);
+ break;
+ case FUSE_READ:
+ out_hdr = req_out->read;
+ break;
+ case FUSE_READDIR:
+ out_hdr = req_out->dirent;
+ break;
+ case FUSE_READDIRPLUS:
+ out_hdr = req_out->direntplus;
+ break;
+ case FUSE_INIT:
+ out_hdr = req_out->init;
+ break;
+ case FUSE_LSEEK:
+ out_hdr = req_out->lseek;
+ break;
+ case FUSE_GETLK:
+ out_hdr = req_out->lk;
+ break;
+ case FUSE_BMAP:
+ out_hdr = req_out->bmap;
+ break;
+ case FUSE_POLL:
+ out_hdr = req_out->poll;
+ break;
+ case FUSE_GETXATTR:
+ case FUSE_LISTXATTR:
+ out_hdr = req_out->getxattr;
+ break;
+ case FUSE_WRITE:
+ out_hdr = req_out->write;
+ break;
+ case FUSE_FORGET:
+ // FUSE_FORGET expects no reply.
+ return 0;
+ case FUSE_CREATE:
+ out_hdr = req_out->create_open;
+ break;
+ case FUSE_IOCTL:
+ out_hdr = req_out->ioctl;
+ break;
+ default:
+ debug("syz_fuse_handle_req: unknown FUSE opcode\n");
+ return -1;
+ }
+
+ return fuse_send_response(fd, in_hdr, out_hdr);
+}
+#endif