diff mbox

[RESEND] implement uid and gid mount options for ext2, ext3 and ext4

Message ID 1336660924-9598-1-git-send-email-ludwig.nussel@suse.de
State Superseded, archived
Headers show

Commit Message

Ludwig Nussel May 10, 2012, 2:42 p.m. UTC
Further development of a patch I sent years ago. I didn't find the
time to address the concerns raised back then and forgot about the
patch. Now here it is again.

When using 'real' file systems on removable storage devices such as
hard disks or usb sticks people quickly face the problem that their
Linux users have different uids on different machines. Therefore one
cannot modify or even read files created on a different machine
without running chown as root or storing everything with mode 777.
Simple file systems such as vfat don't have that problem as they
don't store file ownership information and one can pass the uid
files should belong to as mount option.

The following two patches (for 3.4.0-rc4) implement the uid (and
gid) mount option for ext2, ext3 and ext4 to make them actually
useful on removable media. If a file system is mounted with the uid
option all files appear to be owned by the specified uid. Only newly
created files actually end up with that uid as owner on disk though.
Ownership of existing files cannot be changed permanently if the uid
option was specified.

Acked-by: Rob Landley <rob@landley.net>
Signed-off-by: Ludwig Nussel <ludwig.nussel@suse.de>
---
 Documentation/filesystems/ext2.txt |    9 ++++++
 Documentation/filesystems/ext3.txt |    9 ++++++
 Documentation/filesystems/ext4.txt |    9 ++++++
 fs/ext2/ext2.h                     |    8 +++++
 fs/ext2/inode.c                    |   42 ++++++++++++++++++++------
 fs/ext2/super.c                    |   57 +++++++++++++++++++++++++++++++++++-
 fs/ext3/ext3.h                     |    8 +++++
 fs/ext3/inode.c                    |   50 ++++++++++++++++++++++---------
 fs/ext3/super.c                    |   57 +++++++++++++++++++++++++++++++++++-
 fs/ext4/ext4.h                     |    4 ++
 fs/ext4/inode.c                    |   50 ++++++++++++++++++++++---------
 fs/ext4/super.c                    |   49 ++++++++++++++++++++++++++++++-
 12 files changed, 311 insertions(+), 41 deletions(-)

Comments

Jan Kara May 10, 2012, 3 p.m. UTC | #1
On Thu 10-05-12 16:42:02, Ludwig Nussel wrote:
> Further development of a patch I sent years ago. I didn't find the
> time to address the concerns raised back then and forgot about the
> patch. Now here it is again.
> 
> When using 'real' file systems on removable storage devices such as
> hard disks or usb sticks people quickly face the problem that their
> Linux users have different uids on different machines. Therefore one
> cannot modify or even read files created on a different machine
> without running chown as root or storing everything with mode 777.
> Simple file systems such as vfat don't have that problem as they
> don't store file ownership information and one can pass the uid
> files should belong to as mount option.
> 
> The following two patches (for 3.4.0-rc4) implement the uid (and
> gid) mount option for ext2, ext3 and ext4 to make them actually
> useful on removable media. If a file system is mounted with the uid
> option all files appear to be owned by the specified uid. Only newly
> created files actually end up with that uid as owner on disk though.
> Ownership of existing files cannot be changed permanently if the uid
> option was specified.
  I've looked at this in more detail now. Although it would be nice to
avoid duplicating the the functionality as others suggested, I agree it's
not that simple and it seems to me we'd end up doing similar amount of work
in the filesystems anyway (mount option parsing, writing to disk, printing
mount options, ...). So I'm ok with the implementation as is and I'd be
willing to take it for ext3 / ext2. But we really want to keep all ext?
filesystems consistent so this also depends on Ted's approval of the change
for ext4.  Ted?

  What I'm missing in the changelog description is what i_diskuid/i_diskgid
is good for. Although I can imagine some use case, I'm not sure I can see
any sufficiently convicing one...

  Also please split the patch in three separate patches for ext2, ext3, and
ext4. Thanks.

								Honza
> 
> Acked-by: Rob Landley <rob@landley.net>
> Signed-off-by: Ludwig Nussel <ludwig.nussel@suse.de>
> ---
>  Documentation/filesystems/ext2.txt |    9 ++++++
>  Documentation/filesystems/ext3.txt |    9 ++++++
>  Documentation/filesystems/ext4.txt |    9 ++++++
>  fs/ext2/ext2.h                     |    8 +++++
>  fs/ext2/inode.c                    |   42 ++++++++++++++++++++------
>  fs/ext2/super.c                    |   57 +++++++++++++++++++++++++++++++++++-
>  fs/ext3/ext3.h                     |    8 +++++
>  fs/ext3/inode.c                    |   50 ++++++++++++++++++++++---------
>  fs/ext3/super.c                    |   57 +++++++++++++++++++++++++++++++++++-
>  fs/ext4/ext4.h                     |    4 ++
>  fs/ext4/inode.c                    |   50 ++++++++++++++++++++++---------
>  fs/ext4/super.c                    |   49 ++++++++++++++++++++++++++++++-
>  12 files changed, 311 insertions(+), 41 deletions(-)
> 
> diff --git a/Documentation/filesystems/ext2.txt b/Documentation/filesystems/ext2.txt
> index 67639f9..fcc1002 100644
> --- a/Documentation/filesystems/ext2.txt
> +++ b/Documentation/filesystems/ext2.txt
> @@ -42,6 +42,15 @@ orlov			(*)	Use the Orlov block allocator.
>  resuid=n			The user ID which may use the reserved blocks.
>  resgid=n			The group ID which may use the reserved blocks.
>  
> +uid=n[:m]			Make all files appear to belong to uid n.
> +				Useful for e.g. removable media with fstab
> +				options 'user,uid=useruid'. The optional second
> +				uid m is actually written to the file system.
> +
> +gid=n[:m]			Make all files appear to belong to gid n.
> +				The optional second gid m is actually written to
> +				the file system.
> +
>  sb=n				Use alternate superblock at this location.
>  
>  user_xattr			Enable "user." POSIX Extended Attributes
> diff --git a/Documentation/filesystems/ext3.txt b/Documentation/filesystems/ext3.txt
> index b100adc..b2407a7 100644
> --- a/Documentation/filesystems/ext3.txt
> +++ b/Documentation/filesystems/ext3.txt
> @@ -124,6 +124,15 @@ resgid=n		The group ID which may use the reserved blocks.
>  
>  resuid=n		The user ID which may use the reserved blocks.
>  
> +uid=n[:m]		Make all files appear to belong to uid n.
> +			Useful for e.g. removable media with fstab
> +			options 'user,uid=useruid'. The optional second
> +			uid m is actually written to the file system.
> +
> +gid=n[:m]		Make all files appear to belong to gid n.
> +			The optional second gid m is actually written to
> +			the file system.
> +
>  sb=n			Use alternate superblock at this location.
>  
>  quota			These options are ignored by the filesystem. They
> diff --git a/Documentation/filesystems/ext4.txt b/Documentation/filesystems/ext4.txt
> index 1b7f9ac..b388ab5 100644
> --- a/Documentation/filesystems/ext4.txt
> +++ b/Documentation/filesystems/ext4.txt
> @@ -245,6 +245,15 @@ resgid=n		The group ID which may use the reserved blocks.
>  
>  resuid=n		The user ID which may use the reserved blocks.
>  
> +uid=n[:m]		Make all files appear to belong to uid n.
> +			Useful for e.g. removable media with fstab
> +			options 'user,uid=useruid'. The optional second
> +			uid m is actually written to the file system.
> +
> +gid=n[:m]		Make all files appear to belong to gid n.
> +			The optional second gid m is actually written to
> +			the file system.
> +
>  sb=n			Use alternate superblock at this location.
>  
>  quota			These options are ignored by the filesystem. They
> diff --git a/fs/ext2/ext2.h b/fs/ext2/ext2.h
> index 0b2b4db..b584e45 100644
> --- a/fs/ext2/ext2.h
> +++ b/fs/ext2/ext2.h
> @@ -84,6 +84,10 @@ struct ext2_sb_info {
>  	unsigned long s_sb_block;
>  	uid_t s_resuid;
>  	gid_t s_resgid;
> +	uid_t s_uid;          /* make all files appear to belong to this uid */
> +	uid_t s_diskuid;      /* write this uid to disk (if s_uid != 0) */
> +	gid_t s_gid;          /* make all files appear to belong to this gid */
> +	gid_t s_diskgid;      /* write this gid to disk (if s_gid != 0) */
>  	unsigned short s_mount_state;
>  	unsigned short s_pad;
>  	int s_addr_per_block_bits;
> @@ -639,6 +643,10 @@ struct ext2_mount_options {
>  	unsigned long s_mount_opt;
>  	uid_t s_resuid;
>  	gid_t s_resgid;
> +	uid_t s_uid;
> +	uid_t s_diskuid;
> +	gid_t s_gid;
> +	gid_t s_diskgid;
>  };
>  
>  /*
> diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c
> index 740cad8..aabcb38 100644
> --- a/fs/ext2/inode.c
> +++ b/fs/ext2/inode.c
> @@ -1316,6 +1316,10 @@ struct inode *ext2_iget (struct super_block *sb, unsigned long ino)
>  		inode->i_uid |= le16_to_cpu(raw_inode->i_uid_high) << 16;
>  		inode->i_gid |= le16_to_cpu(raw_inode->i_gid_high) << 16;
>  	}
> +	if (EXT2_SB(sb)->s_uid)
> +		inode->i_uid = EXT2_SB(sb)->s_uid;
> +	if (EXT2_SB(sb)->s_gid)
> +		inode->i_gid = EXT2_SB(sb)->s_gid;
>  	set_nlink(inode, le16_to_cpu(raw_inode->i_links_count));
>  	inode->i_size = le32_to_cpu(raw_inode->i_size);
>  	inode->i_atime.tv_sec = (signed)le32_to_cpu(raw_inode->i_atime);
> @@ -1419,6 +1423,10 @@ static int __ext2_write_inode(struct inode *inode, int do_sync)
>  	struct ext2_inode * raw_inode = ext2_get_inode(sb, ino, &bh);
>  	int n;
>  	int err = 0;
> +	__le16 uid_low;
> +	__le16 gid_low;
> +	__le16 uid_high;
> +	__le16 gid_high;
>  
>  	if (IS_ERR(raw_inode))
>   		return -EIO;
> @@ -1430,26 +1438,40 @@ static int __ext2_write_inode(struct inode *inode, int do_sync)
>  
>  	ext2_get_inode_flags(ei);
>  	raw_inode->i_mode = cpu_to_le16(inode->i_mode);
> +	if (EXT2_SB(sb)->s_uid)
> +		uid = EXT2_SB(sb)->s_diskuid;
> +	if (EXT2_SB(sb)->s_gid)
> +		gid = EXT2_SB(sb)->s_diskgid;
>  	if (!(test_opt(sb, NO_UID32))) {
> -		raw_inode->i_uid_low = cpu_to_le16(low_16_bits(uid));
> -		raw_inode->i_gid_low = cpu_to_le16(low_16_bits(gid));
> +		uid_low = cpu_to_le16(low_16_bits(uid));
> +		gid_low = cpu_to_le16(low_16_bits(gid));
>  /*
>   * Fix up interoperability with old kernels. Otherwise, old inodes get
>   * re-used with the upper 16 bits of the uid/gid intact
>   */
>  		if (!ei->i_dtime) {
> -			raw_inode->i_uid_high = cpu_to_le16(high_16_bits(uid));
> -			raw_inode->i_gid_high = cpu_to_le16(high_16_bits(gid));
> +			uid_high = cpu_to_le16(high_16_bits(uid));
> +			gid_high = cpu_to_le16(high_16_bits(gid));
>  		} else {
> -			raw_inode->i_uid_high = 0;
> -			raw_inode->i_gid_high = 0;
> +			uid_high = 0;
> +			gid_high = 0;
>  		}
>  	} else {
> -		raw_inode->i_uid_low = cpu_to_le16(fs_high2lowuid(uid));
> -		raw_inode->i_gid_low = cpu_to_le16(fs_high2lowgid(gid));
> -		raw_inode->i_uid_high = 0;
> -		raw_inode->i_gid_high = 0;
> +		uid_low = cpu_to_le16(fs_high2lowuid(uid));
> +		gid_low = cpu_to_le16(fs_high2lowgid(gid));
> +		uid_high = 0;
> +		gid_high = 0;
>  	}
> +	/* don't mangle uid/gid of existing files if override is active */
> +	if (!EXT2_SB(sb)->s_uid || ei->i_state & EXT2_STATE_NEW) {
> +		raw_inode->i_uid_high = uid_high;
> +		raw_inode->i_uid_low = uid_low;
> +	}
> +	if (!EXT2_SB(sb)->s_gid || ei->i_state & EXT2_STATE_NEW) {
> +		raw_inode->i_gid_high = gid_high;
> +		raw_inode->i_gid_low = gid_low;
> +	}
> +
>  	raw_inode->i_links_count = cpu_to_le16(inode->i_nlink);
>  	raw_inode->i_size = cpu_to_le32(inode->i_size);
>  	raw_inode->i_atime = cpu_to_le32(inode->i_atime.tv_sec);
> diff --git a/fs/ext2/super.c b/fs/ext2/super.c
> index e1025c7..0661574 100644
> --- a/fs/ext2/super.c
> +++ b/fs/ext2/super.c
> @@ -236,6 +236,20 @@ static int ext2_show_options(struct seq_file *seq, struct dentry *root)
>  	    le16_to_cpu(es->s_def_resgid) != EXT2_DEF_RESGID) {
>  		seq_printf(seq, ",resgid=%u", sbi->s_resgid);
>  	}
> +	if (sbi->s_uid) {
> +		if (sbi->s_uid != sbi->s_diskuid)
> +			seq_printf(seq, ",uid=%u:%u",
> +				sbi->s_uid, sbi->s_diskuid);
> +		else
> +			seq_printf(seq, ",uid=%u", sbi->s_uid);
> +	}
> +	if (sbi->s_gid) {
> +		if (sbi->s_gid != sbi->s_diskgid)
> +			seq_printf(seq, ",gid=%u:%u",
> +				sbi->s_gid, sbi->s_diskgid);
> +		else
> +			seq_printf(seq, ",gid=%u", sbi->s_gid);
> +	}
>  	if (test_opt(sb, ERRORS_RO)) {
>  		int def_errors = le16_to_cpu(es->s_errors);
>  
> @@ -393,7 +407,8 @@ enum {
>  	Opt_err_ro, Opt_nouid32, Opt_nocheck, Opt_debug,
>  	Opt_oldalloc, Opt_orlov, Opt_nobh, Opt_user_xattr, Opt_nouser_xattr,
>  	Opt_acl, Opt_noacl, Opt_xip, Opt_ignore, Opt_err, Opt_quota,
> -	Opt_usrquota, Opt_grpquota, Opt_reservation, Opt_noreservation
> +	Opt_usrquota, Opt_grpquota, Opt_reservation, Opt_noreservation,
> +	Opt_uid, Opt_diskuid, Opt_gid, Opt_diskgid
>  };
>  
>  static const match_table_t tokens = {
> @@ -427,6 +442,10 @@ static const match_table_t tokens = {
>  	{Opt_usrquota, "usrquota"},
>  	{Opt_reservation, "reservation"},
>  	{Opt_noreservation, "noreservation"},
> +	{Opt_uid, "uid=%u"},
> +	{Opt_diskuid, "uid=%u:%u"},
> +	{Opt_gid, "gid=%u"},
> +	{Opt_diskgid, "gid=%u:%u"},
>  	{Opt_err, NULL}
>  };
>  
> @@ -568,6 +587,34 @@ static int parse_options(char *options, struct super_block *sb)
>  			clear_opt(sbi->s_mount_opt, RESERVATION);
>  			ext2_msg(sb, KERN_INFO, "reservations OFF");
>  			break;
> +		case Opt_uid:
> +			if (match_int(&args[0], &option))
> +				return 0;
> +			sbi->s_uid = sbi->s_diskuid = option;
> +			break;
> +		case Opt_diskuid:
> +			if (match_int(&args[0], &option))
> +				return 0;
> +			sbi->s_uid = option;
> +
> +			if (match_int(&args[1], &option))
> +				return 0;
> +			sbi->s_diskuid = option;
> +			break;
> +		case Opt_gid:
> +			if (match_int(&args[0], &option))
> +				return 0;
> +			sbi->s_gid = sbi->s_diskgid = option;
> +			break;
> +		case Opt_diskgid:
> +			if (match_int(&args[0], &option))
> +				return 0;
> +			sbi->s_gid = option;
> +
> +			if (match_int(&args[1], &option))
> +				return 0;
> +			sbi->s_diskgid = option;
> +			break;
>  		case Opt_ignore:
>  			break;
>  		default:
> @@ -1214,6 +1261,10 @@ static int ext2_remount (struct super_block * sb, int * flags, char * data)
>  	old_opts.s_mount_opt = sbi->s_mount_opt;
>  	old_opts.s_resuid = sbi->s_resuid;
>  	old_opts.s_resgid = sbi->s_resgid;
> +	old_opts.s_uid = sbi->s_uid;
> +	old_opts.s_diskuid = sbi->s_diskuid;
> +	old_opts.s_gid = sbi->s_gid;
> +	old_opts.s_diskgid = sbi->s_diskgid;
>  
>  	/*
>  	 * Allow the "check" option to be passed as a remount option.
> @@ -1300,6 +1351,10 @@ restore_opts:
>  	sbi->s_mount_opt = old_opts.s_mount_opt;
>  	sbi->s_resuid = old_opts.s_resuid;
>  	sbi->s_resgid = old_opts.s_resgid;
> +	sbi->s_uid = old_opts.s_uid;
> +	sbi->s_diskuid = old_opts.s_diskuid;
> +	sbi->s_gid = old_opts.s_gid;
> +	sbi->s_diskgid = old_opts.s_diskgid;
>  	sb->s_flags = old_sb_flags;
>  	spin_unlock(&sbi->s_lock);
>  	return err;
> diff --git a/fs/ext3/ext3.h b/fs/ext3/ext3.h
> index b6515fd..c7c4578 100644
> --- a/fs/ext3/ext3.h
> +++ b/fs/ext3/ext3.h
> @@ -245,6 +245,10 @@ struct ext3_mount_options {
>  	unsigned long s_mount_opt;
>  	uid_t s_resuid;
>  	gid_t s_resgid;
> +	uid_t s_uid;
> +	uid_t s_diskuid;
> +	gid_t s_gid;
> +	gid_t s_diskgid;
>  	unsigned long s_commit_interval;
>  #ifdef CONFIG_QUOTA
>  	int s_jquota_fmt;
> @@ -639,6 +643,10 @@ struct ext3_sb_info {
>  	ext3_fsblk_t s_sb_block;
>  	uid_t s_resuid;
>  	gid_t s_resgid;
> +	uid_t s_uid;          /* make all files appear to belong to this uid */
> +	uid_t s_diskuid;      /* write this uid to disk (if s_uid != 0) */
> +	gid_t s_gid;          /* make all files appear to belong to this gid */
> +	gid_t s_diskgid;      /* write this gid to disk (if s_gid != 0) */
>  	unsigned short s_mount_state;
>  	unsigned short s_pad;
>  	int s_addr_per_block_bits;
> diff --git a/fs/ext3/inode.c b/fs/ext3/inode.c
> index 10d7812..095bd31 100644
> --- a/fs/ext3/inode.c
> +++ b/fs/ext3/inode.c
> @@ -2913,6 +2913,10 @@ struct inode *ext3_iget(struct super_block *sb, unsigned long ino)
>  		inode->i_uid |= le16_to_cpu(raw_inode->i_uid_high) << 16;
>  		inode->i_gid |= le16_to_cpu(raw_inode->i_gid_high) << 16;
>  	}
> +	if (EXT3_SB(sb)->s_uid)
> +		inode->i_uid = EXT3_SB(sb)->s_uid;
> +	if (EXT3_SB(sb)->s_gid)
> +		inode->i_gid = EXT3_SB(sb)->s_gid;
>  	set_nlink(inode, le16_to_cpu(raw_inode->i_links_count));
>  	inode->i_size = le32_to_cpu(raw_inode->i_size);
>  	inode->i_atime.tv_sec = (signed)le32_to_cpu(raw_inode->i_atime);
> @@ -3066,8 +3070,14 @@ static int ext3_do_update_inode(handle_t *handle,
>  {
>  	struct ext3_inode *raw_inode = ext3_raw_inode(iloc);
>  	struct ext3_inode_info *ei = EXT3_I(inode);
> +	uid_t uid = inode->i_uid;
> +	gid_t gid = inode->i_gid;
>  	struct buffer_head *bh = iloc->bh;
>  	int err = 0, rc, block;
> +	__le16 uid_low;
> +	__le16 gid_low;
> +	__le16 uid_high;
> +	__le16 gid_high;
>  
>  again:
>  	/* we can't allow multiple procs in here at once, its a bit racey */
> @@ -3080,30 +3090,42 @@ again:
>  
>  	ext3_get_inode_flags(ei);
>  	raw_inode->i_mode = cpu_to_le16(inode->i_mode);
> +	if (EXT3_SB(inode->i_sb)->s_uid)
> +		uid = EXT3_SB(inode->i_sb)->s_diskuid;
> +	if (EXT3_SB(inode->i_sb)->s_gid)
> +		gid = EXT3_SB(inode->i_sb)->s_diskgid;
>  	if(!(test_opt(inode->i_sb, NO_UID32))) {
> -		raw_inode->i_uid_low = cpu_to_le16(low_16_bits(inode->i_uid));
> -		raw_inode->i_gid_low = cpu_to_le16(low_16_bits(inode->i_gid));
> +		uid_low = cpu_to_le16(low_16_bits(uid));
> +		gid_low = cpu_to_le16(low_16_bits(gid));
>  /*
>   * Fix up interoperability with old kernels. Otherwise, old inodes get
>   * re-used with the upper 16 bits of the uid/gid intact
>   */
>  		if(!ei->i_dtime) {
> -			raw_inode->i_uid_high =
> -				cpu_to_le16(high_16_bits(inode->i_uid));
> -			raw_inode->i_gid_high =
> -				cpu_to_le16(high_16_bits(inode->i_gid));
> +			uid_high = cpu_to_le16(high_16_bits(uid));
> +			gid_high = cpu_to_le16(high_16_bits(gid));
>  		} else {
> -			raw_inode->i_uid_high = 0;
> -			raw_inode->i_gid_high = 0;
> +			uid_high = 0;
> +			gid_high = 0;
>  		}
>  	} else {
> -		raw_inode->i_uid_low =
> -			cpu_to_le16(fs_high2lowuid(inode->i_uid));
> -		raw_inode->i_gid_low =
> -			cpu_to_le16(fs_high2lowgid(inode->i_gid));
> -		raw_inode->i_uid_high = 0;
> -		raw_inode->i_gid_high = 0;
> +		uid_low = cpu_to_le16(fs_high2lowuid(uid));
> +		gid_low = cpu_to_le16(fs_high2lowgid(gid));
> +		uid_high = 0;
> +		gid_high = 0;
>  	}
> +	/* don't mangle uid/gid of existing files if override is active */
> +	if (!EXT3_SB(inode->i_sb)->s_uid ||
> +			ext3_test_inode_state(inode, EXT3_STATE_NEW)) {
> +		raw_inode->i_uid_high = uid_high;
> +		raw_inode->i_uid_low = uid_low;
> +	}
> +	if (!EXT3_SB(inode->i_sb)->s_gid ||
> +			ext3_test_inode_state(inode, EXT3_STATE_NEW)) {
> +		raw_inode->i_gid_high = gid_high;
> +		raw_inode->i_gid_low = gid_low;
> +	}
> +
>  	raw_inode->i_links_count = cpu_to_le16(inode->i_nlink);
>  	raw_inode->i_size = cpu_to_le32(ei->i_disksize);
>  	raw_inode->i_atime = cpu_to_le32(inode->i_atime.tv_sec);
> diff --git a/fs/ext3/super.c b/fs/ext3/super.c
> index cf0b592..4dcce09 100644
> --- a/fs/ext3/super.c
> +++ b/fs/ext3/super.c
> @@ -625,6 +625,20 @@ static int ext3_show_options(struct seq_file *seq, struct dentry *root)
>  	    le16_to_cpu(es->s_def_resgid) != EXT3_DEF_RESGID) {
>  		seq_printf(seq, ",resgid=%u", sbi->s_resgid);
>  	}
> +	if (sbi->s_uid) {
> +		if (sbi->s_uid != sbi->s_diskuid)
> +			seq_printf(seq, ",uid=%u:%u",
> +				sbi->s_uid, sbi->s_diskuid);
> +		else
> +			seq_printf(seq, ",uid=%u", sbi->s_uid);
> +	}
> +	if (sbi->s_gid) {
> +		if (sbi->s_gid != sbi->s_diskgid)
> +			seq_printf(seq, ",gid=%u:%u",
> +				sbi->s_gid, sbi->s_diskgid);
> +		else
> +			seq_printf(seq, ",gid=%u", sbi->s_gid);
> +	}
>  	if (test_opt(sb, ERRORS_RO)) {
>  		int def_errors = le16_to_cpu(es->s_errors);
>  
> @@ -820,7 +834,8 @@ enum {
>  	Opt_usrjquota, Opt_grpjquota, Opt_offusrjquota, Opt_offgrpjquota,
>  	Opt_jqfmt_vfsold, Opt_jqfmt_vfsv0, Opt_jqfmt_vfsv1, Opt_quota,
>  	Opt_noquota, Opt_ignore, Opt_barrier, Opt_nobarrier, Opt_err,
> -	Opt_resize, Opt_usrquota, Opt_grpquota
> +	Opt_resize, Opt_usrquota, Opt_grpquota,
> +	Opt_uid, Opt_diskuid, Opt_gid, Opt_diskgid
>  };
>  
>  static const match_table_t tokens = {
> @@ -877,6 +892,10 @@ static const match_table_t tokens = {
>  	{Opt_barrier, "barrier"},
>  	{Opt_nobarrier, "nobarrier"},
>  	{Opt_resize, "resize"},
> +	{Opt_uid, "uid=%u"},
> +	{Opt_diskuid, "uid=%u:%u"},
> +	{Opt_gid, "gid=%u"},
> +	{Opt_diskgid, "gid=%u:%u"},
>  	{Opt_err, NULL},
>  };
>  
> @@ -1267,6 +1286,34 @@ set_qf_format:
>  			ext3_msg(sb, KERN_WARNING,
>  				"warning: ignoring deprecated bh option");
>  			break;
> +		case Opt_uid:
> +			if (match_int(&args[0], &option))
> +				return 0;
> +			sbi->s_uid = sbi->s_diskuid = option;
> +			break;
> +		case Opt_diskuid:
> +			if (match_int(&args[0], &option))
> +				return 0;
> +			sbi->s_uid = option;
> +
> +			if (match_int(&args[1], &option))
> +				return 0;
> +			sbi->s_diskuid = option;
> +			break;
> +		case Opt_gid:
> +			if (match_int(&args[0], &option))
> +				return 0;
> +			sbi->s_gid = sbi->s_diskgid = option;
> +			break;
> +		case Opt_diskgid:
> +			if (match_int(&args[0], &option))
> +				return 0;
> +			sbi->s_gid = option;
> +
> +			if (match_int(&args[1], &option))
> +				return 0;
> +			sbi->s_diskgid = option;
> +			break;
>  		default:
>  			ext3_msg(sb, KERN_ERR,
>  				"error: unrecognized mount option \"%s\" "
> @@ -2590,6 +2637,10 @@ static int ext3_remount (struct super_block * sb, int * flags, char * data)
>  	old_opts.s_mount_opt = sbi->s_mount_opt;
>  	old_opts.s_resuid = sbi->s_resuid;
>  	old_opts.s_resgid = sbi->s_resgid;
> +	old_opts.s_uid = sbi->s_uid;
> +	old_opts.s_diskuid = sbi->s_diskuid;
> +	old_opts.s_gid = sbi->s_gid;
> +	old_opts.s_diskgid = sbi->s_diskgid;
>  	old_opts.s_commit_interval = sbi->s_commit_interval;
>  #ifdef CONFIG_QUOTA
>  	old_opts.s_jquota_fmt = sbi->s_jquota_fmt;
> @@ -2701,6 +2752,10 @@ restore_opts:
>  	sbi->s_mount_opt = old_opts.s_mount_opt;
>  	sbi->s_resuid = old_opts.s_resuid;
>  	sbi->s_resgid = old_opts.s_resgid;
> +	sbi->s_uid = old_opts.s_uid;
> +	sbi->s_diskuid = old_opts.s_diskuid;
> +	sbi->s_gid = old_opts.s_gid;
> +	sbi->s_diskgid = old_opts.s_diskgid;
>  	sbi->s_commit_interval = old_opts.s_commit_interval;
>  #ifdef CONFIG_QUOTA
>  	sbi->s_jquota_fmt = old_opts.s_jquota_fmt;
> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> index 0e01e90..7155b2d 100644
> --- a/fs/ext4/ext4.h
> +++ b/fs/ext4/ext4.h
> @@ -1155,6 +1155,10 @@ struct ext4_sb_info {
>  	ext4_fsblk_t s_sb_block;
>  	uid_t s_resuid;
>  	gid_t s_resgid;
> +	uid_t s_uid;          /* make all files appear to belong to this uid */
> +	uid_t s_diskuid;      /* write this uid to disk (if s_uid != 0) */
> +	gid_t s_gid;          /* make all files appear to belong to this gid */
> +	gid_t s_diskgid;      /* write this gid to disk (if s_gid != 0) */
>  	unsigned short s_mount_state;
>  	unsigned short s_pad;
>  	int s_addr_per_block_bits;
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index c77b0bd..86ce928 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -3651,6 +3651,10 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
>  		inode->i_uid |= le16_to_cpu(raw_inode->i_uid_high) << 16;
>  		inode->i_gid |= le16_to_cpu(raw_inode->i_gid_high) << 16;
>  	}
> +	if (EXT4_SB(sb)->s_uid)
> +		inode->i_uid = EXT4_SB(sb)->s_uid;
> +	if (EXT4_SB(sb)->s_gid)
> +		inode->i_gid = EXT4_SB(sb)->s_gid;
>  	set_nlink(inode, le16_to_cpu(raw_inode->i_links_count));
>  
>  	ext4_clear_state_flags(ei);	/* Only relevant on 32-bit archs */
> @@ -3868,8 +3872,14 @@ static int ext4_do_update_inode(handle_t *handle,
>  {
>  	struct ext4_inode *raw_inode = ext4_raw_inode(iloc);
>  	struct ext4_inode_info *ei = EXT4_I(inode);
> +	uid_t uid = inode->i_uid;
> +	gid_t gid = inode->i_gid;
>  	struct buffer_head *bh = iloc->bh;
>  	int err = 0, rc, block;
> +	__le16 uid_low;
> +	__le16 gid_low;
> +	__le16 uid_high;
> +	__le16 gid_high;
>  
>  	/* For fields not not tracking in the in-memory inode,
>  	 * initialise them to zero for new inodes. */
> @@ -3878,30 +3888,42 @@ static int ext4_do_update_inode(handle_t *handle,
>  
>  	ext4_get_inode_flags(ei);
>  	raw_inode->i_mode = cpu_to_le16(inode->i_mode);
> +	if (EXT4_SB(inode->i_sb)->s_uid)
> +		uid = EXT4_SB(inode->i_sb)->s_diskuid;
> +	if (EXT4_SB(inode->i_sb)->s_gid)
> +		gid = EXT4_SB(inode->i_sb)->s_diskgid;
>  	if (!(test_opt(inode->i_sb, NO_UID32))) {
> -		raw_inode->i_uid_low = cpu_to_le16(low_16_bits(inode->i_uid));
> -		raw_inode->i_gid_low = cpu_to_le16(low_16_bits(inode->i_gid));
> +		uid_low = cpu_to_le16(low_16_bits(uid));
> +		gid_low = cpu_to_le16(low_16_bits(gid));
>  /*
>   * Fix up interoperability with old kernels. Otherwise, old inodes get
>   * re-used with the upper 16 bits of the uid/gid intact
>   */
>  		if (!ei->i_dtime) {
> -			raw_inode->i_uid_high =
> -				cpu_to_le16(high_16_bits(inode->i_uid));
> -			raw_inode->i_gid_high =
> -				cpu_to_le16(high_16_bits(inode->i_gid));
> +			uid_high = cpu_to_le16(high_16_bits(uid));
> +			gid_high = cpu_to_le16(high_16_bits(gid));
>  		} else {
> -			raw_inode->i_uid_high = 0;
> -			raw_inode->i_gid_high = 0;
> +			uid_high = 0;
> +			gid_high = 0;
>  		}
>  	} else {
> -		raw_inode->i_uid_low =
> -			cpu_to_le16(fs_high2lowuid(inode->i_uid));
> -		raw_inode->i_gid_low =
> -			cpu_to_le16(fs_high2lowgid(inode->i_gid));
> -		raw_inode->i_uid_high = 0;
> -		raw_inode->i_gid_high = 0;
> +		uid_low = cpu_to_le16(fs_high2lowuid(uid));
> +		gid_low = cpu_to_le16(fs_high2lowgid(gid));
> +		uid_high = 0;
> +		gid_high = 0;
>  	}
> +	/* don't mangle uid/gid of existing files if override is active */
> +	if (!EXT4_SB(inode->i_sb)->s_uid ||
> +			ext4_test_inode_state(inode, EXT4_STATE_NEW)) {
> +		raw_inode->i_uid_high = uid_high;
> +		raw_inode->i_uid_low = uid_low;
> +	}
> +	if (!EXT4_SB(inode->i_sb)->s_gid ||
> +			ext4_test_inode_state(inode, EXT4_STATE_NEW)) {
> +		raw_inode->i_gid_high = gid_high;
> +		raw_inode->i_gid_low = gid_low;
> +	}
> +
>  	raw_inode->i_links_count = cpu_to_le16(inode->i_nlink);
>  
>  	EXT4_INODE_SET_XTIME(i_ctime, inode, raw_inode);
> diff --git a/fs/ext4/super.c b/fs/ext4/super.c
> index e1fb1d5..5f121f3 100644
> --- a/fs/ext4/super.c
> +++ b/fs/ext4/super.c
> @@ -1186,6 +1186,7 @@ enum {
>  	Opt_inode_readahead_blks, Opt_journal_ioprio,
>  	Opt_dioread_nolock, Opt_dioread_lock,
>  	Opt_discard, Opt_nodiscard, Opt_init_itable, Opt_noinit_itable,
> +	Opt_uid, Opt_diskuid, Opt_gid, Opt_diskgid,
>  };
>  
>  static const match_table_t tokens = {
> @@ -1264,6 +1265,10 @@ static const match_table_t tokens = {
>  	{Opt_removed, "reservation"},	/* mount option from ext2/3 */
>  	{Opt_removed, "noreservation"}, /* mount option from ext2/3 */
>  	{Opt_removed, "journal=%u"},	/* mount option from ext2/3 */
> +	{Opt_uid, "uid=%u"},
> +	{Opt_diskuid, "uid=%u:%u"},
> +	{Opt_gid, "gid=%u"},
> +	{Opt_diskgid, "gid=%u:%u"},
>  	{Opt_err, NULL},
>  };
>  
> @@ -1498,6 +1503,24 @@ static int handle_mount_opt(struct super_block *sb, char *opt, int token,
>  			return -1;
>  		*journal_ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, arg);
>  		return 1;
> +	case Opt_uid:
> +		sbi->s_uid = sbi->s_diskuid = arg;
> +		return 1;
> +	case Opt_diskuid:
> +		sbi->s_uid = arg;
> +		if (match_int(&args[1], &arg))
> +			return -1;
> +		sbi->s_diskuid = arg;
> +		return 1;
> +	case Opt_gid:
> +		sbi->s_gid = sbi->s_diskgid = arg;
> +		return 1;
> +	case Opt_diskgid:
> +		sbi->s_gid = arg;
> +		if (match_int(&args[1], &arg))
> +			return -1;
> +		sbi->s_diskgid = arg;
> +		return 1;
>  	}
>  
>  	for (m = ext4_mount_opts; m->token != Opt_err; m++) {
> @@ -1713,7 +1736,7 @@ static int _ext4_show_options(struct seq_file *seq, struct super_block *sb,
>  	char sep = nodefs ? '\n' : ',';
>  
>  #define SEQ_OPTS_PUTS(str) seq_printf(seq, "%c" str, sep)
> -#define SEQ_OPTS_PRINT(str, arg) seq_printf(seq, "%c" str, sep, arg)
> +#define SEQ_OPTS_PRINT(str, args...) seq_printf(seq, "%c" str, sep, ##args)
>  
>  	if (sbi->s_sb_block != 1)
>  		SEQ_OPTS_PRINT("sb=%llu", sbi->s_sb_block);
> @@ -1738,6 +1761,18 @@ static int _ext4_show_options(struct seq_file *seq, struct super_block *sb,
>  	if (nodefs || sbi->s_resgid != EXT4_DEF_RESGID ||
>  	    le16_to_cpu(es->s_def_resgid) != EXT4_DEF_RESGID)
>  		SEQ_OPTS_PRINT("resgid=%u", sbi->s_resgid);
> +	if (sbi->s_uid) {
> +		if (sbi->s_uid != sbi->s_diskuid)
> +			SEQ_OPTS_PRINT("uid=%u:%u", sbi->s_uid, sbi->s_diskuid);
> +		else
> +			SEQ_OPTS_PRINT("uid=%u", sbi->s_uid);
> +	}
> +	if (sbi->s_gid) {
> +		if (sbi->s_gid != sbi->s_diskgid)
> +			SEQ_OPTS_PRINT("gid=%u:%u", sbi->s_gid, sbi->s_diskgid);
> +		else
> +			SEQ_OPTS_PRINT("gid=%u", sbi->s_gid);
> +	}
>  	def_errors = nodefs ? -1 : le16_to_cpu(es->s_errors);
>  	if (test_opt(sb, ERRORS_RO) && def_errors != EXT4_ERRORS_RO)
>  		SEQ_OPTS_PUTS("errors=remount-ro");
> @@ -4215,6 +4250,10 @@ struct ext4_mount_options {
>  	unsigned long s_mount_opt2;
>  	uid_t s_resuid;
>  	gid_t s_resgid;
> +	uid_t s_uid;
> +	uid_t s_diskuid;
> +	gid_t s_gid;
> +	gid_t s_diskgid;
>  	unsigned long s_commit_interval;
>  	u32 s_min_batch_time, s_max_batch_time;
>  #ifdef CONFIG_QUOTA
> @@ -4245,6 +4284,10 @@ static int ext4_remount(struct super_block *sb, int *flags, char *data)
>  	old_opts.s_mount_opt2 = sbi->s_mount_opt2;
>  	old_opts.s_resuid = sbi->s_resuid;
>  	old_opts.s_resgid = sbi->s_resgid;
> +	old_opts.s_uid = sbi->s_uid;
> +	old_opts.s_diskuid = sbi->s_diskuid;
> +	old_opts.s_gid = sbi->s_gid;
> +	old_opts.s_diskgid = sbi->s_diskgid;
>  	old_opts.s_commit_interval = sbi->s_commit_interval;
>  	old_opts.s_min_batch_time = sbi->s_min_batch_time;
>  	old_opts.s_max_batch_time = sbi->s_max_batch_time;
> @@ -4402,6 +4445,10 @@ restore_opts:
>  	sbi->s_mount_opt2 = old_opts.s_mount_opt2;
>  	sbi->s_resuid = old_opts.s_resuid;
>  	sbi->s_resgid = old_opts.s_resgid;
> +	sbi->s_uid = old_opts.s_uid;
> +	sbi->s_diskuid = old_opts.s_diskuid;
> +	sbi->s_gid = old_opts.s_gid;
> +	sbi->s_diskgid = old_opts.s_diskgid;
>  	sbi->s_commit_interval = old_opts.s_commit_interval;
>  	sbi->s_min_batch_time = old_opts.s_min_batch_time;
>  	sbi->s_max_batch_time = old_opts.s_max_batch_time;
> -- 
> 1.7.7
>
Theodore Ts'o May 10, 2012, 3:30 p.m. UTC | #2
On Thu, May 10, 2012 at 05:00:24PM +0200, Jan Kara wrote:
>   I've looked at this in more detail now. Although it would be nice to
> avoid duplicating the the functionality as others suggested, I agree it's
> not that simple and it seems to me we'd end up doing similar amount of work
> in the filesystems anyway (mount option parsing, writing to disk, printing
> mount options, ...). So I'm ok with the implementation as is and I'd be
> willing to take it for ext3 / ext2. But we really want to keep all ext?
> filesystems consistent so this also depends on Ted's approval of the change
> for ext4.  Ted?

Grumble.  I agree, reluctantly.  As much as I would like to make this
be generic, it looks like the amount of glue code to allow the VFS to
parse text-based mount options, and change the uid/gid fields after it
is read from disk and before writing to disk is probably more than the
code that we could be able to factor out.

>   What I'm missing in the changelog description is what i_diskuid/i_diskgid
> is good for. Although I can imagine some use case, I'm not sure I can see
> any sufficiently convicing one...

I'd much rather force diskuid and diskgid to 0 if the uid or gid is
specified, and if uid or gid option is in force, we force nosuid to be
on, to avoid the very obvious security problems if a sysadmin isn't
super careful.  The use case is going to be for things like USB sticks
where it's not likely that the sysadmin will be taking appropriate
care, so keeping things simple is probably a good thing.

>   Also please split the patch in three separate patches for ext2, ext3, and
> ext4. Thanks.

Yes, please.

							- Ted
--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Roland Eggner May 11, 2012, 3:49 a.m. UTC | #3
On 2012-05-10 Thu 16:42 +0200, Ludwig Nussel wrote:
> …
> When using 'real' file systems on removable storage devices such as
> hard disks or usb sticks people quickly face the problem that their
> Linux users have different uids on different machines. Therefore one
> cannot modify or even read files created on a different machine
> without running chown as root or storing everything with mode 777.
> Simple file systems such as vfat don't have that problem as they
> don't store file ownership information and one can pass the uid
> files should belong to as mount option.
> 
> The following two patches (for 3.4.0-rc4) implement the uid (and
> gid) mount option for ext2, ext3 and ext4 to make them actually
> useful on removable media. If a file system is mounted with the uid
> option all files appear to be owned by the specified uid. Only newly
> created files actually end up with that uid as owner on disk though.
> Ownership of existing files cannot be changed permanently if the uid
> option was specified.
> 
> Acked-by: Rob Landley <rob@landley.net>
> Signed-off-by: Ludwig Nussel <ludwig.nussel@suse.de>
> ---
>  Documentation/filesystems/ext2.txt |    9 ++++++
>  Documentation/filesystems/ext3.txt |    9 ++++++
>  Documentation/filesystems/ext4.txt |    9 ++++++
>  fs/ext2/ext2.h                     |    8 +++++
>  fs/ext2/inode.c                    |   42 ++++++++++++++++++++------
>  fs/ext2/super.c                    |   57 +++++++++++++++++++++++++++++++++++-
>  fs/ext3/ext3.h                     |    8 +++++
>  fs/ext3/inode.c                    |   50 ++++++++++++++++++++++---------
>  fs/ext3/super.c                    |   57 +++++++++++++++++++++++++++++++++++-
>  fs/ext4/ext4.h                     |    4 ++
>  fs/ext4/inode.c                    |   50 ++++++++++++++++++++++---------
>  fs/ext4/super.c                    |   49 ++++++++++++++++++++++++++++++-
>  12 files changed, 311 insertions(+), 41 deletions(-)
>
> …


In short:
.........
Problem solving at its root is more efficient than at “end of pipe”.



IMHO this is an example of “end of pipe“ thinking with following downsides:
...........................................................................
*  Maintainers point of view:
   *  Introduces new problems:  Breaking holes in the access restrictions
      provided by the Linux kernel at will of unprivileged users would render
      the kernel unusable for reliable operation in multiuser environments.
   *  Adds code complexity and risk of bugs.
   *  Adds future maintainance load.
*  Users point of view:
   *  Editing /etc/fstab or using mount commands with options not in
      /etc/fstab require root privileges anyway, at least on sane systems.
   *  Adds usage complexity (new vs. old files, on disk vs. pretended UIDs …).
   *  Adds risk of usage errors.



IMHO the “right thing to do” is to solve the problem at its root:
.................................................................
My habit is, whenever I use {group,user}add commands:
*  In advance I create a list of all current and future users (user, GID, UID)
   common to all systems that might exchange files.  The list is designed to
   have “headroom” for future additions.
*  I always consult this list and use options --gid $userGID --uid $userUID to
   {group,user}add commands.
*  Exchanging files with an unforeseen system is an exception, which requires
   root privileges anyway,

Advantages:
*  Decent migration of files to other systems via backups, external storage …
*  No NEW wholes in the access restrictions provided by the Linux kernel.
*  No NEW kernel code possibly introducing bugs.
*  No need to learn new mount options.
*  No NEW risks of usage errors.


Summary:
........
*  If UIDs differ on machines FORESEEN for file exchange, this is an
   administrator error, not a kernel deficit.
*  File exchange with an UNFORESEEN system requires root privileges anyway.



Thanks,
Roland Eggner
Boaz Harrosh May 11, 2012, 3:31 p.m. UTC | #4
On 05/11/2012 06:49 AM, Roland Eggner wrote:

> On 2012-05-10 Thu 16:42 +0200, Ludwig Nussel wrote:
>> …
>> When using 'real' file systems on removable storage devices such as
>> hard disks or usb sticks people quickly face the problem that their
>> Linux users have different uids on different machines. Therefore one
>> cannot modify or even read files created on a different machine
>> without running chown as root or storing everything with mode 777.
>> Simple file systems such as vfat don't have that problem as they
>> don't store file ownership information and one can pass the uid
>> files should belong to as mount option.
>>
>> The following two patches (for 3.4.0-rc4) implement the uid (and
>> gid) mount option for ext2, ext3 and ext4 to make them actually
>> useful on removable media. If a file system is mounted with the uid
>> option all files appear to be owned by the specified uid. Only newly
>> created files actually end up with that uid as owner on disk though.
>> Ownership of existing files cannot be changed permanently if the uid
>> option was specified.
>>
>> Acked-by: Rob Landley <rob@landley.net>
>> Signed-off-by: Ludwig Nussel <ludwig.nussel@suse.de>
>> ---
>>  Documentation/filesystems/ext2.txt |    9 ++++++
>>  Documentation/filesystems/ext3.txt |    9 ++++++
>>  Documentation/filesystems/ext4.txt |    9 ++++++
>>  fs/ext2/ext2.h                     |    8 +++++
>>  fs/ext2/inode.c                    |   42 ++++++++++++++++++++------
>>  fs/ext2/super.c                    |   57 +++++++++++++++++++++++++++++++++++-
>>  fs/ext3/ext3.h                     |    8 +++++
>>  fs/ext3/inode.c                    |   50 ++++++++++++++++++++++---------
>>  fs/ext3/super.c                    |   57 +++++++++++++++++++++++++++++++++++-
>>  fs/ext4/ext4.h                     |    4 ++
>>  fs/ext4/inode.c                    |   50 ++++++++++++++++++++++---------
>>  fs/ext4/super.c                    |   49 ++++++++++++++++++++++++++++++-
>>  12 files changed, 311 insertions(+), 41 deletions(-)
>>
>> …
> 
> 
> In short:
> .........
> Problem solving at its root is more efficient than at “end of pipe”.
> 
> 
> 
> IMHO this is an example of “end of pipe“ thinking with following downsides:
> ...........................................................................
> *  Maintainers point of view:
>    *  Introduces new problems:  Breaking holes in the access restrictions
>       provided by the Linux kernel at will of unprivileged users would render
>       the kernel unusable for reliable operation in multiuser environments.
>    *  Adds code complexity and risk of bugs.
>    *  Adds future maintainance load.
> *  Users point of view:
>    *  Editing /etc/fstab or using mount commands with options not in
>       /etc/fstab require root privileges anyway, at least on sane systems.
>    *  Adds usage complexity (new vs. old files, on disk vs. pretended UIDs …).
>    *  Adds risk of usage errors.
> 
> 
> 
> IMHO the “right thing to do” is to solve the problem at its root:
> .................................................................
> My habit is, whenever I use {group,user}add commands:
> *  In advance I create a list of all current and future users (user, GID, UID)
>    common to all systems that might exchange files.  The list is designed to
>    have “headroom” for future additions.
> *  I always consult this list and use options --gid $userGID --uid $userUID to
>    {group,user}add commands.
> *  Exchanging files with an unforeseen system is an exception, which requires
>    root privileges anyway,
> 
> Advantages:
> *  Decent migration of files to other systems via backups, external storage …
> *  No NEW wholes in the access restrictions provided by the Linux kernel.
> *  No NEW kernel code possibly introducing bugs.
> *  No need to learn new mount options.
> *  No NEW risks of usage errors.
> 
> 
> Summary:
> ........
> *  If UIDs differ on machines FORESEEN for file exchange, this is an
>    administrator error, not a kernel deficit.
> *  File exchange with an UNFORESEEN system requires root privileges anyway.
> 
> 


I agree with Ludwig completely!!! Thanks, good policy rules.

1. ext* are nothing special and are not a special domain of removable media.
   (If any vfat is dominant at that end)

2. What the hell does removable-media means? and how is it different then
   something else? is ext* over iscsi removable? a soft-mount NFS, is it
   removable?

Above sounds to me like a huge security breach, and actually a data-corruption.

In the NFS world I hang around, we constantly encounter multiple domain
uid/gid views, and it does not mean we blow a hole in POSIX security rules.

The root that mounts this FS can just copy+chmod or just-chmod them.
Next we'll see auto-mounters use these flags and goodbye file-access-control.

There is some convenience you do not allow. a password-less root, and no access
control at all is most convenient would you say?

I bet this code opens up an attack vector like crazy. Windows viruses welcome.

No thanks
Boaz

> 

> Thanks,
> Roland Eggner


--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Theodore Ts'o May 11, 2012, 4:46 p.m. UTC | #5
On Fri, May 11, 2012 at 09:25:37AM -0500, Rob Landley wrote:
> Well it's certainly a point of view. Luckily, FAT already _has_ the
> workaround we're discussing.  The objections were mainly "can't the VFS
> do this for us?" and the answer, upon closer inspection, turned out to
> be "not easily, no, the VFS takes option flags instead of parsing string
> options so doesn't have some necessary infrastructure".

The only reasonable use case I can imagine for this feature is one
where someone wants to use a removable storage device (which could be
a USB thumb drive to a USB HDD to a SSD in a USB 3.0 enclosure) as an
interchange device between Unix systems which do not have compatible
uid/gid spaces.

So perhaps the right approach is that we should have an ext2/3/4
read-only feature flag which enforces a default of nosuid and all
files to be read-only and world-readable.  There would be mount
options which could modify this default behaviour so that the files
could be writeable by a particular uid or gid, and another mount
option which would change the permission bits seen for that file
system from 0755/0644 for directories/files to 0700/0600.

Basically, the idea is we should mark the file system in an explicit
way that it is intended for interchange across incompatible uid/gid
spaces, with defaults which minimize security risk.  The fact that all
files become world-readable is potentially a risk, but if the user is
willing to put their private files on a removeable media that could
easily be dropped in a parking lot, or otherwise stolen or lost,
that's a potential risk that they've implicitly accepted in any case;
we might as well make it be explicit.

							- Ted

--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Boaz Harrosh May 11, 2012, 5:18 p.m. UTC | #6
On 05/11/2012 07:46 PM, Ted Ts'o wrote:

> On Fri, May 11, 2012 at 09:25:37AM -0500, Rob Landley wrote:
>> Well it's certainly a point of view. Luckily, FAT already _has_ the
>> workaround we're discussing.  The objections were mainly "can't the VFS
>> do this for us?" and the answer, upon closer inspection, turned out to
>> be "not easily, no, the VFS takes option flags instead of parsing string
>> options so doesn't have some necessary infrastructure".
> 
> The only reasonable use case I can imagine for this feature is one
> where someone wants to use a removable storage device (which could be
> a USB thumb drive to a USB HDD to a SSD in a USB 3.0 enclosure) as an
> interchange device between Unix systems which do not have compatible
> uid/gid spaces.


How is that ext* special? You said "Unix systems" there are lots more
FSs more common to "Unix" systems

> 
> So perhaps the right approach is that we should have an ext2/3/4
> read-only feature flag which enforces a default of nosuid and all
> files to be read-only and world-readable.  There would be mount
> options which could modify this default behaviour so that the files
> could be writeable by a particular uid or gid, and another mount
> option which would change the permission bits seen for that file
> system from 0755/0644 for directories/files to 0700/0600.
> 
> Basically, the idea is we should mark the file system in an explicit
> way that it is intended for interchange across incompatible uid/gid
> spaces, with defaults which minimize security risk.  The fact that all
> files become world-readable is potentially a risk, but if the user is
> willing to put their private files on a removeable media that could
> easily be dropped in a parking lot, or otherwise stolen or lost,
> that's a potential risk that they've implicitly accepted in any case;
> we might as well make it be explicit.


They can make that explicit by formatting as vfat or ntfs, fully
interchangeable not only on "Unix".

Or by doing the proper copy when filling up the media in the
first place.

As a maintainer of ext4 filesystem which is the official system for
Linux in many distrows, still. Please resists any such crap.
User "convenience-vs-security was never a geol of Unix.

> 
> 							- Ted


Please, for your own peace of mind, in an historical perspective,
don't do this

Boaz

--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Theodore Ts'o May 13, 2012, 4:24 a.m. UTC | #7
On Fri, May 11, 2012 at 08:09:52PM -0500, Rob Landley wrote:
> However, 95% of this use case is already covered by FAT, considering
> that most of these people are going to want to interchange with windows
> and mac, neither of which are necessarily happy with an ext3 formatted
> USB stick. (Sadly, that's what I normally do.  My usb keychain is fat
> formatted, because otherwise I can't use it to give a PDF to the guy at
> kinko's to print out. I suspect this is why it hasn't previously come up
> much.)

The other reason why I suspect it hasn't come up often is that USB
sticks are so painfully slow that the file system really isn't a
bottleneck.

I would expct this might be different if you were using a removable
HDD (or even an SSD) with a USB 3.0 interface.  In that case you
really might want a bette file system than VFAT, especially if you are
interchanging with another Linux system with an incompatible uid/gid
namespace.  That's not nearly as common a use case, though.

	    	       	      	 	      - Ted
--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Boaz Harrosh May 13, 2012, 11:46 a.m. UTC | #8
On 05/11/2012 10:22 PM, Ted Ts'o wrote:

> On Fri, May 11, 2012 at 08:18:35PM +0300, Boaz Harrosh wrote:
>> How is that ext* special? You said "Unix systems" there are lots more
>> FSs more common to "Unix" systems
> 
> Well, because FreeBSD, NetBSD, and Hurd do support ext2/3*.  So if you
> want a file system which is higher performance than VFAT, and
> supported on Unix-like systems beyond Linux, ext* is the best choice
> in a number of cases.
> 


That was a rhetorical questions. I meant  ext* is not special. There
are plenty of other FFs that are common to unix systems.

> As far as NTFS is concerned, the *BSD's can only support NTFS via
> FUSE, and I've been suspicious about the quality of our ntfs support
> under Linux --- we don't have a full-featured fsck for it, for
> example.  I'm at least not comfortable using NTFS on my personal
> machines.  (Last I checked there were all sorts of asterisks about
> data corruption if the system crashed before Linux mounted it, since
> apparently NTFS's logging subsystem was never reverse-engineered.)
> 


No! on all modern Linux distro's ntfs (ntfs-3g) is supported with
that infamous FUSE driver. The Kernel driver is long dead. The same
FUSE driver is also used under *BSD and officially supported by
Apple in OSX. Last benchmarks I saw where Faster then ext2 and in-par
with ext3, MetaData faster, IO slower then ext3. Stability is very
good and I never had an issue, on any of the above systems.

There is also a very good fsck and a suit of other tools under the same
ntfs-3g project. To date I have fixed 10s of friends/family Windows
machines with a Linux rescue USB-stick, I mean machines that would even
boot.

>> As a maintainer of ext4 filesystem which is the official system for
>> Linux in many distrows, still. Please resists any such crap.
>> User "convenience-vs-security was never a geol of Unix.
> 
> Did you look at the proposal I made?  By making it something where the
> file system is explicitly marked as "for interchange", it avoids the
> security problem (as much as you can when you put your unencrypted
> data on removable, portable storage which could be lost or stolen).
> 
> Sure, if you don't need to operate on the data as a mounted file
> system, tar or cpio or zip is a good choice for maximal portability.
> But if you want to do something like rsync on a portable SSD, 


Again, for the last time, you are the maintainer, you do what you
understand, I hope Linus or someone can make it more clear than me.

There is nothing special about USB sticks and ext* filesystems it
is the same old "shared files" problem. If you are not under a single
NIS domain, then these are different users. If files need to be shared
they need to sit in the proper CHMODed directory and bits.

I have at home a bittorent network directory for the all family, So the
first time I set it up I had to ssh into the server and fix the permissions.
That was easy enough listen to learn.

For the last time and I'm off this for good:
	"Shared files" problem is not solved by mount options.

Been there done that


<snip>
--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
NeilBrown May 14, 2012, 11:15 p.m. UTC | #9
On Fri, 11 May 2012 18:31:35 +0300 Boaz Harrosh <bharrosh@panasas.com> wrote:

> On 05/11/2012 06:49 AM, Roland Eggner wrote:
> 
> > On 2012-05-10 Thu 16:42 +0200, Ludwig Nussel wrote:
> >> …
> >> When using 'real' file systems on removable storage devices such as
> >> hard disks or usb sticks people quickly face the problem that their
> >> Linux users have different uids on different machines. Therefore one
> >> cannot modify or even read files created on a different machine
> >> without running chown as root or storing everything with mode 777.
> >> Simple file systems such as vfat don't have that problem as they
> >> don't store file ownership information and one can pass the uid
> >> files should belong to as mount option.
> >>
> >> The following two patches (for 3.4.0-rc4) implement the uid (and
> >> gid) mount option for ext2, ext3 and ext4 to make them actually
> >> useful on removable media. If a file system is mounted with the uid
> >> option all files appear to be owned by the specified uid. Only newly
> >> created files actually end up with that uid as owner on disk though.
> >> Ownership of existing files cannot be changed permanently if the uid
> >> option was specified.
> >>
> >> Acked-by: Rob Landley <rob@landley.net>
> >> Signed-off-by: Ludwig Nussel <ludwig.nussel@suse.de>
> >> ---
> >>  Documentation/filesystems/ext2.txt |    9 ++++++
> >>  Documentation/filesystems/ext3.txt |    9 ++++++
> >>  Documentation/filesystems/ext4.txt |    9 ++++++
> >>  fs/ext2/ext2.h                     |    8 +++++
> >>  fs/ext2/inode.c                    |   42 ++++++++++++++++++++------
> >>  fs/ext2/super.c                    |   57 +++++++++++++++++++++++++++++++++++-
> >>  fs/ext3/ext3.h                     |    8 +++++
> >>  fs/ext3/inode.c                    |   50 ++++++++++++++++++++++---------
> >>  fs/ext3/super.c                    |   57 +++++++++++++++++++++++++++++++++++-
> >>  fs/ext4/ext4.h                     |    4 ++
> >>  fs/ext4/inode.c                    |   50 ++++++++++++++++++++++---------
> >>  fs/ext4/super.c                    |   49 ++++++++++++++++++++++++++++++-
> >>  12 files changed, 311 insertions(+), 41 deletions(-)
> >>
> >> …
> > 
> > 
> > In short:
> > .........
> > Problem solving at its root is more efficient than at “end of pipe”.
> > 
> > 
> > 
> > IMHO this is an example of “end of pipe“ thinking with following downsides:
> > ...........................................................................
> > *  Maintainers point of view:
> >    *  Introduces new problems:  Breaking holes in the access restrictions
> >       provided by the Linux kernel at will of unprivileged users would render
> >       the kernel unusable for reliable operation in multiuser environments.
> >    *  Adds code complexity and risk of bugs.
> >    *  Adds future maintainance load.
> > *  Users point of view:
> >    *  Editing /etc/fstab or using mount commands with options not in
> >       /etc/fstab require root privileges anyway, at least on sane systems.
> >    *  Adds usage complexity (new vs. old files, on disk vs. pretended UIDs …).
> >    *  Adds risk of usage errors.
> > 
> > 
> > 
> > IMHO the “right thing to do” is to solve the problem at its root:
> > .................................................................
> > My habit is, whenever I use {group,user}add commands:
> > *  In advance I create a list of all current and future users (user, GID, UID)
> >    common to all systems that might exchange files.  The list is designed to
> >    have “headroom” for future additions.
> > *  I always consult this list and use options --gid $userGID --uid $userUID to
> >    {group,user}add commands.
> > *  Exchanging files with an unforeseen system is an exception, which requires
> >    root privileges anyway,
> > 
> > Advantages:
> > *  Decent migration of files to other systems via backups, external storage …
> > *  No NEW wholes in the access restrictions provided by the Linux kernel.
> > *  No NEW kernel code possibly introducing bugs.
> > *  No need to learn new mount options.
> > *  No NEW risks of usage errors.
> > 
> > 
> > Summary:
> > ........
> > *  If UIDs differ on machines FORESEEN for file exchange, this is an
> >    administrator error, not a kernel deficit.
> > *  File exchange with an UNFORESEEN system requires root privileges anyway.
> > 
> > 
> 
> 
> I agree with Ludwig completely!!! Thanks, good policy rules.

I think you mean "Roland" - you seem to disagree with Ludwig.

For the record I don't agree with Roland.. though I'm not 100% sure what he
is saying.  I cannot quite figure out what the "pipe" is that he is talking
about.

The thrust of the argument seems to be that by establishing good habits from
the very beginning you can avoid the need for change.  That may well be true,
but it isn't particularly "user friendly".  We should make things simple and
safe so that people don't *need* to carefully form good habits.


> 
> 1. ext* are nothing special and are not a special domain of removable media.
>    (If any vfat is dominant at that end)

VFAT is indeed dominant.  But cannot handle files >4GB and cannot handle a
variety of special characters in file names.  But that isn't really relevant.
Given that we have ext[234] rather than everyone using VFAT or NTFS
everywhere, we should make them as broadly usable as possible.  That path in
general encourages innovation.


> 
> 2. What the hell does removable-media means? and how is it different then
>    something else? is ext* over iscsi removable? a soft-mount NFS, is it
>    removable?

"removable-media" means "The owner is the person holding the physical device,
not the person who knows the password which is stored in some databases along
side some id which is attached to files in the filesystem".

So if I hold a device which has an ethernet port on which it talks NFS or
ISCSI, then yes: it is removable.

And this is not at all far-fetched.  Mobile internet devices (aka phones) are
quite capable for serving NFS.  The current common practice where you need to
tell your device how to appear on the USB (mass storage?  media device?
propriety thingy?) is not very friendly.  If we just do IP (IPv6 of course)
to the devices (whether over USB or BT or Wifi) and provided every service
over that concurrently, that would be much better.

And with NFS, I could write to my uSD card in my smart-phone over the USB
cable at the same time that the phone itself was using the storage of other
purposes.  You cannot do that with usb/mass-storage/VFAT.

My access to files on that filesystem should not be guided by the uid of my
processes, but rather by the crypto-key that I made available.
And I suspect you can do exactly that using kerberos - and that is awesome.
We should have that access to ext4 over usb/mass storage too.

To make that clear: if I "own" a filesystem - whether because I hold the
physical non-encrypted devices or because I know the encryption key - then I
want to be able to  leverage that "ownership" to full access rights to
the contents of the filesystem.  By typing in a key or plugging in a device I
want to get full "root" access to the filesystem on the device.  Not giving
that to me is just getting in my way.

> 
> Above sounds to me like a huge security breach, and actually a data-corruption.

It can certainly be used to cause a security breach, just as "rm -r" can be
used to cause data corruption.  We shouldn't disallow it for that reason
though.


> 
> In the NFS world I hang around, we constantly encounter multiple domain
> uid/gid views, and it does not mean we blow a hole in POSIX security rules.

The Linux NFS server has "all_squash" and "anon_uid=" export options.

> 
> The root that mounts this FS can just copy+chmod or just-chmod them.

Sorry, I don't understand.  It sounds like you are suggesting that when I
mount an ext3 filesystem on my usb-stick, I should chmod -R all 3,000,000
files to make them world writeable?  Or maybe chown them all to me?  Or copy
them to my home directory?
And somehow this is safer than making it appear as though this has already
happened through mount options?
I must have misunderstood something.

> Next we'll see auto-mounters use these flags and goodbye file-access-control.

Yes, of course auto-mounters will do this.  It makes perfect sense for any
removable (physically carry in my hand) media.  You cannot prevent data
destruction on such devices if you lose physical control, and the only
workable data privacy option is encryption.  Trying to pretend that file
permission bits mean anything is extremely naive.

> 
> There is some convenience you do not allow. a password-less root, and no access
> control at all is most convenient would you say?
> 
> I bet this code opens up an attack vector like crazy. Windows viruses welcome.
> 
> No thanks

I'm sure we could provide a CONFIG option for you.

NeilBrown


> Boaz
> 
> > 
> 
> > Thanks,
> > Roland Eggner
> 
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/
Boaz Harrosh May 16, 2012, 7:25 a.m. UTC | #10
On 05/15/2012 02:15 AM, NeilBrown wrote:

> On Fri, 11 May 2012 18:31:35 +0300 Boaz Harrosh <bharrosh@panasas.com> wrote:
> 
>> On 05/11/2012 06:49 AM, Roland Eggner wrote:
>>


Neil && Jan.

As I  said in my last mail:
	"Shared files" problem is not solved by mount options.

For me this is just a UNIX new-be 101 problem. You need to
share files with friends you make the mistake once. Then
you learn and you go on. It's a none problem.

Your removable media thing is just your "shared folder"
on a UNIX system. Deal with it. Da

Or go degrade your FS to vfat security. You are reluctant
to chmod all files to be world/group writeable but you
are doing just that with your mount option. Perhaps
when the chmod -R will scroll through you'd realize
that the passwords file you should remove. The POSIX
permissions are just for that purpose. "I set access
control at creation. Else slave to change them". The
mount option they specifically left out, not because
they were not clever, but because you don't want it.

BTW NTFS is like UNIX. If written in the right user-private
permissions you can't read them. Even admin. You can only chown
them.

BTW Neil with Kerberos auth in an NFS/CIFS shared device
like you mentioned, then a uid/gid is already associated
with the access automatically. It's a perfect IDEA for
these device makers. They need develop nothing only set it
up with the same NFS / SAMBA implementations they already
use.

And one last thing Neil. Yes an rm is a perfect rope
for an admin to hang itself, but it is not aimed at
auto-mounter scripts

I can't believe I got dragged into this again.

:-(
Boaz

--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/filesystems/ext2.txt b/Documentation/filesystems/ext2.txt
index 67639f9..fcc1002 100644
--- a/Documentation/filesystems/ext2.txt
+++ b/Documentation/filesystems/ext2.txt
@@ -42,6 +42,15 @@  orlov			(*)	Use the Orlov block allocator.
 resuid=n			The user ID which may use the reserved blocks.
 resgid=n			The group ID which may use the reserved blocks.
 
+uid=n[:m]			Make all files appear to belong to uid n.
+				Useful for e.g. removable media with fstab
+				options 'user,uid=useruid'. The optional second
+				uid m is actually written to the file system.
+
+gid=n[:m]			Make all files appear to belong to gid n.
+				The optional second gid m is actually written to
+				the file system.
+
 sb=n				Use alternate superblock at this location.
 
 user_xattr			Enable "user." POSIX Extended Attributes
diff --git a/Documentation/filesystems/ext3.txt b/Documentation/filesystems/ext3.txt
index b100adc..b2407a7 100644
--- a/Documentation/filesystems/ext3.txt
+++ b/Documentation/filesystems/ext3.txt
@@ -124,6 +124,15 @@  resgid=n		The group ID which may use the reserved blocks.
 
 resuid=n		The user ID which may use the reserved blocks.
 
+uid=n[:m]		Make all files appear to belong to uid n.
+			Useful for e.g. removable media with fstab
+			options 'user,uid=useruid'. The optional second
+			uid m is actually written to the file system.
+
+gid=n[:m]		Make all files appear to belong to gid n.
+			The optional second gid m is actually written to
+			the file system.
+
 sb=n			Use alternate superblock at this location.
 
 quota			These options are ignored by the filesystem. They
diff --git a/Documentation/filesystems/ext4.txt b/Documentation/filesystems/ext4.txt
index 1b7f9ac..b388ab5 100644
--- a/Documentation/filesystems/ext4.txt
+++ b/Documentation/filesystems/ext4.txt
@@ -245,6 +245,15 @@  resgid=n		The group ID which may use the reserved blocks.
 
 resuid=n		The user ID which may use the reserved blocks.
 
+uid=n[:m]		Make all files appear to belong to uid n.
+			Useful for e.g. removable media with fstab
+			options 'user,uid=useruid'. The optional second
+			uid m is actually written to the file system.
+
+gid=n[:m]		Make all files appear to belong to gid n.
+			The optional second gid m is actually written to
+			the file system.
+
 sb=n			Use alternate superblock at this location.
 
 quota			These options are ignored by the filesystem. They
diff --git a/fs/ext2/ext2.h b/fs/ext2/ext2.h
index 0b2b4db..b584e45 100644
--- a/fs/ext2/ext2.h
+++ b/fs/ext2/ext2.h
@@ -84,6 +84,10 @@  struct ext2_sb_info {
 	unsigned long s_sb_block;
 	uid_t s_resuid;
 	gid_t s_resgid;
+	uid_t s_uid;          /* make all files appear to belong to this uid */
+	uid_t s_diskuid;      /* write this uid to disk (if s_uid != 0) */
+	gid_t s_gid;          /* make all files appear to belong to this gid */
+	gid_t s_diskgid;      /* write this gid to disk (if s_gid != 0) */
 	unsigned short s_mount_state;
 	unsigned short s_pad;
 	int s_addr_per_block_bits;
@@ -639,6 +643,10 @@  struct ext2_mount_options {
 	unsigned long s_mount_opt;
 	uid_t s_resuid;
 	gid_t s_resgid;
+	uid_t s_uid;
+	uid_t s_diskuid;
+	gid_t s_gid;
+	gid_t s_diskgid;
 };
 
 /*
diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c
index 740cad8..aabcb38 100644
--- a/fs/ext2/inode.c
+++ b/fs/ext2/inode.c
@@ -1316,6 +1316,10 @@  struct inode *ext2_iget (struct super_block *sb, unsigned long ino)
 		inode->i_uid |= le16_to_cpu(raw_inode->i_uid_high) << 16;
 		inode->i_gid |= le16_to_cpu(raw_inode->i_gid_high) << 16;
 	}
+	if (EXT2_SB(sb)->s_uid)
+		inode->i_uid = EXT2_SB(sb)->s_uid;
+	if (EXT2_SB(sb)->s_gid)
+		inode->i_gid = EXT2_SB(sb)->s_gid;
 	set_nlink(inode, le16_to_cpu(raw_inode->i_links_count));
 	inode->i_size = le32_to_cpu(raw_inode->i_size);
 	inode->i_atime.tv_sec = (signed)le32_to_cpu(raw_inode->i_atime);
@@ -1419,6 +1423,10 @@  static int __ext2_write_inode(struct inode *inode, int do_sync)
 	struct ext2_inode * raw_inode = ext2_get_inode(sb, ino, &bh);
 	int n;
 	int err = 0;
+	__le16 uid_low;
+	__le16 gid_low;
+	__le16 uid_high;
+	__le16 gid_high;
 
 	if (IS_ERR(raw_inode))
  		return -EIO;
@@ -1430,26 +1438,40 @@  static int __ext2_write_inode(struct inode *inode, int do_sync)
 
 	ext2_get_inode_flags(ei);
 	raw_inode->i_mode = cpu_to_le16(inode->i_mode);
+	if (EXT2_SB(sb)->s_uid)
+		uid = EXT2_SB(sb)->s_diskuid;
+	if (EXT2_SB(sb)->s_gid)
+		gid = EXT2_SB(sb)->s_diskgid;
 	if (!(test_opt(sb, NO_UID32))) {
-		raw_inode->i_uid_low = cpu_to_le16(low_16_bits(uid));
-		raw_inode->i_gid_low = cpu_to_le16(low_16_bits(gid));
+		uid_low = cpu_to_le16(low_16_bits(uid));
+		gid_low = cpu_to_le16(low_16_bits(gid));
 /*
  * Fix up interoperability with old kernels. Otherwise, old inodes get
  * re-used with the upper 16 bits of the uid/gid intact
  */
 		if (!ei->i_dtime) {
-			raw_inode->i_uid_high = cpu_to_le16(high_16_bits(uid));
-			raw_inode->i_gid_high = cpu_to_le16(high_16_bits(gid));
+			uid_high = cpu_to_le16(high_16_bits(uid));
+			gid_high = cpu_to_le16(high_16_bits(gid));
 		} else {
-			raw_inode->i_uid_high = 0;
-			raw_inode->i_gid_high = 0;
+			uid_high = 0;
+			gid_high = 0;
 		}
 	} else {
-		raw_inode->i_uid_low = cpu_to_le16(fs_high2lowuid(uid));
-		raw_inode->i_gid_low = cpu_to_le16(fs_high2lowgid(gid));
-		raw_inode->i_uid_high = 0;
-		raw_inode->i_gid_high = 0;
+		uid_low = cpu_to_le16(fs_high2lowuid(uid));
+		gid_low = cpu_to_le16(fs_high2lowgid(gid));
+		uid_high = 0;
+		gid_high = 0;
 	}
+	/* don't mangle uid/gid of existing files if override is active */
+	if (!EXT2_SB(sb)->s_uid || ei->i_state & EXT2_STATE_NEW) {
+		raw_inode->i_uid_high = uid_high;
+		raw_inode->i_uid_low = uid_low;
+	}
+	if (!EXT2_SB(sb)->s_gid || ei->i_state & EXT2_STATE_NEW) {
+		raw_inode->i_gid_high = gid_high;
+		raw_inode->i_gid_low = gid_low;
+	}
+
 	raw_inode->i_links_count = cpu_to_le16(inode->i_nlink);
 	raw_inode->i_size = cpu_to_le32(inode->i_size);
 	raw_inode->i_atime = cpu_to_le32(inode->i_atime.tv_sec);
diff --git a/fs/ext2/super.c b/fs/ext2/super.c
index e1025c7..0661574 100644
--- a/fs/ext2/super.c
+++ b/fs/ext2/super.c
@@ -236,6 +236,20 @@  static int ext2_show_options(struct seq_file *seq, struct dentry *root)
 	    le16_to_cpu(es->s_def_resgid) != EXT2_DEF_RESGID) {
 		seq_printf(seq, ",resgid=%u", sbi->s_resgid);
 	}
+	if (sbi->s_uid) {
+		if (sbi->s_uid != sbi->s_diskuid)
+			seq_printf(seq, ",uid=%u:%u",
+				sbi->s_uid, sbi->s_diskuid);
+		else
+			seq_printf(seq, ",uid=%u", sbi->s_uid);
+	}
+	if (sbi->s_gid) {
+		if (sbi->s_gid != sbi->s_diskgid)
+			seq_printf(seq, ",gid=%u:%u",
+				sbi->s_gid, sbi->s_diskgid);
+		else
+			seq_printf(seq, ",gid=%u", sbi->s_gid);
+	}
 	if (test_opt(sb, ERRORS_RO)) {
 		int def_errors = le16_to_cpu(es->s_errors);
 
@@ -393,7 +407,8 @@  enum {
 	Opt_err_ro, Opt_nouid32, Opt_nocheck, Opt_debug,
 	Opt_oldalloc, Opt_orlov, Opt_nobh, Opt_user_xattr, Opt_nouser_xattr,
 	Opt_acl, Opt_noacl, Opt_xip, Opt_ignore, Opt_err, Opt_quota,
-	Opt_usrquota, Opt_grpquota, Opt_reservation, Opt_noreservation
+	Opt_usrquota, Opt_grpquota, Opt_reservation, Opt_noreservation,
+	Opt_uid, Opt_diskuid, Opt_gid, Opt_diskgid
 };
 
 static const match_table_t tokens = {
@@ -427,6 +442,10 @@  static const match_table_t tokens = {
 	{Opt_usrquota, "usrquota"},
 	{Opt_reservation, "reservation"},
 	{Opt_noreservation, "noreservation"},
+	{Opt_uid, "uid=%u"},
+	{Opt_diskuid, "uid=%u:%u"},
+	{Opt_gid, "gid=%u"},
+	{Opt_diskgid, "gid=%u:%u"},
 	{Opt_err, NULL}
 };
 
@@ -568,6 +587,34 @@  static int parse_options(char *options, struct super_block *sb)
 			clear_opt(sbi->s_mount_opt, RESERVATION);
 			ext2_msg(sb, KERN_INFO, "reservations OFF");
 			break;
+		case Opt_uid:
+			if (match_int(&args[0], &option))
+				return 0;
+			sbi->s_uid = sbi->s_diskuid = option;
+			break;
+		case Opt_diskuid:
+			if (match_int(&args[0], &option))
+				return 0;
+			sbi->s_uid = option;
+
+			if (match_int(&args[1], &option))
+				return 0;
+			sbi->s_diskuid = option;
+			break;
+		case Opt_gid:
+			if (match_int(&args[0], &option))
+				return 0;
+			sbi->s_gid = sbi->s_diskgid = option;
+			break;
+		case Opt_diskgid:
+			if (match_int(&args[0], &option))
+				return 0;
+			sbi->s_gid = option;
+
+			if (match_int(&args[1], &option))
+				return 0;
+			sbi->s_diskgid = option;
+			break;
 		case Opt_ignore:
 			break;
 		default:
@@ -1214,6 +1261,10 @@  static int ext2_remount (struct super_block * sb, int * flags, char * data)
 	old_opts.s_mount_opt = sbi->s_mount_opt;
 	old_opts.s_resuid = sbi->s_resuid;
 	old_opts.s_resgid = sbi->s_resgid;
+	old_opts.s_uid = sbi->s_uid;
+	old_opts.s_diskuid = sbi->s_diskuid;
+	old_opts.s_gid = sbi->s_gid;
+	old_opts.s_diskgid = sbi->s_diskgid;
 
 	/*
 	 * Allow the "check" option to be passed as a remount option.
@@ -1300,6 +1351,10 @@  restore_opts:
 	sbi->s_mount_opt = old_opts.s_mount_opt;
 	sbi->s_resuid = old_opts.s_resuid;
 	sbi->s_resgid = old_opts.s_resgid;
+	sbi->s_uid = old_opts.s_uid;
+	sbi->s_diskuid = old_opts.s_diskuid;
+	sbi->s_gid = old_opts.s_gid;
+	sbi->s_diskgid = old_opts.s_diskgid;
 	sb->s_flags = old_sb_flags;
 	spin_unlock(&sbi->s_lock);
 	return err;
diff --git a/fs/ext3/ext3.h b/fs/ext3/ext3.h
index b6515fd..c7c4578 100644
--- a/fs/ext3/ext3.h
+++ b/fs/ext3/ext3.h
@@ -245,6 +245,10 @@  struct ext3_mount_options {
 	unsigned long s_mount_opt;
 	uid_t s_resuid;
 	gid_t s_resgid;
+	uid_t s_uid;
+	uid_t s_diskuid;
+	gid_t s_gid;
+	gid_t s_diskgid;
 	unsigned long s_commit_interval;
 #ifdef CONFIG_QUOTA
 	int s_jquota_fmt;
@@ -639,6 +643,10 @@  struct ext3_sb_info {
 	ext3_fsblk_t s_sb_block;
 	uid_t s_resuid;
 	gid_t s_resgid;
+	uid_t s_uid;          /* make all files appear to belong to this uid */
+	uid_t s_diskuid;      /* write this uid to disk (if s_uid != 0) */
+	gid_t s_gid;          /* make all files appear to belong to this gid */
+	gid_t s_diskgid;      /* write this gid to disk (if s_gid != 0) */
 	unsigned short s_mount_state;
 	unsigned short s_pad;
 	int s_addr_per_block_bits;
diff --git a/fs/ext3/inode.c b/fs/ext3/inode.c
index 10d7812..095bd31 100644
--- a/fs/ext3/inode.c
+++ b/fs/ext3/inode.c
@@ -2913,6 +2913,10 @@  struct inode *ext3_iget(struct super_block *sb, unsigned long ino)
 		inode->i_uid |= le16_to_cpu(raw_inode->i_uid_high) << 16;
 		inode->i_gid |= le16_to_cpu(raw_inode->i_gid_high) << 16;
 	}
+	if (EXT3_SB(sb)->s_uid)
+		inode->i_uid = EXT3_SB(sb)->s_uid;
+	if (EXT3_SB(sb)->s_gid)
+		inode->i_gid = EXT3_SB(sb)->s_gid;
 	set_nlink(inode, le16_to_cpu(raw_inode->i_links_count));
 	inode->i_size = le32_to_cpu(raw_inode->i_size);
 	inode->i_atime.tv_sec = (signed)le32_to_cpu(raw_inode->i_atime);
@@ -3066,8 +3070,14 @@  static int ext3_do_update_inode(handle_t *handle,
 {
 	struct ext3_inode *raw_inode = ext3_raw_inode(iloc);
 	struct ext3_inode_info *ei = EXT3_I(inode);
+	uid_t uid = inode->i_uid;
+	gid_t gid = inode->i_gid;
 	struct buffer_head *bh = iloc->bh;
 	int err = 0, rc, block;
+	__le16 uid_low;
+	__le16 gid_low;
+	__le16 uid_high;
+	__le16 gid_high;
 
 again:
 	/* we can't allow multiple procs in here at once, its a bit racey */
@@ -3080,30 +3090,42 @@  again:
 
 	ext3_get_inode_flags(ei);
 	raw_inode->i_mode = cpu_to_le16(inode->i_mode);
+	if (EXT3_SB(inode->i_sb)->s_uid)
+		uid = EXT3_SB(inode->i_sb)->s_diskuid;
+	if (EXT3_SB(inode->i_sb)->s_gid)
+		gid = EXT3_SB(inode->i_sb)->s_diskgid;
 	if(!(test_opt(inode->i_sb, NO_UID32))) {
-		raw_inode->i_uid_low = cpu_to_le16(low_16_bits(inode->i_uid));
-		raw_inode->i_gid_low = cpu_to_le16(low_16_bits(inode->i_gid));
+		uid_low = cpu_to_le16(low_16_bits(uid));
+		gid_low = cpu_to_le16(low_16_bits(gid));
 /*
  * Fix up interoperability with old kernels. Otherwise, old inodes get
  * re-used with the upper 16 bits of the uid/gid intact
  */
 		if(!ei->i_dtime) {
-			raw_inode->i_uid_high =
-				cpu_to_le16(high_16_bits(inode->i_uid));
-			raw_inode->i_gid_high =
-				cpu_to_le16(high_16_bits(inode->i_gid));
+			uid_high = cpu_to_le16(high_16_bits(uid));
+			gid_high = cpu_to_le16(high_16_bits(gid));
 		} else {
-			raw_inode->i_uid_high = 0;
-			raw_inode->i_gid_high = 0;
+			uid_high = 0;
+			gid_high = 0;
 		}
 	} else {
-		raw_inode->i_uid_low =
-			cpu_to_le16(fs_high2lowuid(inode->i_uid));
-		raw_inode->i_gid_low =
-			cpu_to_le16(fs_high2lowgid(inode->i_gid));
-		raw_inode->i_uid_high = 0;
-		raw_inode->i_gid_high = 0;
+		uid_low = cpu_to_le16(fs_high2lowuid(uid));
+		gid_low = cpu_to_le16(fs_high2lowgid(gid));
+		uid_high = 0;
+		gid_high = 0;
 	}
+	/* don't mangle uid/gid of existing files if override is active */
+	if (!EXT3_SB(inode->i_sb)->s_uid ||
+			ext3_test_inode_state(inode, EXT3_STATE_NEW)) {
+		raw_inode->i_uid_high = uid_high;
+		raw_inode->i_uid_low = uid_low;
+	}
+	if (!EXT3_SB(inode->i_sb)->s_gid ||
+			ext3_test_inode_state(inode, EXT3_STATE_NEW)) {
+		raw_inode->i_gid_high = gid_high;
+		raw_inode->i_gid_low = gid_low;
+	}
+
 	raw_inode->i_links_count = cpu_to_le16(inode->i_nlink);
 	raw_inode->i_size = cpu_to_le32(ei->i_disksize);
 	raw_inode->i_atime = cpu_to_le32(inode->i_atime.tv_sec);
diff --git a/fs/ext3/super.c b/fs/ext3/super.c
index cf0b592..4dcce09 100644
--- a/fs/ext3/super.c
+++ b/fs/ext3/super.c
@@ -625,6 +625,20 @@  static int ext3_show_options(struct seq_file *seq, struct dentry *root)
 	    le16_to_cpu(es->s_def_resgid) != EXT3_DEF_RESGID) {
 		seq_printf(seq, ",resgid=%u", sbi->s_resgid);
 	}
+	if (sbi->s_uid) {
+		if (sbi->s_uid != sbi->s_diskuid)
+			seq_printf(seq, ",uid=%u:%u",
+				sbi->s_uid, sbi->s_diskuid);
+		else
+			seq_printf(seq, ",uid=%u", sbi->s_uid);
+	}
+	if (sbi->s_gid) {
+		if (sbi->s_gid != sbi->s_diskgid)
+			seq_printf(seq, ",gid=%u:%u",
+				sbi->s_gid, sbi->s_diskgid);
+		else
+			seq_printf(seq, ",gid=%u", sbi->s_gid);
+	}
 	if (test_opt(sb, ERRORS_RO)) {
 		int def_errors = le16_to_cpu(es->s_errors);
 
@@ -820,7 +834,8 @@  enum {
 	Opt_usrjquota, Opt_grpjquota, Opt_offusrjquota, Opt_offgrpjquota,
 	Opt_jqfmt_vfsold, Opt_jqfmt_vfsv0, Opt_jqfmt_vfsv1, Opt_quota,
 	Opt_noquota, Opt_ignore, Opt_barrier, Opt_nobarrier, Opt_err,
-	Opt_resize, Opt_usrquota, Opt_grpquota
+	Opt_resize, Opt_usrquota, Opt_grpquota,
+	Opt_uid, Opt_diskuid, Opt_gid, Opt_diskgid
 };
 
 static const match_table_t tokens = {
@@ -877,6 +892,10 @@  static const match_table_t tokens = {
 	{Opt_barrier, "barrier"},
 	{Opt_nobarrier, "nobarrier"},
 	{Opt_resize, "resize"},
+	{Opt_uid, "uid=%u"},
+	{Opt_diskuid, "uid=%u:%u"},
+	{Opt_gid, "gid=%u"},
+	{Opt_diskgid, "gid=%u:%u"},
 	{Opt_err, NULL},
 };
 
@@ -1267,6 +1286,34 @@  set_qf_format:
 			ext3_msg(sb, KERN_WARNING,
 				"warning: ignoring deprecated bh option");
 			break;
+		case Opt_uid:
+			if (match_int(&args[0], &option))
+				return 0;
+			sbi->s_uid = sbi->s_diskuid = option;
+			break;
+		case Opt_diskuid:
+			if (match_int(&args[0], &option))
+				return 0;
+			sbi->s_uid = option;
+
+			if (match_int(&args[1], &option))
+				return 0;
+			sbi->s_diskuid = option;
+			break;
+		case Opt_gid:
+			if (match_int(&args[0], &option))
+				return 0;
+			sbi->s_gid = sbi->s_diskgid = option;
+			break;
+		case Opt_diskgid:
+			if (match_int(&args[0], &option))
+				return 0;
+			sbi->s_gid = option;
+
+			if (match_int(&args[1], &option))
+				return 0;
+			sbi->s_diskgid = option;
+			break;
 		default:
 			ext3_msg(sb, KERN_ERR,
 				"error: unrecognized mount option \"%s\" "
@@ -2590,6 +2637,10 @@  static int ext3_remount (struct super_block * sb, int * flags, char * data)
 	old_opts.s_mount_opt = sbi->s_mount_opt;
 	old_opts.s_resuid = sbi->s_resuid;
 	old_opts.s_resgid = sbi->s_resgid;
+	old_opts.s_uid = sbi->s_uid;
+	old_opts.s_diskuid = sbi->s_diskuid;
+	old_opts.s_gid = sbi->s_gid;
+	old_opts.s_diskgid = sbi->s_diskgid;
 	old_opts.s_commit_interval = sbi->s_commit_interval;
 #ifdef CONFIG_QUOTA
 	old_opts.s_jquota_fmt = sbi->s_jquota_fmt;
@@ -2701,6 +2752,10 @@  restore_opts:
 	sbi->s_mount_opt = old_opts.s_mount_opt;
 	sbi->s_resuid = old_opts.s_resuid;
 	sbi->s_resgid = old_opts.s_resgid;
+	sbi->s_uid = old_opts.s_uid;
+	sbi->s_diskuid = old_opts.s_diskuid;
+	sbi->s_gid = old_opts.s_gid;
+	sbi->s_diskgid = old_opts.s_diskgid;
 	sbi->s_commit_interval = old_opts.s_commit_interval;
 #ifdef CONFIG_QUOTA
 	sbi->s_jquota_fmt = old_opts.s_jquota_fmt;
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 0e01e90..7155b2d 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1155,6 +1155,10 @@  struct ext4_sb_info {
 	ext4_fsblk_t s_sb_block;
 	uid_t s_resuid;
 	gid_t s_resgid;
+	uid_t s_uid;          /* make all files appear to belong to this uid */
+	uid_t s_diskuid;      /* write this uid to disk (if s_uid != 0) */
+	gid_t s_gid;          /* make all files appear to belong to this gid */
+	gid_t s_diskgid;      /* write this gid to disk (if s_gid != 0) */
 	unsigned short s_mount_state;
 	unsigned short s_pad;
 	int s_addr_per_block_bits;
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index c77b0bd..86ce928 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -3651,6 +3651,10 @@  struct inode *ext4_iget(struct super_block *sb, unsigned long ino)
 		inode->i_uid |= le16_to_cpu(raw_inode->i_uid_high) << 16;
 		inode->i_gid |= le16_to_cpu(raw_inode->i_gid_high) << 16;
 	}
+	if (EXT4_SB(sb)->s_uid)
+		inode->i_uid = EXT4_SB(sb)->s_uid;
+	if (EXT4_SB(sb)->s_gid)
+		inode->i_gid = EXT4_SB(sb)->s_gid;
 	set_nlink(inode, le16_to_cpu(raw_inode->i_links_count));
 
 	ext4_clear_state_flags(ei);	/* Only relevant on 32-bit archs */
@@ -3868,8 +3872,14 @@  static int ext4_do_update_inode(handle_t *handle,
 {
 	struct ext4_inode *raw_inode = ext4_raw_inode(iloc);
 	struct ext4_inode_info *ei = EXT4_I(inode);
+	uid_t uid = inode->i_uid;
+	gid_t gid = inode->i_gid;
 	struct buffer_head *bh = iloc->bh;
 	int err = 0, rc, block;
+	__le16 uid_low;
+	__le16 gid_low;
+	__le16 uid_high;
+	__le16 gid_high;
 
 	/* For fields not not tracking in the in-memory inode,
 	 * initialise them to zero for new inodes. */
@@ -3878,30 +3888,42 @@  static int ext4_do_update_inode(handle_t *handle,
 
 	ext4_get_inode_flags(ei);
 	raw_inode->i_mode = cpu_to_le16(inode->i_mode);
+	if (EXT4_SB(inode->i_sb)->s_uid)
+		uid = EXT4_SB(inode->i_sb)->s_diskuid;
+	if (EXT4_SB(inode->i_sb)->s_gid)
+		gid = EXT4_SB(inode->i_sb)->s_diskgid;
 	if (!(test_opt(inode->i_sb, NO_UID32))) {
-		raw_inode->i_uid_low = cpu_to_le16(low_16_bits(inode->i_uid));
-		raw_inode->i_gid_low = cpu_to_le16(low_16_bits(inode->i_gid));
+		uid_low = cpu_to_le16(low_16_bits(uid));
+		gid_low = cpu_to_le16(low_16_bits(gid));
 /*
  * Fix up interoperability with old kernels. Otherwise, old inodes get
  * re-used with the upper 16 bits of the uid/gid intact
  */
 		if (!ei->i_dtime) {
-			raw_inode->i_uid_high =
-				cpu_to_le16(high_16_bits(inode->i_uid));
-			raw_inode->i_gid_high =
-				cpu_to_le16(high_16_bits(inode->i_gid));
+			uid_high = cpu_to_le16(high_16_bits(uid));
+			gid_high = cpu_to_le16(high_16_bits(gid));
 		} else {
-			raw_inode->i_uid_high = 0;
-			raw_inode->i_gid_high = 0;
+			uid_high = 0;
+			gid_high = 0;
 		}
 	} else {
-		raw_inode->i_uid_low =
-			cpu_to_le16(fs_high2lowuid(inode->i_uid));
-		raw_inode->i_gid_low =
-			cpu_to_le16(fs_high2lowgid(inode->i_gid));
-		raw_inode->i_uid_high = 0;
-		raw_inode->i_gid_high = 0;
+		uid_low = cpu_to_le16(fs_high2lowuid(uid));
+		gid_low = cpu_to_le16(fs_high2lowgid(gid));
+		uid_high = 0;
+		gid_high = 0;
 	}
+	/* don't mangle uid/gid of existing files if override is active */
+	if (!EXT4_SB(inode->i_sb)->s_uid ||
+			ext4_test_inode_state(inode, EXT4_STATE_NEW)) {
+		raw_inode->i_uid_high = uid_high;
+		raw_inode->i_uid_low = uid_low;
+	}
+	if (!EXT4_SB(inode->i_sb)->s_gid ||
+			ext4_test_inode_state(inode, EXT4_STATE_NEW)) {
+		raw_inode->i_gid_high = gid_high;
+		raw_inode->i_gid_low = gid_low;
+	}
+
 	raw_inode->i_links_count = cpu_to_le16(inode->i_nlink);
 
 	EXT4_INODE_SET_XTIME(i_ctime, inode, raw_inode);
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index e1fb1d5..5f121f3 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1186,6 +1186,7 @@  enum {
 	Opt_inode_readahead_blks, Opt_journal_ioprio,
 	Opt_dioread_nolock, Opt_dioread_lock,
 	Opt_discard, Opt_nodiscard, Opt_init_itable, Opt_noinit_itable,
+	Opt_uid, Opt_diskuid, Opt_gid, Opt_diskgid,
 };
 
 static const match_table_t tokens = {
@@ -1264,6 +1265,10 @@  static const match_table_t tokens = {
 	{Opt_removed, "reservation"},	/* mount option from ext2/3 */
 	{Opt_removed, "noreservation"}, /* mount option from ext2/3 */
 	{Opt_removed, "journal=%u"},	/* mount option from ext2/3 */
+	{Opt_uid, "uid=%u"},
+	{Opt_diskuid, "uid=%u:%u"},
+	{Opt_gid, "gid=%u"},
+	{Opt_diskgid, "gid=%u:%u"},
 	{Opt_err, NULL},
 };
 
@@ -1498,6 +1503,24 @@  static int handle_mount_opt(struct super_block *sb, char *opt, int token,
 			return -1;
 		*journal_ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, arg);
 		return 1;
+	case Opt_uid:
+		sbi->s_uid = sbi->s_diskuid = arg;
+		return 1;
+	case Opt_diskuid:
+		sbi->s_uid = arg;
+		if (match_int(&args[1], &arg))
+			return -1;
+		sbi->s_diskuid = arg;
+		return 1;
+	case Opt_gid:
+		sbi->s_gid = sbi->s_diskgid = arg;
+		return 1;
+	case Opt_diskgid:
+		sbi->s_gid = arg;
+		if (match_int(&args[1], &arg))
+			return -1;
+		sbi->s_diskgid = arg;
+		return 1;
 	}
 
 	for (m = ext4_mount_opts; m->token != Opt_err; m++) {
@@ -1713,7 +1736,7 @@  static int _ext4_show_options(struct seq_file *seq, struct super_block *sb,
 	char sep = nodefs ? '\n' : ',';
 
 #define SEQ_OPTS_PUTS(str) seq_printf(seq, "%c" str, sep)
-#define SEQ_OPTS_PRINT(str, arg) seq_printf(seq, "%c" str, sep, arg)
+#define SEQ_OPTS_PRINT(str, args...) seq_printf(seq, "%c" str, sep, ##args)
 
 	if (sbi->s_sb_block != 1)
 		SEQ_OPTS_PRINT("sb=%llu", sbi->s_sb_block);
@@ -1738,6 +1761,18 @@  static int _ext4_show_options(struct seq_file *seq, struct super_block *sb,
 	if (nodefs || sbi->s_resgid != EXT4_DEF_RESGID ||
 	    le16_to_cpu(es->s_def_resgid) != EXT4_DEF_RESGID)
 		SEQ_OPTS_PRINT("resgid=%u", sbi->s_resgid);
+	if (sbi->s_uid) {
+		if (sbi->s_uid != sbi->s_diskuid)
+			SEQ_OPTS_PRINT("uid=%u:%u", sbi->s_uid, sbi->s_diskuid);
+		else
+			SEQ_OPTS_PRINT("uid=%u", sbi->s_uid);
+	}
+	if (sbi->s_gid) {
+		if (sbi->s_gid != sbi->s_diskgid)
+			SEQ_OPTS_PRINT("gid=%u:%u", sbi->s_gid, sbi->s_diskgid);
+		else
+			SEQ_OPTS_PRINT("gid=%u", sbi->s_gid);
+	}
 	def_errors = nodefs ? -1 : le16_to_cpu(es->s_errors);
 	if (test_opt(sb, ERRORS_RO) && def_errors != EXT4_ERRORS_RO)
 		SEQ_OPTS_PUTS("errors=remount-ro");
@@ -4215,6 +4250,10 @@  struct ext4_mount_options {
 	unsigned long s_mount_opt2;
 	uid_t s_resuid;
 	gid_t s_resgid;
+	uid_t s_uid;
+	uid_t s_diskuid;
+	gid_t s_gid;
+	gid_t s_diskgid;
 	unsigned long s_commit_interval;
 	u32 s_min_batch_time, s_max_batch_time;
 #ifdef CONFIG_QUOTA
@@ -4245,6 +4284,10 @@  static int ext4_remount(struct super_block *sb, int *flags, char *data)
 	old_opts.s_mount_opt2 = sbi->s_mount_opt2;
 	old_opts.s_resuid = sbi->s_resuid;
 	old_opts.s_resgid = sbi->s_resgid;
+	old_opts.s_uid = sbi->s_uid;
+	old_opts.s_diskuid = sbi->s_diskuid;
+	old_opts.s_gid = sbi->s_gid;
+	old_opts.s_diskgid = sbi->s_diskgid;
 	old_opts.s_commit_interval = sbi->s_commit_interval;
 	old_opts.s_min_batch_time = sbi->s_min_batch_time;
 	old_opts.s_max_batch_time = sbi->s_max_batch_time;
@@ -4402,6 +4445,10 @@  restore_opts:
 	sbi->s_mount_opt2 = old_opts.s_mount_opt2;
 	sbi->s_resuid = old_opts.s_resuid;
 	sbi->s_resgid = old_opts.s_resgid;
+	sbi->s_uid = old_opts.s_uid;
+	sbi->s_diskuid = old_opts.s_diskuid;
+	sbi->s_gid = old_opts.s_gid;
+	sbi->s_diskgid = old_opts.s_diskgid;
 	sbi->s_commit_interval = old_opts.s_commit_interval;
 	sbi->s_min_batch_time = old_opts.s_min_batch_time;
 	sbi->s_max_batch_time = old_opts.s_max_batch_time;