git: 890cae197737 - main - fsck_msdosfs: truncate directory entry when the head pointer is invalid.

From: Xin LI <delphij_at_FreeBSD.org>
Date: Thu, 04 Nov 2021 05:09:44 UTC
The branch main has been updated by delphij:

URL: https://cgit.FreeBSD.org/src/commit/?id=890cae197737b463e56d1cc5a3f61f84cb49c807

commit 890cae197737b463e56d1cc5a3f61f84cb49c807
Author:     Xin LI <delphij@FreeBSD.org>
AuthorDate: 2021-11-04 05:09:32 +0000
Commit:     Xin LI <delphij@FreeBSD.org>
CommitDate: 2021-11-04 05:09:32 +0000

    fsck_msdosfs: truncate directory entry when the head pointer is invalid.
    
    As far as we know, there is no FAT implementation that supported hard
    links, and our msdosfs driver assumed one cluster chain is only
    referenced by one directory entry and clears it out when the file is
    deleted.  On the other hand, the current code would proceed with
    checkchain() when the directory entry's head cluster is a valid numbered
    cluster without checking if it was a valid head node of a cluster chain.
    
    So if the cluster do not being a chain (e.g. CLUST_FREE, CLUST_BAD),
    or was already referenced by another directory entry, this would
    trigger an assertion in check_chain() at a later time.
    
    Fix this by giving the user an option to truncate the directory entry
    when the head cluster is an invalid cluster, an visited head node,
    or not a head node.
    
    Reported by:    NetApp (kevans@)
    Reviewed by:    kevans, emaste (no objection)
    MFC after:      2 weeks
    Differential Revision: https://reviews.freebsd.org/D32699
---
 sbin/fsck_msdosfs/dir.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/sbin/fsck_msdosfs/dir.c b/sbin/fsck_msdosfs/dir.c
index 471f6cc0335e..dbe4e0c7db2f 100644
--- a/sbin/fsck_msdosfs/dir.c
+++ b/sbin/fsck_msdosfs/dir.c
@@ -400,8 +400,21 @@ checksize(struct fat_descriptor *fat, u_char *p, struct dosDirEntry *dir)
 	if (dir->head == CLUST_FREE) {
 		physicalSize = 0;
 	} else {
-		if (!fat_is_valid_cl(fat, dir->head))
-			return FSERROR;
+		if (!fat_is_valid_cl(fat, dir->head) || !fat_is_cl_head(fat, dir->head)) {
+			pwarn("Directory entry %s of size %u referencing invalid cluster %u\n",
+			    fullpath(dir), dir->size, dir->head);
+			if (ask(1, "Truncate")) {
+				p[28] = p[29] = p[30] = p[31] = 0;
+				p[26] = p[27] = 0;
+				if (boot->ClustMask == CLUST32_MASK)
+					p[20] = p[21] = 0;
+				dir->size = 0;
+				dir->head = CLUST_FREE;
+				return FSDIRMOD;
+			} else {
+				return FSERROR;
+			}
+		}
 		ret = checkchain(fat, dir->head, &chainsize);
 		/*
 		 * Upon return, chainsize would hold the chain length