aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--executor/common_linux.h181
-rw-r--r--pkg/csource/generated.go168
-rw-r--r--pkg/host/syscalls_linux.go15
-rw-r--r--sys/linux/fs_fuse.txt37
-rw-r--r--sys/linux/test/syz_fuse_handle_req8
5 files changed, 405 insertions, 4 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
diff --git a/pkg/csource/generated.go b/pkg/csource/generated.go
index 2da36802c..c28b1ccaf 100644
--- a/pkg/csource/generated.go
+++ b/pkg/csource/generated.go
@@ -8926,6 +8926,174 @@ static void setup_usb()
#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 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;
+};
+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;
+}
+static volatile long syz_fuse_handle_req(volatile long a0,
+ volatile long a1,
+ volatile long a2,
+ volatile long a3)
+{
+ 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;
+ }
+ 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:
+ 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:
+ 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
+
#elif GOOS_test
#include <stdlib.h>
diff --git a/pkg/host/syscalls_linux.go b/pkg/host/syscalls_linux.go
index 06e9a261f..192e88100 100644
--- a/pkg/host/syscalls_linux.go
+++ b/pkg/host/syscalls_linux.go
@@ -250,6 +250,16 @@ func isBtfVmlinuxSupported(c *prog.Syscall, target *prog.Target, sandbox string)
return onlySandboxNone(sandbox)
}
+func isSyzFuseSupported(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) {
+ if ok, reason := isSupportedFilesystem("fuse"); !ok {
+ return ok, reason
+ }
+ if ok, reason := onlySandboxNoneOrNamespace(sandbox); !ok {
+ return false, reason
+ }
+ return true, ""
+}
+
var syzkallSupport = map[string]func(*prog.Syscall, *prog.Target, string) (bool, string){
"syz_open_dev": isSyzOpenDevSupported,
"syz_open_procfs": alwaysSupported,
@@ -274,8 +284,9 @@ var syzkallSupport = map[string]func(*prog.Syscall, *prog.Target, string) (bool,
"syz_io_uring_setup": isSyzIoUringSupported,
// syz_memcpy_off is only used for io_uring descriptions, thus, enable it
// only if io_uring syscalls are enabled.
- "syz_memcpy_off": isSyzIoUringSupported,
- "syz_btf_id_by_name": isBtfVmlinuxSupported,
+ "syz_memcpy_off": isSyzIoUringSupported,
+ "syz_btf_id_by_name": isBtfVmlinuxSupported,
+ "syz_fuse_handle_req": isSyzFuseSupported,
}
func isSupportedSyzkall(c *prog.Syscall, target *prog.Target, sandbox string) (bool, string) {
diff --git a/sys/linux/fs_fuse.txt b/sys/linux/fs_fuse.txt
index 5d3efa8a6..d7d887a9e 100644
--- a/sys/linux/fs_fuse.txt
+++ b/sys/linux/fs_fuse.txt
@@ -41,6 +41,8 @@ write$FUSE_NOTIFY_STORE(fd fd_fuse, arg ptr[in, fuse_notify[FUSE_NOTIFY_STORE, f
write$FUSE_NOTIFY_RETRIEVE(fd fd_fuse, arg ptr[in, fuse_notify[FUSE_NOTIFY_RETRIEVE, fuse_notify_retrieve_out]], len bytesize[arg])
write$FUSE_NOTIFY_DELETE(fd fd_fuse, arg ptr[in, fuse_notify[FUSE_NOTIFY_DELETE, fuse_notify_delete_out]], len bytesize[arg])
+syz_fuse_handle_req(fd fd_fuse, buf ptr[in, read_buffer], len bytesize[buf], res ptr[in, syz_fuse_req_out])
+
type fuse_ino int64[0:6]
type fuse_gen int64[0:3]
@@ -62,13 +64,20 @@ type fuse_in[PAYLOAD] {
payload PAYLOAD
} [packed]
-type fuse_out[PAYLOAD] {
+type fuse_out_t[UNIQUE, PAYLOAD] {
len len[parent, int32]
err flags[fuse_errors, int32]
- unique fuse_unique
+ unique UNIQUE
payload PAYLOAD
} [packed]
+type fuse_out[PAYLOAD] fuse_out_t[fuse_unique, PAYLOAD]
+# This response header is used by syz_fuse_handle_req(). It defines the FUSE unique
+# identifier as int64 because syz_fuse_handle_req() retrieves it internally
+# (defining it as a resource would create a dependency with read$FUSE() which is
+# incorrect).
+type syz_fuse_out[PAYLOAD] fuse_out_t[int64, PAYLOAD]
+
# -ENOENT, -EAGAIN, -ENOSYS
fuse_errors = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -11, -38
@@ -154,6 +163,10 @@ fuse_write_out {
padding const[0, int32]
}
+fuse_read_out {
+ content string
+}
+
fuse_open_out {
fh const[0, int64]
open_flags flags[fuse_open_flags, int32]
@@ -278,3 +291,23 @@ fuse_opts [
fuse_mode = S_IFREG, S_IFCHR, S_IFBLK, S_IFIFO, S_IFSOCK, S_IFLNK, S_IFDIR
fuse_block_sizes = 512, 1024, 2048, 4096
+
+# Used by syz_fuse_handle_req() to mimic a FUSE daemon.
+syz_fuse_req_out {
+ init ptr[in, syz_fuse_out[fuse_init_out]]
+ lseek ptr[in, syz_fuse_out[fuse_lseek_out]]
+ bmap ptr[in, syz_fuse_out[fuse_bmap_out]]
+ poll ptr[in, syz_fuse_out[fuse_poll_out]]
+ getxattr ptr[in, syz_fuse_out[fuse_getxattr_out]]
+ lk ptr[in, syz_fuse_out[fuse_lk_out]]
+ statfs ptr[in, syz_fuse_out[fuse_statfs_out]]
+ write ptr[in, syz_fuse_out[fuse_write_out]]
+ read ptr[in, syz_fuse_out[fuse_read_out]]
+ open ptr[in, syz_fuse_out[fuse_open_out]]
+ attr ptr[in, syz_fuse_out[fuse_attr_out]]
+ entry ptr[in, syz_fuse_out[fuse_entry_out]]
+ dirent ptr[in, syz_fuse_out[array[fuse_dirent]]]
+ direntplus ptr[in, syz_fuse_out[array[fuse_direntplus]]]
+ create_open ptr[in, syz_fuse_out[fuse_create_open_out]]
+ ioctl ptr[in, syz_fuse_out[fuse_ioctl_out]]
+}
diff --git a/sys/linux/test/syz_fuse_handle_req b/sys/linux/test/syz_fuse_handle_req
new file mode 100644
index 000000000..a26592b9d
--- /dev/null
+++ b/sys/linux/test/syz_fuse_handle_req
@@ -0,0 +1,8 @@
+mkdirat(0xffffffffffffff9c, &AUTO='./file0\x00', 0x0)
+r0 = openat$fuse(0xffffffffffffff9c, &AUTO='/dev/fuse\x00', 0x2, 0x0)
+mount$fuse(0x0, &AUTO='./file0\x00', &AUTO='fuse\x00', 0x0, &AUTO={{'fd', 0x3d, r0}, 0x2c, {'rootmode', 0x3d, 0x4000}, 0x2c, {'user_id', 0x3d, 0x0}, 0x2c, {'group_id', 0x3d, 0x0}, 0x2c, {[], [], 0x0}})
+r1 = openat$dir(0xffffffffffffff9c, &AUTO='./file0\x00', 0x0, 0x0)
+# FUSE_INIT
+syz_fuse_handle_req(r0, &AUTO=""/8192, AUTO, &AUTO={&AUTO={AUTO, 0x0, 0x0, {AUTO, AUTO, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, AUTO, AUTO, [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]}}, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0})
+# FUSE_OPENDIR
+syz_fuse_handle_req(r0, &AUTO=""/8192, AUTO, &AUTO={0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, &AUTO={AUTO, 0x0, 0x0, {0x0, 0x0, 0x0}}, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0})