From c8504184a01cb859975fe3ff996c1377eb2875d1 Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Fri, 4 Jun 2021 11:01:37 +0900 Subject: [PATCH] fsck: repair corrupted dentry sets Repair the following corrupted dentry sets: * invalid File dentry's SetChecksum. * invalid File dentry's SecondaryCount. * Absent Stream Extension dentry. * mismatch between the file size and the number of clusters. * invalid NameHash. * mismatch between the length of name and NameLength. Signed-off-by: Hyunchul Lee --- fsck/fsck.c | 158 ++++++++++++++++++++++++++++++++++++------------- fsck/repair.c | 12 +++- fsck/repair.h | 6 ++ lib/exfat_fs.c | 8 ++- 4 files changed, 137 insertions(+), 47 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 9c2508f8..8512a659 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -603,36 +603,87 @@ static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node) checksum = file_calc_checksum(iter); exfat_de_iter_get(iter, 0, &dentry); if (checksum != le16_to_cpu(dentry->file_checksum)) { - if (repair_file_ask(iter, node, ER_DE_CHECKSUM, - "the checksum of a file is wrong")) { - exfat_de_iter_get_dirty(iter, 0, &dentry); - dentry->file_checksum = cpu_to_le16(checksum); - ret = 1; - } else - valid = false; + exfat_de_iter_get_dirty(iter, 0, &dentry); + dentry->file_checksum = cpu_to_le16(checksum); + ret = 1; } return valid ? ret : -EINVAL; } +static int check_name_dentry_set(struct exfat_de_iter *iter, + struct exfat_inode *inode) +{ + struct exfat_dentry *stream_de; + size_t name_len; + __u16 hash; + + exfat_de_iter_get(iter, 1, &stream_de); + + name_len = exfat_utf16_len(inode->name, NAME_BUFFER_SIZE); + if (stream_de->stream_name_len != name_len) { + if (repair_file_ask(iter, NULL, ER_DE_NAME_LEN, + "the name length of a file is wrong")) { + exfat_de_iter_get_dirty(iter, 1, &stream_de); + stream_de->stream_name_len = (__u8)name_len; + } else { + return -EINVAL; + } + } + + hash = exfat_calc_name_hash(iter->exfat, inode->name, (int)name_len); + if (cpu_to_le16(hash) != stream_de->stream_name_hash) { + if (repair_file_ask(iter, NULL, ER_DE_NAME_HASH, + "the name hash of a file is wrong")) { + exfat_de_iter_get_dirty(iter, 1, &stream_de); + stream_de->stream_name_hash = cpu_to_le16(hash); + } else { + return -EINVAL; + } + } + return 0; +} + static int read_file_dentry_set(struct exfat_de_iter *iter, struct exfat_inode **new_node, int *skip_dentries) { - struct exfat_dentry *file_de, *stream_de, *name_de; - struct exfat_inode *node; + struct exfat_dentry *file_de, *stream_de, *dentry; + struct exfat_inode *node = NULL; int i, ret; - - /* TODO: mtime, atime, ... */ + bool need_delete = false; + uint16_t checksum; ret = exfat_de_iter_get(iter, 0, &file_de); if (ret || file_de->type != EXFAT_FILE) { - exfat_err("failed to get file dentry. %d\n", ret); + exfat_err("failed to get file dentry\n"); return -EINVAL; } + + checksum = file_calc_checksum(iter); + if (checksum != le16_to_cpu(file_de->file_checksum)) { + if (repair_file_ask(iter, NULL, ER_DE_CHECKSUM, + "the checksum of a file is wrong")) + need_delete = true; + *skip_dentries = 1; + goto skip_dset; + } + + if (file_de->file_num_ext < 2) { + if (repair_file_ask(iter, NULL, ER_DE_SECONDARY_COUNT, + "a file has too few secondary count. %d", + file_de->file_num_ext)) + need_delete = true; + *skip_dentries = 1; + goto skip_dset; + } + ret = exfat_de_iter_get(iter, 1, &stream_de); if (ret || stream_de->type != EXFAT_STREAM) { - exfat_err("failed to get stream dentry. %d\n", ret); - return -EINVAL; + if (repair_file_ask(iter, NULL, ER_DE_STREAM, + "failed to get stream dentry")) + need_delete = true; + *skip_dentries = 2; + goto skip_dset; } *new_node = NULL; @@ -640,24 +691,28 @@ static int read_file_dentry_set(struct exfat_de_iter *iter, if (!node) return -ENOMEM; - if (file_de->file_num_ext < 2) { - exfat_err("too few secondary count. %d\n", - file_de->file_num_ext); - exfat_free_inode(node); - return -EINVAL; - } - for (i = 2; i <= file_de->file_num_ext; i++) { - ret = exfat_de_iter_get(iter, i, &name_de); - if (ret || name_de->type != EXFAT_NAME) { - exfat_err("failed to get name dentry. %d\n", ret); - ret = -EINVAL; - goto err; + ret = exfat_de_iter_get(iter, i, &dentry); + if (ret || dentry->type != EXFAT_NAME) { + if (i > 2 && repair_file_ask(iter, NULL, ER_DE_NAME, + "failed to get name dentry")) { + exfat_de_iter_get_dirty(iter, 0, &file_de); + file_de->file_num_ext = i - 1; + break; + } + *skip_dentries = i + 1; + goto skip_dset; } memcpy(node->name + - (i-2) * ENTRY_NAME_MAX, name_de->name_unicode, - sizeof(name_de->name_unicode)); + (i - 2) * ENTRY_NAME_MAX, dentry->name_unicode, + sizeof(dentry->name_unicode)); + } + + ret = check_name_dentry_set(iter, node); + if (ret) { + *skip_dentries = file_de->file_num_ext + 1; + goto skip_dset; } node->first_clus = le32_to_cpu(stream_de->stream_start_clu); @@ -666,27 +721,41 @@ static int read_file_dentry_set(struct exfat_de_iter *iter, node->size = le64_to_cpu(stream_de->stream_size); if (node->size < le64_to_cpu(stream_de->stream_valid_size)) { + *skip_dentries = file_de->file_num_ext + 1; if (repair_file_ask(iter, node, ER_FILE_VALID_SIZE, - "valid size %" PRIu64 " greater than size %" PRIu64, - le64_to_cpu(stream_de->stream_valid_size), - node->size)) { + "valid size %" PRIu64 " greater than size %" PRIu64, + le64_to_cpu(stream_de->stream_valid_size), + node->size)) { exfat_de_iter_get_dirty(iter, 1, &stream_de); stream_de->stream_valid_size = stream_de->stream_size; } else { - ret = -EINVAL; - goto err; + *skip_dentries = file_de->file_num_ext + 1; + goto skip_dset; } } *skip_dentries = (file_de->file_num_ext + 1); *new_node = node; return 0; -err: - *skip_dentries = 1; +skip_dset: + if (need_delete) { + exfat_de_iter_get_dirty(iter, 0, &dentry); + dentry->type &= EXFAT_DELETE; + } + for (i = 1; i < *skip_dentries; i++) { + exfat_de_iter_get(iter, i, &dentry); + if (dentry->type == EXFAT_FILE) + break; + if (need_delete) { + exfat_de_iter_get_dirty(iter, i, &dentry); + dentry->type &= EXFAT_DELETE; + } + } + *skip_dentries = i; *new_node = NULL; exfat_free_inode(node); - return ret; + return need_delete ? 1 : -EINVAL; } static int read_file(struct exfat_de_iter *de_iter, @@ -948,12 +1017,17 @@ static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir) exfat_stat.fixed_count++; } - if ((node->attr & ATTR_SUBDIR) && node->size) { - node->parent = dir; - list_add_tail(&node->sibling, &dir->children); - list_add_tail(&node->list, &exfat->dir_list); - } else - exfat_free_inode(node); + if (node) { + if ((node->attr & ATTR_SUBDIR) && node->size) { + node->parent = dir; + list_add_tail(&node->sibling, + &dir->children); + list_add_tail(&node->list, + &exfat->dir_list); + } else { + exfat_free_inode(node); + } + } break; case EXFAT_LAST: goto out; diff --git a/fsck/repair.c b/fsck/repair.c index 97c3ed69..65f4a9ff 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -27,18 +27,26 @@ struct exfat_repair_problem { /* Prompt types */ #define ERP_FIX 0x00000001 #define ERP_TRUNCATE 0x00000002 +#define ERP_DELETE 0x00000003 static const char *prompts[] = { "Repair", "Fix", "Truncate", + "Delete", }; static struct exfat_repair_problem problems[] = { {ER_BS_CHECKSUM, ERF_PREEN_YES, ERP_FIX}, {ER_BS_BOOT_REGION, 0, ERP_FIX}, - {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_FIX}, - {ER_DE_UNKNOWN, ERF_PREEN_YES, ERP_FIX}, + {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_DELETE}, + {ER_DE_UNKNOWN, ERF_PREEN_YES, ERP_DELETE}, + {ER_DE_FILE, ERF_PREEN_YES, ERP_DELETE}, + {ER_DE_SECONDARY_COUNT, ERF_PREEN_YES, ERP_DELETE}, + {ER_DE_STREAM, ERF_PREEN_YES, ERP_DELETE}, + {ER_DE_NAME, ERF_PREEN_YES, ERP_DELETE}, + {ER_DE_NAME_HASH, ERF_PREEN_YES, ERP_FIX}, + {ER_DE_NAME_LEN, ERF_PREEN_YES, ERP_FIX}, {ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX}, {ER_FILE_INVALID_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, {ER_FILE_FIRST_CLUS, ERF_PREEN_YES, ERP_TRUNCATE}, diff --git a/fsck/repair.h b/fsck/repair.h index 4f365471..4e9a6bf9 100644 --- a/fsck/repair.h +++ b/fsck/repair.h @@ -9,6 +9,12 @@ #define ER_BS_BOOT_REGION 0x00000002 #define ER_DE_CHECKSUM 0x00001001 #define ER_DE_UNKNOWN 0x00001002 +#define ER_DE_FILE 0x00001010 +#define ER_DE_SECONDARY_COUNT 0x00001011 +#define ER_DE_STREAM 0x00001020 +#define ER_DE_NAME 0x00001030 +#define ER_DE_NAME_HASH 0x00001031 +#define ER_DE_NAME_LEN 0x00001032 #define ER_FILE_VALID_SIZE 0x00002001 #define ER_FILE_INVALID_CLUS 0x00002002 #define ER_FILE_FIRST_CLUS 0x00002003 diff --git a/lib/exfat_fs.c b/lib/exfat_fs.c index baf2d127..41518b07 100644 --- a/lib/exfat_fs.c +++ b/lib/exfat_fs.c @@ -37,9 +37,11 @@ struct exfat_inode *exfat_alloc_inode(__u16 attr) void exfat_free_inode(struct exfat_inode *node) { - if (node->dentry_set) - free(node->dentry_set); - free(node); + if (node) { + if (node->dentry_set) + free(node->dentry_set); + free(node); + } } void exfat_free_children(struct exfat_inode *dir, bool file_only)