DragonFlyBSD Kernel Audit
DF-0912 / fix.diff
← back to finding ↓ download raw
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, "");