2022-03-10 23:46:08

by Vasant Karasulli

[permalink] [raw]
Subject: [PATCH v2 2/2] exfat currently unconditionally strips trailing periods '.' when performing path lookup, but allows them in the filenames during file creation. This is done 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.

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/

With this change, the "keep_last_dots" 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

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/namei.c | 50 ++++++++++++++++++++++++++++++++++--------------
1 file changed, 36 insertions(+), 14 deletions(-)

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;

/*
--
2.32.0


2022-03-11 14:54:01

by Vasant Karasulli

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] exfat currently unconditionally strips trailing periods '.' when performing path lookup, but allows them in the filenames during file creation. This is done intentionally, loosely following Windows behaviour and specifications which state:

On Fr 11-03-22 18:03:57, Namjae Jeon wrote:
> 2022-03-11 5:06 GMT+09:00, David Disseldorp <[email protected]>:
> > Thanks for reworking these changes, Vasant.
> >
> > Please trim the 1/2 and 2/2 patch subjects down to around 50 chars
> > (including a "exfat: " prefix), with the details moved into the commit
> > message body...
> >
> > On Thu, 10 Mar 2022 15:24:55 +0100, Vasant Karasulli wrote:
> >
> >> #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.
> >
> > I think it makes sense to mention your findings from the Windows tests
> > here. E.g. "Windows 10 also retains leading and trailing space
> > characters".
> Windows 10 do also strip them. So you can make another patch to strip
> it as well as trailing periods.
Actually I found contradicting behavior between Window 10 File Explorer and
Commandline. Commandline seems to strip trailing spaces, but File Explorer
doesn't.

> >
> >> 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/
> >>
> >> With this change, the "keep_last_dots" 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
> >
> > It'd be nice to demonstrate "keep_last_dots" creation here as well, e.g.
> >
> > + echo > kexfat_created_dots_again...
> > sh: kexfat_created_dots_again...: Invalid argument
> >
> > @Namjae: not sure whether this is what you had in mind for preventing
> > creation of invalid paths. What's your preference?
> Look like what I wanted.
That's great. I will resend the patch after modifying the commit message
as David suggested.
>
> Thanks!
> >
> > Cheers, David
> >

Thanks,
Vasant Karasulli
Kernel generalist
http://www.suse.com<http://www.suse.com>
[https://www.suse.com/assets/img/social-platforms-suse-logo.png]<http://www.suse.com/>
SUSE - Open Source Solutions for Enterprise Servers & Cloud<http://www.suse.com/>
Modernize your infrastructure with SUSE Linux Enterprise servers, cloud technology for IaaS, and SUSE's software-defined storage.
http://www.suse.com

2022-03-11 15:50:19

by David Disseldorp

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] exfat currently unconditionally strips trailing periods '.' when performing path lookup, but allows them in the filenames during file creation. This is done intentionally, loosely following Windows behaviour and specifications which state:

Thanks for reworking these changes, Vasant.

Please trim the 1/2 and 2/2 patch subjects down to around 50 chars
(including a "exfat: " prefix), with the details moved into the commit
message body...

On Thu, 10 Mar 2022 15:24:55 +0100, Vasant Karasulli wrote:

> #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.

I think it makes sense to mention your findings from the Windows tests
here. E.g. "Windows 10 also retains leading and trailing space
characters".

> 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/
>
> With this change, the "keep_last_dots" 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

It'd be nice to demonstrate "keep_last_dots" creation here as well, e.g.

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

@Namjae: not sure whether this is what you had in mind for preventing
creation of invalid paths. What's your preference?

Cheers, David

2022-03-11 19:08:06

by Namjae Jeon

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] exfat currently unconditionally strips trailing periods '.' when performing path lookup, but allows them in the filenames during file creation. This is done intentionally, loosely following Windows behaviour and specifications which state:

2022-03-11 5:06 GMT+09:00, David Disseldorp <[email protected]>:
> Thanks for reworking these changes, Vasant.
>
> Please trim the 1/2 and 2/2 patch subjects down to around 50 chars
> (including a "exfat: " prefix), with the details moved into the commit
> message body...
>
> On Thu, 10 Mar 2022 15:24:55 +0100, Vasant Karasulli wrote:
>
>> #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.
>
> I think it makes sense to mention your findings from the Windows tests
> here. E.g. "Windows 10 also retains leading and trailing space
> characters".
Windows 10 do also strip them. So you can make another patch to strip
it as well as trailing periods.
>
>> 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/
>>
>> With this change, the "keep_last_dots" 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
>
> It'd be nice to demonstrate "keep_last_dots" creation here as well, e.g.
>
> + echo > kexfat_created_dots_again...
> sh: kexfat_created_dots_again...: Invalid argument
>
> @Namjae: not sure whether this is what you had in mind for preventing
> creation of invalid paths. What's your preference?
Look like what I wanted.

Thanks!
>
> Cheers, David
>

2022-03-15 15:33:40

by [email protected]

[permalink] [raw]
Subject: RE: [PATCH v2 2/2] exfat currently unconditionally strips trailing periods '.' when performing path lookup, but allows them in the filenames during file creation. This is done intentionally, loosely following Windows behaviour and specifications which ...

Hi, Vasant Karasulli.

> > > I think it makes sense to mention your findings from the Windows
> > > tests here. E.g. "Windows 10 also retains leading and trailing space
> > > characters".
> > Windows 10 do also strip them. So you can make another patch to strip
> > it as well as trailing periods.
> Actually I found contradicting behavior between Window 10 File Explorer and Commandline. Commandline seems to strip
> trailing spaces, but File Explorer doesn't.

The exfat specification specifies an invalid character set, but there are no restrictions on the use of leading or trailing white-space or dots.
Even if the filename has trailing-dot as shown below, it conforms to the exfat specification and can be created on Windows.
"a"
"a."
"a.."
These are treated as "a" in the current implementation of linix-exfat, so the intended file cannot be accessed.
The specified filename should not be modified to comply with the exfat specification.
Therefore, exfat_striptail_len() should not be used.

Note:
Windows explorer removes trailing white-space and dots, but not the behavior of the filesystem.
Also, you can create a trailing-dot filename by quoting it on the command line.

BR
T.Kohada

2022-03-17 03:44:52

by David Disseldorp

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] exfat currently unconditionally strips trailing periods '.' when performing path lookup, but allows them in the filenames during file creation. This is done intentionally, loosely following Windows behaviour and specifications which ...

Hi T.Kohada,

On Mon, 14 Mar 2022 03:52:08 +0000, [email protected] wrote:

> Hi, Vasant Karasulli.
>
> > > > I think it makes sense to mention your findings from the Windows
> > > > tests here. E.g. "Windows 10 also retains leading and trailing space
> > > > characters".
> > > Windows 10 do also strip them. So you can make another patch to strip
> > > it as well as trailing periods.
> > Actually I found contradicting behavior between Window 10 File Explorer and Commandline. Commandline seems to strip
> > trailing spaces, but File Explorer doesn't.
>
> The exfat specification specifies an invalid character set, but there are no restrictions on the use of leading or trailing white-space or dots.
> Even if the filename has trailing-dot as shown below, it conforms to the exfat specification and can be created on Windows.
> "a"
> "a."
> "a.."
> These are treated as "a" in the current implementation of linix-exfat, so the intended file cannot be accessed.
> The specified filename should not be modified to comply with the exfat specification.
> Therefore, exfat_striptail_len() should not be used.
>
> Note:
> Windows explorer removes trailing white-space and dots, but not the behavior of the filesystem.
> Also, you can create a trailing-dot filename by quoting it on the command line.

Please explain how you came to that conclusion.
I did some further tests using the win32 CopyFile() API directly[1] on
Windows10 and observe that both trailing periods and trailing spaces are
trimmed for an exfat destination path.

Cheers, David

1: calling win32 CopyFile() from powershell
https://devblogs.microsoft.com/scripting/use-powershell-to-interact-with-the-windows-api-part-1/

2022-03-17 10:04:40

by [email protected]

[permalink] [raw]
Subject: Re: [PATCH v2 2/2] exfat currently unconditionally strips trailing periods '.' when performing path lookup, but allows them in the filenames during file creation. This is done intentionally, loosely following Windows behaviour and specifications which ...

Hi, David.

Thank you for confirming the actual behavior.

> Please explain how you came to that conclusion.
> I did some further tests using the win32 CopyFile() API directly[1] on
> Windows10 and observe that both trailing periods and trailing spaces are
> trimmed for an exfat destination path.

I'm using the native api to investigate the behavior of the filesystem on windows.
This time, I verified it using NtCreateFile().
https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntcreatefile

Cygwin and some tools can also create filenames with a trailing dot.

BR
T.Kohada