2022-03-17 10:09:38

by Vasant Karasulli

[permalink] [raw]
Subject: [PATCH v5 0/1] exfat: allow access to paths with trailing dots

This is the version 5 of the patch that introduces
a new exfat mount option 'keep_last_dots'. In this
version commit message of the patch is improved
according to the review comments in
https://lore.kernel.org/linux-fsdevel/[email protected]/T/#t

Version 4 of the patch:
https://lore.kernel.org/linux-fsdevel/[email protected]/T/#t

Vasant Karasulli (1):
The Linux kernel exfat driver currently unconditionally strips
trailing periods '.' from path components. This is done
intentionally, loosely following Windows behaviour and
specifications which state:

fs/exfat/exfat_fs.h | 3 ++-
fs/exfat/namei.c | 50 ++++++++++++++++++++++++++++++++-------------
fs/exfat/super.c | 7 +++++++
3 files changed, 45 insertions(+), 15 deletions(-)


base-commit: ffb217a13a2eaf6d5bd974fc83036a53ca69f1e2
prerequisite-patch-id: aa89fed0f25e0593bd930cb1925a61318970af3b
prerequisite-patch-id: b82d57cf11a808fd91ebce196ad90742f266ae39
prerequisite-patch-id: 8fb922007d8da42e7d8915ad4192c3a881384720
prerequisite-patch-id: 80a740f0cc838892abca091667fa5b407611ea39
prerequisite-patch-id: 70a6044affdfcfba97c7651fb2150fa42cf01805
prerequisite-patch-id: a017bdbdcc66df4dd3a66ba03a37714b8e68d253
prerequisite-patch-id: 52771ab4aa8cbdafed3594d7d9a0c75b8a53f6e4
prerequisite-patch-id: fe388ead9e78b2e67bfb1ddfd5cd60a496c12d1c
prerequisite-patch-id: fb7ef4d34a652d20b3c6edefaf72ca6298d1e8f4
prerequisite-patch-id: 4194bee4bb9ee6eaeb6ee1ddd82cb84cdcab8d38
prerequisite-patch-id: e178f0c524a65e855ffb7861f1b9a9b2d56a2428
prerequisite-patch-id: e35925ec691c2fa8c167c19844cb40ef090845aa
prerequisite-patch-id: 6f54f7183bb0c519f9f76d8645da5a881ca71458
prerequisite-patch-id: b2d2dc9c206fbfc0c80f1be3d9ab031ba2dfd279
prerequisite-patch-id: 47a6c9093808e07f6ff561d011566a4ee4e7465a
prerequisite-patch-id: 70b5f29b3208d4e0b8bd27900618aeaba901d19a
prerequisite-patch-id: 56b6ad48cc9999893d34e7378ad1bbad293ca52b
prerequisite-patch-id: 3318f5c0bb1dac4c932e892329c52c1a118633b4
prerequisite-patch-id: e6aa617911d8647d1b7764c6916e3a01ffed1371
prerequisite-patch-id: 60f8292acf6a200ce4b8f0080a4e7e6429f0f78f
prerequisite-patch-id: e2f2b35b6e7c1aaa6a9b22e563c0f753df5fbe88
prerequisite-patch-id: 70880c12ac3be19b196165cf4a06baf3b6074072
prerequisite-patch-id: 55d302be95eccc06e20360e5b392f72e7249ea76
prerequisite-patch-id: 0fc645d44f0354b6217c3a713d1ac144de7c786f
prerequisite-patch-id: ebf526d3226975950e8997ed5f83f9b11d631aa5
prerequisite-patch-id: ddb73fe6ea6d1e72b379f480b47ec60162352eb7
prerequisite-patch-id: 091d1b77db64fcd1832dbe1db820fd4685f5651b
prerequisite-patch-id: 30006d3af6ae601664ddee4c468520dc800b27bf
prerequisite-patch-id: fcfb1b6ee7b1c7ab5cdd02d71113551c710a21ca
prerequisite-patch-id: 8f225e2d574ffabb6ed842e92543fd0aab52fa19
prerequisite-patch-id: cf3c7778997c4ca97ab08b195cfe861c565f504c
prerequisite-patch-id: 0757c9df4b98726a778f260c4c8ac12f764a2a66
prerequisite-patch-id: 60d1cbbfad4650cce75ffea3253629e8bd9a512c
prerequisite-patch-id: 6657313f73334d9d56215ce286cb0d96674a9c9d
prerequisite-patch-id: 352c99d07523c55e000b9df37a938da22bfedb9f
prerequisite-patch-id: 27176966d0b2d1b98804f3de159a39fcbff0dc9a
prerequisite-patch-id: 6d0311021d93c54e5a306af2d40f43b482a7ee66
prerequisite-patch-id: b5d02eeea8fd63acf76bf7ad5235c20e68858d16
prerequisite-patch-id: f169a13a30c2b09839055a91dd8853c476e8c3f7
prerequisite-patch-id: 14df2ff4090dc2b7518be5a48feec7711628e0d4
prerequisite-patch-id: f4d3503460646e4accd4c54f44f9593816fc62a0
prerequisite-patch-id: 9205bc688a48d7ab778b6bd8454890e1fb7cc039
prerequisite-patch-id: 48513af66ec62d400de1d559f7e75327610b5686
prerequisite-patch-id: 2169938ae51c00399f010f88027bf70b05a1d90f
prerequisite-patch-id: 2b9450e957e69b30414bbf0e1cc3caebd334f654
prerequisite-patch-id: 0c7d6e599af288ca298bbc59a08246715982a7ab
prerequisite-patch-id: a2c1416d88c502ef5b7d621325e2f6712e56a3f4
prerequisite-patch-id: bb6d17d263aa6dab21e87887c98b535595e25c1f
prerequisite-patch-id: f7fa011b9e279104d8c8e2bf09fb5d3ab3f34b67
prerequisite-patch-id: 8358ac66a9d65695fb2bc02637cfddf4695ddeaa
prerequisite-patch-id: 665d9352bf8e6ffc4b7c0ff25659468b5262d4e7
prerequisite-patch-id: 307e544949317538681dc124fdf2b33df538d897
prerequisite-patch-id: 31616804feb9f7b6d5e010d5306b0304005d5efe
prerequisite-patch-id: f8065fb1765262737c2222ad80d2f3d60454e955
prerequisite-patch-id: d99c3d909e24f1ca3269c1e08034f7936f3a5622
prerequisite-patch-id: 0e1efe816412e68314ec226f80b2ded7d2fb33a0
prerequisite-patch-id: 1cf9b69fe0847e9961756cbd69858aecbdab9d1e
--
2.32.0


2022-03-17 11:13:37

by Vasant Karasulli

[permalink] [raw]
Subject: [PATCH v5 1/1] exfat: allow access to paths with trailing dots

The Linux kernel exfat driver currently unconditionally strips
trailing periods '.' from path components. This isdone intentionally,
loosely following Windows behaviour and specifications
which state:

#exFAT
The concatenated file name has the same set of illegal characters as
other FAT-based file systems (see Table 31).

#FAT
...
Leading and trailing spaces in a long name are ignored.
Leading and embedded periods are allowed in a name and are stored in
the long name. Trailing periods are ignored.

Note: Leading and trailing space ' ' characters are currently retained
by Linux kernel exfat, in conflict with the above specification.
On Windows 10, trailing and leading space ' ' characters are stripped
from the filenames.
Some implementations, such as fuse-exfat, don't perform path trailer
removal. When mounting images which contain trailing-dot paths, these
paths are unreachable, e.g.:

+ mount.exfat-fuse /dev/zram0 /mnt/test/
FUSE exfat 1.3.0
+ cd /mnt/test/
+ touch fuse_created_dots... ' fuse_created_spaces '
+ ls -l
total 0
-rwxrwxrwx 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
-rwxrwxrwx 1 root 0 0 Aug 18 09:45 fuse_created_dots...
+ cd /
+ umount /mnt/test/
+ mount -t exfat /dev/zram0 /mnt/test
+ cd /mnt/test
+ ls -l
ls: cannot access 'fuse_created_dots...': No such file or directory
total 0
-rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
-????????? ? ? ? ? ? fuse_created_dots...
+ touch kexfat_created_dots... ' kexfat_created_spaces '
+ ls -l
ls: cannot access 'fuse_created_dots...': No such file or directory
total 0
-rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
-rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' kexfat_created_spaces '
-????????? ? ? ? ? ? fuse_created_dots...
-rwxr-xr-x 1 root 0 0 Aug 18 09:45 kexfat_created_dots
+ cd /
+ umount /mnt/test/

This commit adds "keep_last_dots" mount option that controls whether or
not trailing periods '.' are stripped
from path components during file lookup or file creation.
This mount option can be used to access
paths with trailing periods and disallow creating files with names with
trailing periods. E.g. continuing from the previous example:

+ mount -t exfat -o keep_last_dots /dev/zram0 /mnt/test
+ cd /mnt/test
+ ls -l
total 0
-rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' fuse_created_spaces '
-rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' kexfat_created_spaces '
-rwxr-xr-x 1 root 0 0 Aug 18 10:32 fuse_created_dots...
-rwxr-xr-x 1 root 0 0 Aug 18 10:32 kexfat_created_dots

+ echo > kexfat_created_dots_again...
sh: kexfat_created_dots_again...: Invalid argument

Link: https://bugzilla.suse.com/show_bug.cgi?id=1188964
Link: https://lore.kernel.org/linux-fsdevel/003b01d755e4$31fb0d80$95f12880$
@samsung.com/
Link: https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification
Suggested-by: Takashi Iwai <[email protected]>
Signed-off-by: Vasant Karasulli <[email protected]>
Co-developed-by: David Disseldorp <[email protected]>
Signed-off-by: David Disseldorp <[email protected]>
---
fs/exfat/exfat_fs.h | 3 ++-
fs/exfat/namei.c | 50 ++++++++++++++++++++++++++++++++-------------
fs/exfat/super.c | 7 +++++++
3 files changed, 45 insertions(+), 15 deletions(-)

diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index 619e5b4bed10..c6800b880920 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -203,7 +203,8 @@ struct exfat_mount_options {
/* on error: continue, panic, remount-ro */
enum exfat_error_mode errors;
unsigned utf8:1, /* Use of UTF-8 character set */
- discard:1; /* Issue discard requests on deletions */
+ discard:1, /* Issue discard requests on deletions */
+ keep_last_dots:1; /* Keep trailing periods in paths */
int time_offset; /* Offset of timestamps from UTC (in minutes) */
};

diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index af4eb39cc0c3..a4f8010fbd38 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -65,11 +65,14 @@ static int exfat_d_revalidate(struct dentry *dentry, unsigned int flags)
return ret;
}

-/* returns the length of a struct qstr, ignoring trailing dots */
-static unsigned int exfat_striptail_len(unsigned int len, const char *name)
+/* returns the length of a struct qstr, ignoring trailing dots if necessary */
+static unsigned int exfat_striptail_len(unsigned int len, const char *name,
+ bool keep_last_dots)
{
- while (len && name[len - 1] == '.')
- len--;
+ if (!keep_last_dots) {
+ while (len && name[len - 1] == '.')
+ len--;
+ }
return len;
}

@@ -83,7 +86,8 @@ static int exfat_d_hash(const struct dentry *dentry, struct qstr *qstr)
struct super_block *sb = dentry->d_sb;
struct nls_table *t = EXFAT_SB(sb)->nls_io;
const unsigned char *name = qstr->name;
- unsigned int len = exfat_striptail_len(qstr->len, qstr->name);
+ unsigned int len = exfat_striptail_len(qstr->len, qstr->name,
+ EXFAT_SB(sb)->options.keep_last_dots);
unsigned long hash = init_name_hash(dentry);
int i, charlen;
wchar_t c;
@@ -104,8 +108,10 @@ static int exfat_d_cmp(const struct dentry *dentry, unsigned int len,
{
struct super_block *sb = dentry->d_sb;
struct nls_table *t = EXFAT_SB(sb)->nls_io;
- unsigned int alen = exfat_striptail_len(name->len, name->name);
- unsigned int blen = exfat_striptail_len(len, str);
+ unsigned int alen = exfat_striptail_len(name->len, name->name,
+ EXFAT_SB(sb)->options.keep_last_dots);
+ unsigned int blen = exfat_striptail_len(len, str,
+ EXFAT_SB(sb)->options.keep_last_dots);
wchar_t c1, c2;
int charlen, i;

@@ -136,7 +142,8 @@ static int exfat_utf8_d_hash(const struct dentry *dentry, struct qstr *qstr)
{
struct super_block *sb = dentry->d_sb;
const unsigned char *name = qstr->name;
- unsigned int len = exfat_striptail_len(qstr->len, qstr->name);
+ unsigned int len = exfat_striptail_len(qstr->len, qstr->name,
+ EXFAT_SB(sb)->options.keep_last_dots);
unsigned long hash = init_name_hash(dentry);
int i, charlen;
unicode_t u;
@@ -161,8 +168,11 @@ static int exfat_utf8_d_cmp(const struct dentry *dentry, unsigned int len,
const char *str, const struct qstr *name)
{
struct super_block *sb = dentry->d_sb;
- unsigned int alen = exfat_striptail_len(name->len, name->name);
- unsigned int blen = exfat_striptail_len(len, str);
+ unsigned int alen = exfat_striptail_len(name->len, name->name,
+ EXFAT_SB(sb)->options.keep_last_dots);
+ unsigned int blen = exfat_striptail_len(len, str,
+ EXFAT_SB(sb)->options.keep_last_dots);
+
unicode_t u_a, u_b;
int charlen, i;

@@ -416,13 +426,25 @@ static int __exfat_resolve_path(struct inode *inode, const unsigned char *path,
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode);
+ int pathlen = strlen(path);

- /* strip all trailing periods */
- namelen = exfat_striptail_len(strlen(path), path);
+ /*
+ * get the length of the pathname excluding
+ * trailing periods, if any.
+ */
+ namelen = exfat_striptail_len(pathlen, path, false);
+ if (EXFAT_SB(sb)->options.keep_last_dots) {
+ /*
+ * Do not allow the creation of files with names
+ * ending with period(s).
+ */
+ if (!lookup && (namelen < pathlen))
+ return -EINVAL;
+ namelen = pathlen;
+ }
if (!namelen)
return -ENOENT;
-
- if (strlen(path) > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE))
+ if (pathlen > (MAX_NAME_LENGTH * MAX_CHARSET_SIZE))
return -ENAMETOOLONG;

/*
diff --git a/fs/exfat/super.c b/fs/exfat/super.c
index 8c9fb7dcec16..4c3f80ed17b1 100644
--- a/fs/exfat/super.c
+++ b/fs/exfat/super.c
@@ -174,6 +174,8 @@ static int exfat_show_options(struct seq_file *m, struct dentry *root)
seq_puts(m, ",errors=remount-ro");
if (opts->discard)
seq_puts(m, ",discard");
+ if (opts->keep_last_dots)
+ seq_puts(m, ",keep_last_dots");
if (opts->time_offset)
seq_printf(m, ",time_offset=%d", opts->time_offset);
return 0;
@@ -217,6 +219,7 @@ enum {
Opt_charset,
Opt_errors,
Opt_discard,
+ Opt_keep_last_dots,
Opt_time_offset,

/* Deprecated options */
@@ -243,6 +246,7 @@ static const struct fs_parameter_spec exfat_parameters[] = {
fsparam_string("iocharset", Opt_charset),
fsparam_enum("errors", Opt_errors, exfat_param_enums),
fsparam_flag("discard", Opt_discard),
+ fsparam_flag("keep_last_dots", Opt_keep_last_dots),
fsparam_s32("time_offset", Opt_time_offset),
__fsparam(NULL, "utf8", Opt_utf8, fs_param_deprecated,
NULL),
@@ -297,6 +301,9 @@ static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param)
case Opt_discard:
opts->discard = 1;
break;
+ case Opt_keep_last_dots:
+ opts->keep_last_dots = 1;
+ break;
case Opt_time_offset:
/*
* Make the limit 24 just in case someone invents something
--
2.32.0

2022-03-17 19:54:09

by Namjae Jeon

[permalink] [raw]
Subject: Re: [PATCH v5 1/1] exfat: allow access to paths with trailing dots

2022-03-17 18:50 GMT+09:00, Vasant Karasulli <[email protected]>:
> The Linux kernel exfat driver currently unconditionally strips
> trailing periods '.' from path components. This isdone intentionally,
> loosely following Windows behaviour and specifications
> which state:
>
> #exFAT
> The concatenated file name has the same set of illegal characters as
> other FAT-based file systems (see Table 31).
>
> #FAT
> ...
> Leading and trailing spaces in a long name are ignored.
> Leading and embedded periods are allowed in a name and are stored in
> the long name. Trailing periods are ignored.
>
> Note: Leading and trailing space ' ' characters are currently retained
> by Linux kernel exfat, in conflict with the above specification.
> On Windows 10, trailing and leading space ' ' characters are stripped
> from the filenames.
> Some implementations, such as fuse-exfat, don't perform path trailer
> removal. When mounting images which contain trailing-dot paths, these
> paths are unreachable, e.g.:
>
> + mount.exfat-fuse /dev/zram0 /mnt/test/
> FUSE exfat 1.3.0
> + cd /mnt/test/
> + touch fuse_created_dots... ' fuse_created_spaces '
> + ls -l
> total 0
> -rwxrwxrwx 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
> -rwxrwxrwx 1 root 0 0 Aug 18 09:45 fuse_created_dots...
> + cd /
> + umount /mnt/test/
> + mount -t exfat /dev/zram0 /mnt/test
> + cd /mnt/test
> + ls -l
> ls: cannot access 'fuse_created_dots...': No such file or directory
> total 0
> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
> -????????? ? ? ? ? ? fuse_created_dots...
> + touch kexfat_created_dots... ' kexfat_created_spaces '
> + ls -l
> ls: cannot access 'fuse_created_dots...': No such file or directory
> total 0
> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' fuse_created_spaces '
> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 ' kexfat_created_spaces '
> -????????? ? ? ? ? ? fuse_created_dots...
> -rwxr-xr-x 1 root 0 0 Aug 18 09:45 kexfat_created_dots
> + cd /
> + umount /mnt/test/
>
> This commit adds "keep_last_dots" mount option that controls whether or
> not trailing periods '.' are stripped
> from path components during file lookup or file creation.
> This mount option can be used to access
> paths with trailing periods and disallow creating files with names with
> trailing periods. E.g. continuing from the previous example:
>
> + mount -t exfat -o keep_last_dots /dev/zram0 /mnt/test
> + cd /mnt/test
> + ls -l
> total 0
> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' fuse_created_spaces '
> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 ' kexfat_created_spaces '
> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 fuse_created_dots...
> -rwxr-xr-x 1 root 0 0 Aug 18 10:32 kexfat_created_dots
>
> + echo > kexfat_created_dots_again...
> sh: kexfat_created_dots_again...: Invalid argument
>
> Link: https://bugzilla.suse.com/show_bug.cgi?id=1188964
> Link: https://lore.kernel.org/linux-fsdevel/003b01d755e4$31fb0d80$95f12880$
> @samsung.com/
> Link:
> https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification
> Suggested-by: Takashi Iwai <[email protected]>
> Signed-off-by: Vasant Karasulli <[email protected]>
> Co-developed-by: David Disseldorp <[email protected]>
> Signed-off-by: David Disseldorp <[email protected]>
Applied, Thanks for your patch!