Do a basic sanity check that dirents returned via fuse are kind of sane.
authorclaudio <claudio@openbsd.org>
Thu, 12 Sep 2024 09:10:46 +0000 (09:10 +0000)
committerclaudio <claudio@openbsd.org>
Thu, 12 Sep 2024 09:10:46 +0000 (09:10 +0000)
Ensure that file names passed back by readdir do not include a '/'
character. The '/' char is the path separator and is not allowed in
any filename. On top of this also check that d_reclen and d_namlen
are kind of sane and zero out the padding bytes after d_name.

OK beck@

sys/miscfs/fuse/fuse_vnops.c

index 8c38ab5..c1592e7 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: fuse_vnops.c,v 1.69 2024/05/13 11:17:40 semarie Exp $ */
+/* $OpenBSD: fuse_vnops.c,v 1.70 2024/09/12 09:10:46 claudio Exp $ */
 /*
  * Copyright (c) 2012-2013 Sylvestre Gallon <ccna.syl@gmail.com>
  *
@@ -752,6 +752,8 @@ fusefs_readdir(void *v)
        struct fusefs_node *ip;
        struct fusefs_mnt *fmp;
        struct fusebuf *fbuf;
+       struct dirent *dp;
+       char *edp;
        struct vnode *vp;
        struct proc *p;
        struct uio *uio;
@@ -806,6 +808,35 @@ fusefs_readdir(void *v)
                        break;
                }
 
+               /* validate the returned dirents */
+               dp = (struct dirent *)fbuf->fb_dat;
+               edp = fbuf->fb_dat + fbuf->fb_len;
+               while ((char *)dp < edp) {
+                       if ((char *)dp + offsetof(struct dirent, d_name) >= edp
+                           || dp->d_reclen <= offsetof(struct dirent, d_name)
+                           || (char *)dp + dp->d_reclen > edp) {
+                               error = EINVAL;
+                               break;
+                       }
+                       if (dp->d_namlen + offsetof(struct dirent, d_name) >=
+                           dp->d_reclen) {
+                               error = EINVAL;
+                               break;
+                       }
+                       memset(dp->d_name + dp->d_namlen, 0, dp->d_reclen -
+                           dp->d_namlen - offsetof(struct dirent, d_name));
+
+                       if (memchr(dp->d_name, '/', dp->d_namlen) != NULL) {
+                               error = EINVAL;
+                               break;
+                       }
+                       dp = (struct dirent *)((char *)dp + dp->d_reclen);
+               }
+               if (error) {
+                       fb_delete(fbuf);
+                       break;
+               }
+
                if ((error = uiomove(fbuf->fb_dat, fbuf->fb_len, uio))) {
                        fb_delete(fbuf);
                        break;