fs/ntfs3: Add windows_names mount option
authorDaniel Pinto <danielpinto52@gmail.com>
Mon, 10 Oct 2022 11:14:31 +0000 (12:14 +0100)
committerKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Sat, 12 Nov 2022 17:59:42 +0000 (20:59 +0300)
When enabled, the windows_names mount option prevents the creation
of files or directories with names not allowed by Windows. Use
the same option name as NTFS-3G for compatibility.

Signed-off-by: Daniel Pinto <danielpinto52@gmail.com>
Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
fs/ntfs3/frecord.c
fs/ntfs3/fsntfs.c
fs/ntfs3/inode.c
fs/ntfs3/ntfs_fs.h
fs/ntfs3/super.c

index 70a80f9..ce5e8f3 100644 (file)
@@ -3011,6 +3011,7 @@ int ni_add_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
                struct NTFS_DE *de)
 {
        int err;
+       struct ntfs_sb_info *sbi = ni->mi.sbi;
        struct ATTRIB *attr;
        struct ATTR_LIST_ENTRY *le;
        struct mft_inode *mi;
@@ -3018,6 +3019,10 @@ int ni_add_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
        struct ATTR_FILE_NAME *de_name = (struct ATTR_FILE_NAME *)(de + 1);
        u16 de_key_size = le16_to_cpu(de->key_size);
 
+       if (sbi->options->windows_names &&
+           !valid_windows_name(sbi, (struct le_str *)&de_name->name_len))
+               return -EINVAL;
+
        mi_get_ref(&ni->mi, &de->ref);
        mi_get_ref(&dir_ni->mi, &de_name->home);
 
@@ -3036,7 +3041,7 @@ int ni_add_name(struct ntfs_inode *dir_ni, struct ntfs_inode *ni,
        memcpy(Add2Ptr(attr, SIZEOF_RESIDENT), de_name, de_key_size);
 
        /* Insert new name into directory. */
-       err = indx_insert_entry(&dir_ni->dir, dir_ni, de, ni->mi.sbi, NULL, 0);
+       err = indx_insert_entry(&dir_ni->dir, dir_ni, de, sbi, NULL, 0);
        if (err)
                ni_remove_attr_le(ni, attr, mi, le);
 
index b6e22bc..0992fb2 100644 (file)
@@ -98,6 +98,30 @@ const __le16 WOF_NAME[17] = {
 };
 #endif
 
+static const __le16 CON_NAME[3] = {
+       cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('N'),
+};
+
+static const __le16 NUL_NAME[3] = {
+       cpu_to_le16('N'), cpu_to_le16('U'), cpu_to_le16('L'),
+};
+
+static const __le16 AUX_NAME[3] = {
+       cpu_to_le16('A'), cpu_to_le16('U'), cpu_to_le16('X'),
+};
+
+static const __le16 PRN_NAME[3] = {
+       cpu_to_le16('P'), cpu_to_le16('R'), cpu_to_le16('N'),
+};
+
+static const __le16 COM_NAME[3] = {
+       cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('M'),
+};
+
+static const __le16 LPT_NAME[3] = {
+       cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'),
+};
+
 // clang-format on
 
 /*
@@ -2504,3 +2528,83 @@ int run_deallocate(struct ntfs_sb_info *sbi, struct runs_tree *run, bool trim)
 
        return 0;
 }
+
+static inline bool name_has_forbidden_chars(const struct le_str *fname)
+{
+       int i, ch;
+
+       /* check for forbidden chars */
+       for (i = 0; i < fname->len; ++i) {
+               ch = le16_to_cpu(fname->name[i]);
+
+               /* control chars */
+               if (ch < 0x20)
+                       return true;
+
+               switch (ch) {
+               /* disallowed by Windows */
+               case '\\':
+               case '/':
+               case ':':
+               case '*':
+               case '?':
+               case '<':
+               case '>':
+               case '|':
+               case '\"':
+                       return true;
+
+               default:
+                       /* allowed char */
+                       break;
+               }
+       }
+
+       /* file names cannot end with space or . */
+       if (fname->len > 0) {
+               ch = le16_to_cpu(fname->name[fname->len - 1]);
+               if (ch == ' ' || ch == '.')
+                       return true;
+       }
+
+       return false;
+}
+
+static inline bool is_reserved_name(struct ntfs_sb_info *sbi,
+                                   const struct le_str *fname)
+{
+       int port_digit;
+       const __le16 *name = fname->name;
+       int len = fname->len;
+       u16 *upcase = sbi->upcase;
+
+       /* check for 3 chars reserved names (device names) */
+       /* name by itself or with any extension is forbidden */
+       if (len == 3 || (len > 3 && le16_to_cpu(name[3]) == '.'))
+               if (!ntfs_cmp_names(name, 3, CON_NAME, 3, upcase, false) ||
+                   !ntfs_cmp_names(name, 3, NUL_NAME, 3, upcase, false) ||
+                   !ntfs_cmp_names(name, 3, AUX_NAME, 3, upcase, false) ||
+                   !ntfs_cmp_names(name, 3, PRN_NAME, 3, upcase, false))
+                       return true;
+
+       /* check for 4 chars reserved names (port name followed by 1..9) */
+       /* name by itself or with any extension is forbidden */
+       if (len == 4 || (len > 4 && le16_to_cpu(name[4]) == '.')) {
+               port_digit = le16_to_cpu(name[3]);
+               if (port_digit >= '1' && port_digit <= '9')
+                       if (!ntfs_cmp_names(name, 3, COM_NAME, 3, upcase, false) ||
+                           !ntfs_cmp_names(name, 3, LPT_NAME, 3, upcase, false))
+                               return true;
+       }
+
+       return false;
+}
+
+/*
+ * valid_windows_name - Check if a file name is valid in Windows.
+ */
+bool valid_windows_name(struct ntfs_sb_info *sbi, const struct le_str *fname)
+{
+       return !name_has_forbidden_chars(fname) &&
+              !is_reserved_name(sbi, fname);
+}
index 9c24402..a2522fc 100644 (file)
@@ -1368,6 +1368,13 @@ struct inode *ntfs_create_inode(struct user_namespace *mnt_userns,
        mi_get_ref(&ni->mi, &new_de->ref);
 
        fname = (struct ATTR_FILE_NAME *)(new_de + 1);
+
+       if (sbi->options->windows_names &&
+           !valid_windows_name(sbi, (struct le_str *)&fname->name_len)) {
+               err = -EINVAL;
+               goto out4;
+       }
+
        mi_get_ref(&dir_ni->mi, &fname->home);
        fname->dup.cr_time = fname->dup.m_time = fname->dup.c_time =
                fname->dup.a_time = std5->cr_time;
index 6c1c7ef..ebfb720 100644 (file)
@@ -98,6 +98,7 @@ struct ntfs_mount_options {
        unsigned showmeta : 1; /* Show meta files. */
        unsigned nohidden : 1; /* Do not show hidden files. */
        unsigned hide_dot_files : 1; /* Set hidden flag on dot files. */
+       unsigned windows_names : 1; /* Disallow names forbidden by Windows. */
        unsigned force : 1; /* RW mount dirty volume. */
        unsigned noacsrules : 1; /* Exclude acs rules. */
        unsigned prealloc : 1; /* Preallocate space when file is growing. */
@@ -645,6 +646,7 @@ int ntfs_remove_reparse(struct ntfs_sb_info *sbi, __le32 rtag,
                        const struct MFT_REF *ref);
 void mark_as_free_ex(struct ntfs_sb_info *sbi, CLST lcn, CLST len, bool trim);
 int run_deallocate(struct ntfs_sb_info *sbi, struct runs_tree *run, bool trim);
+bool valid_windows_name(struct ntfs_sb_info *sbi, const struct le_str *name);
 
 /* Globals from index.c */
 int indx_used_bit(struct ntfs_index *indx, struct ntfs_inode *ni, size_t *bit);
index 2fd1367..a91852b 100644 (file)
@@ -248,6 +248,7 @@ enum Opt {
        Opt_sparse,
        Opt_nohidden,
        Opt_hide_dot_files,
+       Opt_windows_names,
        Opt_showmeta,
        Opt_acl,
        Opt_iocharset,
@@ -269,6 +270,7 @@ static const struct fs_parameter_spec ntfs_fs_parameters[] = {
        fsparam_flag_no("sparse",               Opt_sparse),
        fsparam_flag_no("hidden",               Opt_nohidden),
        fsparam_flag_no("hidedotfiles",         Opt_hide_dot_files),
+       fsparam_flag_no("windows_names",        Opt_windows_names),
        fsparam_flag_no("acl",                  Opt_acl),
        fsparam_flag_no("showmeta",             Opt_showmeta),
        fsparam_flag_no("prealloc",             Opt_prealloc),
@@ -361,6 +363,9 @@ static int ntfs_fs_parse_param(struct fs_context *fc,
        case Opt_hide_dot_files:
                opts->hide_dot_files = result.negated ? 1 : 0;
                break;
+       case Opt_windows_names:
+               opts->windows_names = result.negated ? 0 : 1;
+               break;
        case Opt_acl:
                if (!result.negated)
 #ifdef CONFIG_NTFS3_FS_POSIX_ACL
@@ -561,6 +566,8 @@ static int ntfs_show_options(struct seq_file *m, struct dentry *root)
                seq_puts(m, ",showmeta");
        if (opts->nohidden)
                seq_puts(m, ",nohidden");
+       if (opts->windows_names)
+               seq_puts(m, ",windows_names");
        if (opts->force)
                seq_puts(m, ",force");
        if (opts->noacsrules)