sbin/fsck_msdosfs: Bring in freebsd/freebsd@437a93c1 (test ./.. for dir dirents)
authorTomohiro Kusumi <kusumi.tomohiro@gmail.com>
Sat, 14 Sep 2019 18:29:38 +0000 (03:29 +0900)
committerTomohiro Kusumi <kusumi.tomohiro@gmail.com>
Sat, 14 Sep 2019 18:40:54 +0000 (03:40 +0900)
Also bring in freebsd/freebsd@76844b29.

sbin/fsck_msdosfs/dir.c

index 4046096..5a44095 100644 (file)
@@ -415,6 +415,75 @@ checksize(struct bootblock *boot, struct fatEntry *fat, u_char *p,
        return FSOK;
 }
 
+static const u_char dot_name[11]    = ".          ";
+static const u_char dotdot_name[11] = "..         ";
+
+/*
+ * Basic sanity check if the subdirectory have good '.' and '..' entries,
+ * and they are directory entries.  Further sanity checks are performed
+ * when we traverse into it.
+ */
+static int
+check_subdirectory(int f, struct bootblock *boot, struct dosDirEntry *dir)
+{
+       u_char *buf, *cp;
+       off_t off;
+       cl_t cl;
+       int retval = FSOK;
+
+       cl = dir->head;
+       if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
+               return FSERROR;
+       }
+
+       if (!(boot->flags & FAT32) && !dir->parent) {
+               off = boot->bpbResSectors + boot->bpbFATs *
+                       boot->FATsecs;
+       } else {
+               off = cl * boot->bpbSecPerClust + boot->ClusterOffset;
+       }
+
+       /*
+        * We only need to check the first two entries of the directory,
+        * which is found in the first sector of the directory entry,
+        * so read in only the first sector.
+        */
+       buf = malloc(boot->bpbBytesPerSec);
+       if (buf == NULL) {
+               perr("No space for directory buffer (%u)",
+                   boot->bpbBytesPerSec);
+               return FSFATAL;
+       }
+
+       off *= boot->bpbBytesPerSec;
+       if (lseek(f, off, SEEK_SET) != off ||
+           read(f, buf, boot->bpbBytesPerSec) != boot->bpbBytesPerSec) {
+               perr("Unable to read directory");
+               free(buf);
+               return FSFATAL;
+       }
+
+       /*
+        * Both `.' and `..' must be present and be the first two entries
+        * and be ATTR_DIRECTORY of a valid subdirectory.
+        */
+       cp = buf;
+       if (memcmp(cp, dot_name, sizeof(dot_name)) != 0 ||
+           (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
+               pwarn("%s: Incorrect `.' for %s.\n", __func__, dir->name);
+               retval |= FSERROR;
+       }
+       cp += 32;
+       if (memcmp(cp, dotdot_name, sizeof(dotdot_name)) != 0 ||
+           (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) {
+               pwarn("%s: Incorrect `..' for %s. \n", __func__, dir->name);
+               retval |= FSERROR;
+       }
+
+       free(buf);
+       return retval;
+}
+
 /*
  * Read a directory and
  *   - resolve long name records
@@ -461,9 +530,6 @@ readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat,
                        return FSFATAL;
                }
                last /= 32;
-               /*
-                * Check `.' and `..' entries here?                     XXX
-                */
                for (p = buffer, i = 0; i < last; i++, p += 32) {
                        if (dir->fsckflags & DIREMPWARN) {
                                *p = SLOT_EMPTY;
@@ -806,6 +872,36 @@ readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat,
                                                }
                                        }
                                        continue;
+                               } else {
+                                       /*
+                                        * Only one directory entry can point
+                                        * to dir->head, it's '.'.
+                                        */
+                                       if (dirent.head == dir->head) {
+                                               pwarn("%s entry in %s has incorrect start cluster\n",
+                                                               dirent.name, fullpath(dir));
+                                               if (ask(1, "Remove")) {
+                                                       *p = SLOT_DELETED;
+                                                       mod |= THISMOD|FSDIRMOD;
+                                               } else
+                                                       mod |= FSERROR;
+                                               continue;
+                                       } else if ((check_subdirectory(f, boot,
+                                           &dirent) & FSERROR) == FSERROR) {
+                                               /*
+                                                * A subdirectory should have
+                                                * a dot (.) entry and a dot-dot
+                                                * (..) entry of ATTR_DIRECTORY,
+                                                * we will inspect further when
+                                                * traversing into it.
+                                                */
+                                               if (ask(1, "Remove")) {
+                                                       *p = SLOT_DELETED;
+                                                       mod |= THISMOD|FSDIRMOD;
+                                               } else
+                                                       mod |= FSERROR;
+                                               continue;
+                                       }
                                }
 
                                /* create directory tree node */