diff options
| -rw-r--r-- | executor/common_linux.h | 181 | ||||
| -rw-r--r-- | pkg/csource/generated.go | 168 | ||||
| -rw-r--r-- | pkg/host/syscalls_linux.go | 15 | ||||
| -rw-r--r-- | sys/linux/fs_fuse.txt | 37 | ||||
| -rw-r--r-- | sys/linux/test/syz_fuse_handle_req | 8 |
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}) |
