From 7d354e5be83ed3bb981c59b3435ff64da56d185d Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Mon, 4 Sep 2023 17:43:38 +0800 Subject: [PATCH 1/7] exfatprogs: fix argument validation check Since the last argument should be the device name. For the options which required argument, if argument is not specified, the device name will be treated as the required argument, that is unexpected. For the options which not required argument, if the device name is not specified, the device name pointer will be NULL, but it is not checked. Such as: # tune.exfat -L /dev/sdb1 exfatprogs version : 1.2.4 new label: /dev/sdb1 # exfatlabel -i exfatprogs version : 1.2.4 open failed : (null), No such file or directory Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- dump/dump.c | 2 +- label/label.c | 2 +- tune/tune.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dump/dump.c b/dump/dump.c index 7e30f34..e15e195 100644 --- a/dump/dump.c +++ b/dump/dump.c @@ -244,7 +244,7 @@ int main(int argc, char *argv[]) if (version_only) exit(EXIT_FAILURE); - if (argc < 2) + if (argc - optind != 1) usage(); ui.dev_name = argv[1]; diff --git a/label/label.c b/label/label.c index c5eea7a..12d027b 100644 --- a/label/label.c +++ b/label/label.c @@ -78,7 +78,7 @@ int main(int argc, char *argv[]) if (version_only) exit(EXIT_FAILURE); - if (argc < 2) + if (argc - optind != 1) usage(); ui.dev_name = argv[serial_mode + 1]; diff --git a/tune/tune.c b/tune/tune.c index 218e840..8a4b934 100644 --- a/tune/tune.c +++ b/tune/tune.c @@ -115,7 +115,7 @@ int main(int argc, char *argv[]) if (version_only) exit(EXIT_FAILURE); - if (argc < 3) + if (argc < 3 || argc - optind != 1) usage(); ui.dev_name = argv[argc - 1]; From 0c9ece9f6d54f8b93e513c407d98f9625b306d87 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Tue, 5 Sep 2023 10:22:12 +0800 Subject: [PATCH 2/7] fsck: fix bad char check for non-ASCII characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Invalid filename characters in the exfat specification are defined in UTF16 format. We need to convert the file name to UTF16 format and then check it is valid, otherwise the input file name containing non-ASCII characters will be incorrectly checked as invalid. Such as: $ fsck.exfat -r exfat.img exfatprogs version : 1.2.4 ERROR: '..' filename is not allowed. [1] Insert the name you want to rename. [2] Automatically renames filename. [3] Bypass this check(No repair) Select (Number: ?) 1 New name: 点点 filename contain invalid character(�) Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fsck/repair.c | 62 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/fsck/repair.c b/fsck/repair.c index 6179b65..35d69f6 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -160,34 +160,43 @@ int exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode, return repair; } -static int check_bad_char(char w) +static int check_bad_utf16_char(unsigned short w) { return (w < 0x0020) || (w == '*') || (w == '?') || (w == '<') || (w == '>') || (w == '|') || (w == '"') || (w == ':') || (w == '/') || (w == '\\'); } -static char *get_rename_from_user(struct exfat_de_iter *iter) +static int get_rename_from_user(struct exfat_de_iter *iter, + __le16 *utf16_name, int name_size) { + int len = 0; char *rename = malloc(ENTRY_NAME_MAX + 2); if (!rename) - return NULL; + return -ENOMEM; retry: /* +2 means LF(Line Feed) and NULL terminator */ memset(rename, 0x1, ENTRY_NAME_MAX + 2); printf("New name: "); if (fgets(rename, ENTRY_NAME_MAX + 2, stdin)) { - int i, len, err; + int i, err; struct exfat_lookup_filter filter; len = strlen(rename); /* Remove LF in filename */ rename[len - 1] = '\0'; - for (i = 0; i < len - 1; i++) { - if (check_bad_char(rename[i])) { - printf("filename contain invalid character(%c)\n", rename[i]); + + memset(utf16_name, 0, name_size); + len = exfat_utf16_enc(rename, utf16_name, name_size); + if (len < 0) + goto out; + + for (i = 0; i < len >> 1; i++) { + if (check_bad_utf16_char(le16_to_cpu(utf16_name[i]))) { + printf("filename contain invalid character(%c)\n", + le16_to_cpu(utf16_name[i])); goto retry; } } @@ -200,23 +209,27 @@ static char *get_rename_from_user(struct exfat_de_iter *iter) } } - return rename; +out: + free(rename); + + return len; } -static char *generate_rename(struct exfat_de_iter *iter) +static int generate_rename(struct exfat_de_iter *iter, __le16 *utf16_name, + int name_size) { + int err; char *rename; if (iter->invalid_name_num > INVALID_NAME_NUM_MAX) - return NULL; + return -ERANGE; rename = malloc(ENTRY_NAME_MAX + 1); if (!rename) - return NULL; + return -ENOMEM; while (1) { struct exfat_lookup_filter filter; - int err; snprintf(rename, ENTRY_NAME_MAX + 1, "FILE%07d.CHK", iter->invalid_name_num++); @@ -227,7 +240,11 @@ static char *generate_rename(struct exfat_de_iter *iter) break; } - return rename; + memset(utf16_name, 0, name_size); + err = exfat_utf16_enc(rename, utf16_name, name_size); + free(rename); + + return err; } int exfat_repair_rename_ask(struct exfat_fsck *fsck, struct exfat_de_iter *iter, @@ -243,38 +260,31 @@ int exfat_repair_rename_ask(struct exfat_fsck *fsck, struct exfat_de_iter *iter, " [3] Bypass this check(No repair)\n"); if (num) { __le16 utf16_name[ENTRY_NAME_MAX]; - char *rename = NULL; __u16 hash; struct exfat_dentry *dentry; int ret; switch (num) { case 1: - rename = get_rename_from_user(iter); + ret = get_rename_from_user(iter, utf16_name, + sizeof(utf16_name)); break; case 2: - rename = generate_rename(iter); + ret = generate_rename(iter, utf16_name, + sizeof(utf16_name)); break; case 3: - break; + return -EINVAL; default: exfat_info("select 1 or 2 number instead of %d\n", num); goto ask_again; } - if (!rename) + if (ret < 0) return -EINVAL; - exfat_info("%s filename is renamed to %s\n", old_name, rename); - exfat_de_iter_get_dirty(iter, 2, &dentry); - memset(utf16_name, 0, sizeof(utf16_name)); - ret = exfat_utf16_enc(rename, utf16_name, sizeof(utf16_name)); - free(rename); - if (ret < 0) - return ret; - ret >>= 1; memcpy(dentry->name_unicode, utf16_name, ENTRY_NAME_MAX * 2); hash = exfat_calc_name_hash(iter->exfat, utf16_name, ret); From 171d2c1999a02219a8fa63500ed3e9f888edb282 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Tue, 5 Sep 2023 10:37:45 +0800 Subject: [PATCH 3/7] lib: add exfat_check_name() Filename character validation check is applied not only to filename, but also to volume label. Let's add a helper for it. Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fsck/repair.c | 20 ++++++-------------- include/libexfat.h | 1 + lib/libexfat.c | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/fsck/repair.c b/fsck/repair.c index 35d69f6..415df65 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -160,13 +160,6 @@ int exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode, return repair; } -static int check_bad_utf16_char(unsigned short w) -{ - return (w < 0x0020) || (w == '*') || (w == '?') || (w == '<') || - (w == '>') || (w == '|') || (w == '"') || (w == ':') || - (w == '/') || (w == '\\'); -} - static int get_rename_from_user(struct exfat_de_iter *iter, __le16 *utf16_name, int name_size) { @@ -181,7 +174,7 @@ static int get_rename_from_user(struct exfat_de_iter *iter, memset(rename, 0x1, ENTRY_NAME_MAX + 2); printf("New name: "); if (fgets(rename, ENTRY_NAME_MAX + 2, stdin)) { - int i, err; + int err; struct exfat_lookup_filter filter; len = strlen(rename); @@ -193,12 +186,11 @@ static int get_rename_from_user(struct exfat_de_iter *iter, if (len < 0) goto out; - for (i = 0; i < len >> 1; i++) { - if (check_bad_utf16_char(le16_to_cpu(utf16_name[i]))) { - printf("filename contain invalid character(%c)\n", - le16_to_cpu(utf16_name[i])); - goto retry; - } + err = exfat_check_name(utf16_name, len >> 1); + if (err != len >> 1) { + printf("filename contain invalid character(%c)\n", + le16_to_cpu(utf16_name[err])); + goto retry; } exfat_de_iter_flush(iter); diff --git a/include/libexfat.h b/include/libexfat.h index 06b5498..bcab23a 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -190,6 +190,7 @@ bool exfat_heap_clus(struct exfat *exfat, clus_t clus); int exfat_root_clus_count(struct exfat *exfat); int read_boot_sect(struct exfat_blk_dev *bdev, struct pbr **bs); int exfat_parse_ulong(const char *s, unsigned long *out); +int exfat_check_name(__le16 *utf16_name, int len); /* * Exfat Print diff --git a/lib/libexfat.c b/lib/libexfat.c index 9cc184f..868b3bf 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -1058,3 +1058,22 @@ int exfat_parse_ulong(const char *s, unsigned long *out) return 0; } + +static inline int check_bad_utf16_char(unsigned short w) +{ + return (w < 0x0020) || (w == '*') || (w == '?') || (w == '<') || + (w == '>') || (w == '|') || (w == '"') || (w == ':') || + (w == '/') || (w == '\\'); +} + +int exfat_check_name(__le16 *utf16_name, int len) +{ + int i; + + for (i = 0; i < len; i++) { + if (check_bad_utf16_char(le16_to_cpu(utf16_name[i]))) + break; + } + + return i; +} From f47b411e638df6f48d55c3d87a9758039e34ac91 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 8 Sep 2023 14:23:53 +0800 Subject: [PATCH 4/7] lib: add validation check for input volume label Added validation check for input volume label to prevent user from entering invalid volume label. Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- lib/libexfat.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/libexfat.c b/lib/libexfat.c index 868b3bf..3211454 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -496,12 +496,20 @@ int exfat_set_volume_label(struct exfat *exfat, char *label_input) volume_label, sizeof(volume_label)); if (volume_label_len < 0) { exfat_err("failed to encode volume label\n"); - free(pvol); - return -1; + err = -1; + goto out; } - memcpy(pvol->vol_label, volume_label, volume_label_len); pvol->vol_char_cnt = volume_label_len/2; + err = exfat_check_name(volume_label, pvol->vol_char_cnt); + if (err != pvol->vol_char_cnt) { + exfat_err("volume label contain invalid character(%c)\n", + le16_to_cpu(label_input[err])); + err = -1; + goto out; + } + + memcpy(pvol->vol_label, volume_label, volume_label_len); loc.parent = exfat->root; loc.file_offset = filter.out.file_offset; @@ -509,6 +517,7 @@ int exfat_set_volume_label(struct exfat *exfat, char *label_input) err = exfat_add_dentry_set(exfat, &loc, pvol, dcount, false); exfat_info("new label: %s\n", label_input); +out: free(pvol); return err; From ec48fba6143e58d16032ed834bf7441721b0e024 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 31 Aug 2023 16:49:28 +0800 Subject: [PATCH 5/7] fsck: move decode name into exfat_repair_rename_ask() Decoded filename is only needed if rename is needed, so moved decoded filename into exfat_repair_rename_ask(). Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fsck/fsck.c | 31 +++++++++++-------------------- fsck/repair.c | 8 +++++++- fsck/repair.h | 2 +- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 7cac445..91512e5 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -647,7 +647,6 @@ static int handle_duplicated_filename(struct exfat_de_iter *iter, { int ret; struct exfat_lookup_filter filter; - char filename[PATH_MAX + 1] = {0}; ret = exfat_lookup_file_by_utf16name(iter->exfat, iter->parent, inode->name, &filter); @@ -660,14 +659,7 @@ static int handle_duplicated_filename(struct exfat_de_iter *iter, if (exfat_de_iter_device_offset(iter) == filter.out.dev_offset) return 0; - ret = exfat_utf16_dec(inode->name, NAME_BUFFER_SIZE, filename, - PATH_MAX); - if (ret < 0) { - exfat_err("failed to decode filename\n"); - return ret; - } - - return exfat_repair_rename_ask(&exfat_fsck, iter, filename, + return exfat_repair_rename_ask(&exfat_fsck, iter, inode->name, ER_DE_DUPLICATED_NAME, "filename is duplicated"); } @@ -713,21 +705,20 @@ static int check_name_dentry_set(struct exfat_de_iter *iter, return ret; } -const __le16 MSDOS_DOT[ENTRY_NAME_MAX] = {cpu_to_le16(46), 0, }; -const __le16 MSDOS_DOTDOT[ENTRY_NAME_MAX] = {cpu_to_le16(46), cpu_to_le16(46), 0, }; +#define MSDOS_DOT cpu_to_le16('.') static int handle_dot_dotdot_filename(struct exfat_de_iter *iter, - struct exfat_dentry *dentry, + __le16 *filename, int strm_name_len) { - char *filename; + int i; - if (!memcmp(dentry->name_unicode, MSDOS_DOT, strm_name_len * 2)) - filename = "."; - else if (!memcmp(dentry->name_unicode, MSDOS_DOTDOT, - strm_name_len * 2)) - filename = ".."; - else + for (i = 0; i < strm_name_len; i++) { + if (filename[i] != MSDOS_DOT) + return 0; + } + + if (filename[i]) return 0; return exfat_repair_rename_ask(&exfat_fsck, iter, filename, @@ -817,7 +808,7 @@ static int read_file_dentry_set(struct exfat_de_iter *iter, } if (file_de->file_num_ext == 2 && stream_de->stream_name_len <= 2) { - ret = handle_dot_dotdot_filename(iter, dentry, + ret = handle_dot_dotdot_filename(iter, node->name, stream_de->stream_name_len); if (ret < 0) { *skip_dentries = file_de->file_num_ext + 1; diff --git a/fsck/repair.c b/fsck/repair.c index 415df65..dd1511c 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -240,9 +240,15 @@ static int generate_rename(struct exfat_de_iter *iter, __le16 *utf16_name, } int exfat_repair_rename_ask(struct exfat_fsck *fsck, struct exfat_de_iter *iter, - char *old_name, er_problem_code_t prcode, char *error_msg) + __le16 *uname, er_problem_code_t prcode, char *error_msg) { int num; + char old_name[PATH_MAX + 1] = {0}; + + if (exfat_utf16_dec(uname, NAME_BUFFER_SIZE, old_name, PATH_MAX) <= 0) { + exfat_err("failed to decode filename\n"); + return -EINVAL; + } ask_again: num = exfat_repair_ask(fsck, prcode, "ERROR: '%s' %s.\n%s", diff --git a/fsck/repair.h b/fsck/repair.h index f1cde24..0e98f1b 100644 --- a/fsck/repair.h +++ b/fsck/repair.h @@ -35,5 +35,5 @@ int exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode, const char *fmt, ...); int exfat_repair_rename_ask(struct exfat_fsck *fsck, struct exfat_de_iter *iter, - char *old_name, er_problem_code_t prcode, char *error_msg); + __le16 *uname, er_problem_code_t prcode, char *error_msg); #endif From 313a924e62d7751f0b8e7b22d6f64db0157fc7f5 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 30 Aug 2023 15:44:32 +0800 Subject: [PATCH 6/7] fsck: support repair invalid filename This commit adds the ability to check and fix invalid characters in filenames. Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- fsck/fsck.c | 14 +++++++++++++- fsck/repair.c | 1 + fsck/repair.h | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/fsck/fsck.c b/fsck/fsck.c index 91512e5..405c23c 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -674,7 +674,7 @@ static int check_name_dentry_set(struct exfat_de_iter *iter, 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 (name_len && 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); @@ -685,6 +685,18 @@ static int check_name_dentry_set(struct exfat_de_iter *iter, } } + ret = exfat_check_name(inode->name, stream_de->stream_name_len); + if (ret != stream_de->stream_name_len) { + char err_msg[36]; + + snprintf(err_msg, sizeof(err_msg), + "filename has invalid character '%c'", + le16_to_cpu(inode->name[ret])); + + return exfat_repair_rename_ask(&exfat_fsck, iter, inode->name, + ER_DE_INVALID_NAME, err_msg); + } + 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, diff --git a/fsck/repair.c b/fsck/repair.c index dd1511c..420b1fb 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -55,6 +55,7 @@ static struct exfat_repair_problem problems[] = { {ER_DE_NAME_LEN, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}, {ER_DE_DOT_NAME, ERF_PREEN_YES, ERP_RENAME, 2, 3, 4}, {ER_DE_DUPLICATED_NAME, ERF_PREEN_YES, ERP_RENAME, 2, 3, 4}, + {ER_DE_INVALID_NAME, ERF_PREEN_YES, ERP_RENAME, 2, 3, 4}, {ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}, {ER_FILE_INVALID_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, {ER_FILE_FIRST_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, diff --git a/fsck/repair.h b/fsck/repair.h index 0e98f1b..2d46b8e 100644 --- a/fsck/repair.h +++ b/fsck/repair.h @@ -19,6 +19,7 @@ #define ER_DE_NAME_LEN 0x00001032 #define ER_DE_DOT_NAME 0x00001033 #define ER_DE_DUPLICATED_NAME 0x00001034 +#define ER_DE_INVALID_NAME 0x00001035 #define ER_FILE_VALID_SIZE 0x00002001 #define ER_FILE_INVALID_CLUS 0x00002002 #define ER_FILE_FIRST_CLUS 0x00002003 From d298884ca25e497cb3c77e2483e337dfdbb32dce Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Mon, 11 Sep 2023 14:30:23 +0800 Subject: [PATCH 7/7] fsck: tests: add test for repairing invalid filename The exfat specification stipulates 41 invalid file name characters. In invalid_name/exfat.img.tar.xz, 41 files were created with these invalid file name characters. Signed-off-by: Yuezhang Mo Reviewed-by: Aoyama Wataru Reviewed-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- tests/invalid_name/exfat.img.tar.xz | Bin 0 -> 4444 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/invalid_name/exfat.img.tar.xz diff --git a/tests/invalid_name/exfat.img.tar.xz b/tests/invalid_name/exfat.img.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..d27402869f3395fde689ecbaadf59ed6ded40332 GIT binary patch literal 4444 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