DF-0912 / fix.diff
diff --git a/sys/vfs/fuse/fuse.h b/sys/vfs/fuse/fuse.h --- a/sys/vfs/fuse/fuse.h +++ b/sys/vfs/fuse/fuse.h @@ -179,7 +179,7 @@ void fuse_fill_in_header(struct fuse_in_header*, uint32_t, uint32_t, uint64_t, uint64_t, uint32_t, uint32_t, uint32_t); int fuse_forget_node(struct fuse_mount*, uint64_t, uint64_t, struct ucred*); -int fuse_audit_length(struct fuse_in_header*, struct fuse_out_header*); +int fuse_audit_length(struct fuse_in_header*, struct fuse_out_header*, size_t); const char *fuse_get_ops(int); void fuse_io_thread(void *arg); diff --git a/sys/vfs/fuse/fuse_util.c b/sys/vfs/fuse/fuse_util.c --- a/sys/vfs/fuse/fuse_util.c +++ b/sys/vfs/fuse/fuse_util.c @@ -83,10 +83,23 @@ * Ignore FUSE_COMPAT_XXX which seem to exist for backward compatibility * for ancient versions of FUSE protocol. */ +/* + * Validate the reply the daemon actually wrote (actual_len = fip->reply.len, + * i.e. the real uio_resid of the /dev/fuse write), NOT the daemon-CLAIMED + * ohd->len field. Using ohd->len allowed a malicious daemon to write a short + * buffer while claiming a struct-sized length, passing this audit and then + * causing the consumers (fuse_mount/fuse_statfs/fuse_statvfs, which read + * fixed-size structs via fuse_out_data()) to read past the allocation. + */ int -fuse_audit_length(struct fuse_in_header *ihd, struct fuse_out_header *ohd) +fuse_audit_length(struct fuse_in_header *ihd, struct fuse_out_header *ohd, + size_t actual_len) { - size_t len = ohd->len - sizeof(struct fuse_out_header); + size_t len; + + if (actual_len < sizeof(struct fuse_out_header)) + return -1; + len = actual_len - sizeof(struct fuse_out_header); bool res; switch (ihd->opcode) { diff --git a/sys/vfs/fuse/fuse_device.c b/sys/vfs/fuse/fuse_device.c --- a/sys/vfs/fuse/fuse_device.c +++ b/sys/vfs/fuse/fuse_device.c @@ -205,12 +205,20 @@ fip->reply = fb; ihd = fuse_in(fip); - /* Non zero ohd->error is not /dev/fuse write error. */ + /* Non zero ohd->error is not /dev/fuse write error. + * + * fuse_audit_length() validates the ACTUAL written length (fb.len), + * not the daemon-claimed ohd->len; otherwise a short write with a + * forged ohd->len passes the audit and the consumers (which read + * fixed-size structs via fuse_out_data()) OOB-read past the reply + * buffer. On audit failure we set ohd->error so the kernel-side + * consumer aborts instead of dereferencing the too-short buffer. */ if (ohd->error == -ENOSYS) { fuse_set_nosys(fmp, ihd->opcode); fuse_dbgipc(fip, ohd->error, "ENOSYS"); - } else if (!ohd->error && fuse_audit_length(ihd, ohd)) { + } else if (!ohd->error && fuse_audit_length(ihd, ohd, fb.len)) { error = EPROTO; + ohd->error = -EPROTO; fuse_dbgipc(fip, error, "audit"); } else fuse_dbgipc(fip, 0, ""); |