Skip to contentSkip to conversation

FAT extension filters

RFC In review Malcolm Smith 5 participants 8 messages May 15, 2002 – May 16, 2002 VFAT/FAT/msdos filesystem

lore
Share & tools
lore
#0 lore raw

RFC 1/1 FAT extension filters

Gordon/All,

(Sorry if I'm doing stupid things - I'm a newbie. Send me a private email and I'll fix them.)

This is a patch that adds an extra mount option for msdos/vfat partitions, which allows you to specify a specific umask/uid/gid for files with a particular extension. Supports multiple filters using linked list. Note that this does not provide security on an inherently insecure fs.

Use -o filter=ext[:[umask][:[uid][:[gid]]]]

This patch is for kernel 2.5.15
- Malcolm

fs/fat/inode.c Tue May 14 22:49:29 2002 renamed from fs/fat/inode.c Tue May 14 21:38:20 2002 +30 -0
diff -Nur linus-2.5/fs/fat/inode.c linux/fs/fat/inode.c
@@ -169,6 +169,53 @@
        unlock_kernel();
 }
 
+/*
+ * Deletes all the elements from the linked list of filters.
+ */
+void fat_clear_filter(struct fat_filter_data *filter)
+{
+       struct fat_filter_data *this,*next;
+       this=filter;
+       while (this) {
+               next=this->next;
+               kfree(this);
+               this=next;
+       }
+}
+
+/*
+ * Loads a filter option into the filters linked list.
+ */
+void fat_load_filter(struct fat_filter_data *filter, char *value)
+{
+       char *tmp_pointer;
+       tmp_pointer = strchr(value, ':');
+       if (tmp_pointer) *tmp_pointer = '\0';
+       strncpy(filter->extension,value,3);
+       filter->next = NULL;
+       filter->mask_umask = 0;
+       filter->mask_uid = 0;
+       filter->mask_gid = 0;
+       if (tmp_pointer) {
+       if (tmp_pointer[1] != ':') {
+               filter->filter_umask = simple_strtoul(tmp_pointer + 1,

NULL, 8);
+ filter->mask_umask = 1;

+       }
+       tmp_pointer = strchr(tmp_pointer + 1, ':');
+       if (tmp_pointer) {
+       if (tmp_pointer[1] != ':') {
+               filter->filter_uid = simple_strtoul(tmp_pointer + 1,
NULL, 0);
+               filter->mask_uid = 1;
+       }
+       tmp_pointer = strchr(tmp_pointer + 1, ':');
+       if (tmp_pointer) {
+               filter->filter_gid = simple_strtoul(tmp_pointer + 1,
NULL, 0);
+               filter->mask_gid = 1;
+       }
+       }
+       }
+}
+
 void fat_put_super(struct super_block *sb)
 {
        struct msdos_sb_info *sbi = MSDOS_SB(sb);
@@ -196,6 +243,10 @@
                kfree(sbi->options.iocharset);
                sbi->options.iocharset = NULL;
        }

+ if (sbi->options.filter) {
+ fat_clear_filter(sbi->options.filter);
+ sbi->options.filter = NULL;
+ }

        sb->u.generic_sbp = NULL;
        kfree(sbi);
 }
@@ -220,6 +271,7 @@
        opts->shortname = 0;
        opts->utf8 = 0;
        opts->iocharset = NULL;
+       opts->filter = NULL;
        *debug = 0;

        if (!options)
@@ -256,6 +308,27 @@
                                opts->conversion = 'a';
                        else ret = 0;
                }
+               else if (!strcmp(this_char,"filter") && value) {
+                       struct fat_filter_data *cur_filter;
+                       /* Allocates memory in the list for a filter */
+                       if (opts->filter == NULL) {

+ opts->filter = kmalloc(
+ sizeof(struct fat_filter_data),
+ GFP_KERNEL);
+ cur_filter=opts->filter;
+ } else {
+ cur_filter = opts->filter;
+ while (cur_filter->next != NULL)
+ cur_filter = cur_filter->next;
+ cur_filter->next =
+ kmalloc(sizeof(struct
fat_filter_data),
+ GFP_KERNEL);
+ cur_filter = cur_filter->next;

+                       }
+                       /* Processes the filter option itself */
+                       if (cur_filter)
+                               fat_load_filter(cur_filter, value);
+               }
                else if (!strcmp(this_char,"dots")) {
                        opts->dotsOK = 1;
                }
@@ -920,6 +993,10 @@
                unload_nls(sbi->nls_disk);
        if (sbi->options.iocharset)
                kfree(sbi->options.iocharset);

+ if (sbi->options.filter) {
+ fat_clear_filter(sbi->options.filter);
+ sbi->options.filter = NULL;
+ }

        if (sbi->private_data)
                kfree(sbi->private_data);
        sbi->private_data = NULL;
@@ -1001,6 +1078,7 @@
 {
        struct super_block *sb = inode->i_sb;
        struct msdos_sb_info *sbi = MSDOS_SB(sb);
+       struct fat_filter_data *filter;
        int error;

        MSDOS_I(inode)->i_location = 0;
@@ -1042,6 +1120,20 @@
                       !is_exec(de->ext))
                        ? S_IRUGO|S_IWUGO : S_IRWXUGO)
                    & ~sbi->options.fs_umask) | S_IFREG;

+ /* Check if the inode's extension needs special
treatment */
+ filter = fat_is_filtered(de->ext, &sbi->options);
+ if (filter) {
+ if (filter->mask_uid)
+ inode->i_uid = filter->filter_uid;
+ if (filter->mask_gid)
+ inode->i_gid = filter->filter_gid;
+ if (filter->mask_umask)
+ inode->i_mode = MSDOS_MKMODE(de->attr,
+ ((sbi->options.showexec &&
+ !is_exec(de->ext))
+ ? S_IRUGO|S_IWUGO : S_IRWXUGO)
+ & ~filter->filter_umask) | S_IFREG;
+ }

                MSDOS_I(inode)->i_start = CF_LE_W(de->start);
                if (sbi->fat_bits == 32) {
                        MSDOS_I(inode)->i_start |=
fs/fat/misc.c Tue May 14 22:36:53 2002 renamed from fs/fat/misc.c Tue May 14 21:37:24 2002 +2 -0
diff -Nur linus-2.5/fs/fat/misc.c linux/fs/fat/misc.c
@@ -80,6 +80,19 @@
        }
 }
 
+/*
+ * fat_is_filtered returns nonzero if the file should be treated

specially.
+ */
+
+struct fat_filter_data *fat_is_filtered(char *extension,
+ struct fat_mount_options * opts)
+{
+ struct fat_filter_data *walk;
+ for (walk = opts->filter; walk; walk = walk->next)
+ if (!strncmp(extension,walk->extension,3)) return walk;
+ return NULL;
+}
+
void lock_fat(struct super_block *sb)

 {
        down(&(MSDOS_SB(sb)->fat_lock));

diff -Nur linus-2.5/include/linux/msdos_fs.h
linux/include/linux/msdos_fs.h

include/linux/msdos_fs.h Tue May 14 22:57:47 2002 renamed from include/linux/msdos_fs.h Tue May 14 21:45:55 2002 +3 -0
@@ -299,6 +299,9 @@
 /* fat/misc.c */
 extern void fat_fs_panic(struct super_block *s, const char *fmt, ...);
 extern int fat_is_binary(char conversion, char *extension);
+extern struct fat_filter_data *
+fat_is_filtered(char *extension,
+               struct fat_mount_options *opts);
 extern void lock_fat(struct super_block *sb);
 extern void unlock_fat(struct super_block *sb);
 extern void fat_clusters_flush(struct super_block *sb);

diff -Nur linus-2.5/include/linux/msdos_fs_sb.h
linux/include/linux/msdos_fs_sb.h
--- linus-2.5/include/linux/msdos_fs_sb.h Thu May 2 23:06:24 2002

+++ linux/include/linux/msdos_fs_sb.h Tue May 14 22:54:32 2002
@@ -3,6 +3,20 @@
#include<linux/fat_cvf.h>

/*
+ * Specifies a filter to be applied to a specific file's extension.
+ */
+struct fat_filter_data {
+ char extension[4]; /* The extension to be filtered
*/

+       uid_t filter_uid;               /* The uid of this filter */
+       gid_t filter_gid;               /* The gid of this filter */
+       unsigned short filter_umask;    /* The umask of this filter */
+       unsigned mask_umask:1,          /* Is the umask applied? */
+                mask_uid:1,            /* Is the uid applied? */
+                mask_gid:1;            /* Is the gid applied? */
+       struct fat_filter_data *next;   /* The next filter */

+};
+
+/*
* MS-DOS file system in-core superblock data
*/

@@ -12,6 +26,7 @@
unsigned short fs_umask;
unsigned short codepage; /* Codepage for shortname conversions
*/
char *iocharset; /* Charset used for filename
input/display */
+ struct fat_filter_data *filter; /* List of extensions to be
excised */
unsigned short shortname; /* flags for shortname display/create
rule */
unsigned char name_check; /* r = relaxed, n = normal, s = strict
*/
unsigned char conversion; /* b = binary, t = text, a = auto */

#1 lore raw
Quoted 10 lines

(Sorry if I'm doing stupid things - I'm a newbie. Send me a private email and I'll fix them.)

This is a patch that adds an extra mount option for msdos/vfat partitions, which allows you to specify a specific umask/uid/gid for files with a particular extension. Supports multiple filters using linked list. Note that this does not provide security on an inherently insecure fs.

Use -o filter=ext[:[umask][:[uid][:[gid]]]]

Some ideas:

1. use this to express the existing showexec option
2. make this work for all non-UNIX filesystems
3. specify the data some other place

While you're hacking around FAT stuff, see about using the timestamp of the volume name file for the root directory. Remember that very old filesystems won't have a volume name file, so you might need to create one.

Also I found a bug in the vfat code. It doesn't properly handle old (pre-vfat) files with names that start with 0xE5; these are stored on disk with 0x05 as the first character.

maintainer
#2 lore raw
Quoted 4 lines · Albert D. Cahalan

Also I found a bug in the vfat code. It doesn't properly handle old (pre-vfat) files with names that start with 0xE5; these are stored on disk with 0x05 as the first character.

Umm... why? 0xE5 is free entry mark.

Signature · 1 line
OGAWA Hirofumi <[email protected]>
#3 lore raw
Quoted 8 lines · OGAWA Hirofumi
Quoted 4 lines · Albert D. Cahalan

Also I found a bug in the vfat code. It doesn't properly handle old (pre-vfat) files with names that start with 0xE5; these are stored on disk with 0x05 as the first character.

Umm... why? 0xE5 is free entry mark.

That is exactly why! You can store a name that starts with 0xE5 by substituting 0x05. You can not store a file that starts with 0x05, because it would appear to start with 0xE5 when you read it back.

Using plain old MS-DOS, or Linux right before the vfat code was merged, create a file with this name:

E5 44 05 44 E5 44 44 44 44 E5 05

On disk it gets stored as this:

05 44 05 44 E5 44 44 44 44 E5 05
^^

Now remount or reboot so you don't cheat by accident. Do an "ls -l" and note that you see the original filename. The 0xE5 is at the beginning of the name, and the 0x05 in the middle has not been mangled.

Using vfat is not supposed to make old msdos files inaccessible. I discovered this problem while trying to make a backup of my old FAT partition. Even "ls -l" would fail.

I think the problem is in fs/fat/dir.c where it does:

        for (i = 0; i < 8; i++) {
                /* see namei.c, msdos_format_name */
                if (de->name[i] == 0x05)
                        work[i] = 0xE5;
                else
                        work[i] = de->name[i];
        }

That should be:

        for (i = 0; i < 8; i++) work[i] = 0xE5;
         /* see namei.c, msdos_format_name */
        if (*work == 0x05) *work = 0xE5;
maintainer
#4 lore raw
Quoted 7 lines · Albert D. Cahalan

Using plain old MS-DOS, or Linux right before the vfat code was merged, create a file with this name:

E5 44 05 44 E5 44 44 44 44 E5 05

On disk it gets stored as this:

Quoted 8 lines

05 44 05 44 E5 44 44 44 44 E5 05
^^

Now remount or reboot so you don't cheat by accident. Do an "ls -l" and note that you see the original filename. The 0xE5 is at the beginning of the name, and the 0x05 in the middle has not been mangled.

Ah, yes. Indeed.

I'll submit the following patch to Linus. Thanks.

fs/fat/dir.c +8 -14
diff -urN linux-bk/fs/fat/dir.c linux-2.5.14/fs/fat/dir.c
@@ -271,13 +271,10 @@
 				long_slots = 0;
 		}
 
-		for (i = 0; i < 8; i++) {
-			/* see namei.c, msdos_format_name */
-			if (de->name[i] == 0x05)
-				work[i] = 0xE5;
-			else
-				work[i] = de->name[i];
-		}
+		/* see namei.c, msdos_format_name */
+		memcpy(work, de->name, sizeof(de->name));
+		if (de->name[0] == 0x05)
+			work[0] = 0xE5;
 		for (i = 0, j = 0, last_u = 0; i < 8;) {
 			if (!work[i]) break;
 			chl = fat_shortname2uni(nls_disk, &work[i], 8 - i,
@@ -478,13 +475,10 @@
 		dotoffset = 1;
 	}
 
-	for (i = 0; i < 8; i++) {
-		/* see namei.c, msdos_format_name */
-		if (de->name[i] == 0x05)
-			work[i] = 0xE5;
-		else
-			work[i] = de->name[i];
-	}
+	/* see namei.c, msdos_format_name */
+	memcpy(work, de->name, sizeof(de->name));
+	if (de->name[0] == 0x05)
+		work[0] = 0xE5;
 	for (i = 0, j = 0, last = 0, last_u = 0; i < 8;) {
 		if (!(c = work[i])) break;
 		chl = fat_shortname2uni(nls_disk, &work[i], 8 - i,
Signature · 1 line
OGAWA Hirofumi <[email protected]>
#5 lore raw
In reply to OGAWA Hirofumi
Quoted 13 lines · Albert D. Cahalan

I think the problem is in fs/fat/dir.c where
it does:

        for (i = 0; i < 8; i++) {
                /* see namei.c, msdos_format_name */
                if (de->name[i] == 0x05)
                        work[i] = 0xE5;
                else
                        work[i] = de->name[i];
        }
That should be:
        for (i = 0; i < 8; i++) work[i] = 0xE5;
         /* see namei.c, msdos_format_name */
        if (*work == 0x05) *work = 0xE5;

I assume that should be:

Quoted 3 lines
        for (i = 0; i < 8; i++) work[i] = de->name[i];
         /* see namei.c, msdos_format_name */
        if (*work == 0x05) *work = 0xE5;

The comment from msdos/namei.c reads:

  /*  0xE5 is legal as a first character, but we must substitute 0x05     */
  /*  because 0xE5 marks deleted files.  Yes, DOS really does this.       */
  /*  It seems that Microsoft hacked DOS to support non-US characters     */
  /*  after the 0xE5 character was already in use to mark deleted files.  */

A question for the long-time kernel developers who are kind enough to spend time on a question such as this:

Should the code:

Quoted 1 line
        for (i = 0; i < 8; i++) work[i] = de->name[i];

Be written as some sort of memcpy()? I would expect the inlined version to do two 32-bit copies, or one 64-bit copy operation for 64-bit platforms for the above piece of code. Is much of the code written as the above? Or is effort made to try and inline it in some better architecture-specific way?

mark

Signature · 9 lines
[email protected]/[email protected]/[email protected] __________________________
.  .  _  ._  . .   .__    .  . ._. .__ .   . . .__  | Neighbourhood Coder
|\/| |_| |_| |/    |_     |\/|  |  |_  |   |/  |_   | 
|  | | | | \ | \   |__ .  |  | .|. |__ |__ | \ |__  | Ottawa, Ontario, Canada

  One ring to rule them all, one ring to find them, one ring to bring them all
                       and in the darkness bind them...

                           http://mark.mielke.cc/
#6 lore raw
Quoted 16 lines · Mark Mielke
Quoted 13 lines · Albert D. Cahalan

I think the problem is in fs/fat/dir.c where
it does:

        for (i = 0; i < 8; i++) {
                /* see namei.c, msdos_format_name */
                if (de->name[i] == 0x05)
                        work[i] = 0xE5;
                else
                        work[i] = de->name[i];
        }
That should be:
        for (i = 0; i < 8; i++) work[i] = 0xE5;
         /* see namei.c, msdos_format_name */
        if (*work == 0x05) *work = 0xE5;
Quoted 5 lines

I assume that should be:

Quoted 3 lines
        for (i = 0; i < 8; i++) work[i] = de->name[i];
         /* see namei.c, msdos_format_name */
        if (*work == 0x05) *work = 0xE5;

Sure, and using memcpy() might be good too. I wasn't paying much attention. Here:

        memcpy(work,de->name,8);
        /* see namei.c, msdos_format_name */
        if(*work == 0x05) *work = 0xE5;
#7 lore raw
In reply to Albert D. Cahalan
Quoted 4 lines · Albert D. Cahalan

Also I found a bug in the vfat code. It doesn't properly handle old (pre-vfat) files with names that start with 0xE5; these are stored on disk with 0x05 as the first character.

Isn't this a deleted file ?