aboutsummaryrefslogtreecommitdiffstats
path: root/executor
diff options
context:
space:
mode:
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