diff --git a/Documentation/filesystems/ntfs.rst b/Documentation/filesystems/ntfs.rst index fe42a9d22643..a40f60eef0f2 100644 --- a/Documentation/filesystems/ntfs.rst +++ b/Documentation/filesystems/ntfs.rst @@ -38,9 +38,21 @@ The project is available at: Linux file attributes ===================== -The driver supports querying compression, encryption, immutable, append-only -and mount-wide case folding through lsattr(1). Case folding is reported -according to the mount options. +The driver supports lsattr(1) and setting immutable with chattr(1). +Immutable is stored in bit 0 of a private ``$LINUXFLAGS`` extended attribute +within the standard NTFS ``$EA``/``$EA_INFORMATION`` attributes. Its value +is a 32-bit little-endian bitmask. Unknown bits are preserved; the entry +is removed when the entire value becomes zero. The setting survives +inode eviction, unmount and reboot. Direct writes or removal through +xattr interfaces are rejected; use chattr(1) instead. + +System metadata files and files protected by ``sys_immutable`` cannot +have their immutable protection cleared. Protection derived solely from +that policy is not stored in the EA. Compression, encryption, append-only +and mount-wide case folding are reported but cannot be changed through +chattr(1). + +Windows does not enforce the Linux immutable flag stored in this EA. Supported mount options ======================= diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 483082a7c86f..8f73590362e5 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -481,6 +481,51 @@ int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size, return err; } +int ntfs_ea_get_linuxflags(struct inode *inode) +{ + struct ntfs_inode *ni = NTFS_I(inode); + __le32 value; + int err; + + err = ntfs_get_ea(inode, "$LINUXFLAGS", sizeof("$LINUXFLAGS") - 1, + &value, sizeof(value)); + if (err == -ENODATA) + return 0; + if (err < 0) + return err == -ERANGE ? -EUCLEAN : err; + if (err != sizeof(value)) + return -EUCLEAN; + + ni->linuxflags = le32_to_cpu(value); + if (ni->linuxflags & NTFS_LINUXFLAGS_IMMUTABLE) + inode->i_flags |= S_IMMUTABLE; + return 0; +} + +int ntfs_ea_set_linuxflags(struct inode *inode, u32 linuxflags) +{ + struct ntfs_inode *ni = NTFS_I(inode); + __le32 value; + int err; + + if (linuxflags) { + value = cpu_to_le32(linuxflags); + err = ntfs_set_ea(inode, "$LINUXFLAGS", sizeof("$LINUXFLAGS") - 1, + &value, sizeof(value), 0, NULL); + } else if (NInoHasEA(ni)) { + err = ntfs_set_ea(inode, "$LINUXFLAGS", sizeof("$LINUXFLAGS") - 1, + NULL, 0, XATTR_REPLACE, NULL); + if (err == -ENODATA) + err = 0; + } else { + err = 0; + } + + if (!err) + ni->linuxflags = linuxflags; + return err; +} + ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size) { struct inode *inode = d_inode(dentry); @@ -861,6 +906,10 @@ static int ntfs_setxattr(const struct xattr_handler *handler, if (NVolShutdown(ni->vol)) return -EIO; + /* Only fileattr_set may change the flags and their cached state. */ + if (!strcmp(name, "$LINUXFLAGS")) + return -EPERM; + if (ntfs_is_reserved_lxattr(name) && !capable(CAP_SYS_ADMIN)) return -EPERM; diff --git a/fs/ntfs/ea.h b/fs/ntfs/ea.h index 690fafe181fb..c417ed9c556e 100644 --- a/fs/ntfs/ea.h +++ b/fs/ntfs/ea.h @@ -7,6 +7,10 @@ #define NTFS_EA_GID BIT(2) #define NTFS_EA_MODE BIT(3) +/* $LINUXFLAGS stores these bits in a single little-endian 32-bit value. */ +#define NTFS_LINUXFLAGS_IMMUTABLE BIT(0) +#define NTFS_LINUXFLAGS_MASK NTFS_LINUXFLAGS_IMMUTABLE + extern const struct xattr_handler *const ntfs_xattr_handlers[]; int ntfs_ea_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode, dev_t dev); @@ -14,6 +18,8 @@ int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags, bool *has_lxmod); int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size, unsigned int flags); +int ntfs_ea_get_linuxflags(struct inode *inode); +int ntfs_ea_set_linuxflags(struct inode *inode, u32 linuxflags); ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size); #ifdef CONFIG_NTFS_FS_POSIX_ACL diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index ce3625a3533b..b000a53672a5 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -174,6 +174,87 @@ int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return 0; } +static bool ntfs_is_forced_immutable(struct inode *vi) +{ + struct ntfs_inode *ni = NTFS_I(vi); + + if (ni->mft_no < FILE_first_user && S_ISREG(vi->i_mode)) + return true; + + return NVolSysImmutable(ni->vol) && + (ni->flags & FILE_ATTR_SYSTEM) && + !S_ISFIFO(vi->i_mode) && !S_ISSOCK(vi->i_mode) && + !S_ISLNK(vi->i_mode); +} + +/* + * ntfs_fileattr_set - inode_operations::fileattr_set + * @idmap: idmap of the mount @dentry was found from + * @dentry: dentry to set the flags of + * @fa: flags to set + */ +int ntfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, + struct file_kattr *fa) +{ + struct inode *vi = d_inode(dentry); + struct ntfs_inode *ni = NTFS_I(vi); + u32 allowed = FS_IMMUTABLE_FL; + u32 readonly = 0; + u32 linuxflags = ni->linuxflags & ~NTFS_LINUXFLAGS_MASK; + unsigned int new_fl = 0; + bool forced_immutable; + int err; + + if (NVolShutdown(ni->vol)) + return -EIO; + + if (fileattr_has_fsx(fa)) + return -EOPNOTSUPP; + + if ((fa->flags & FS_APPEND_FL) != + (IS_APPEND(vi) ? FS_APPEND_FL : 0)) + return -EOPNOTSUPP; + allowed |= fa->flags & FS_APPEND_FL; + + /* chattr passes the unchanged read-only flags back to us too. */ + if (NInoCompressed(ni) || NInoWofCompressed(ni)) + readonly |= FS_COMPR_FL; + if (NInoEncrypted(ni)) + readonly |= FS_ENCRYPT_FL; + if ((fa->flags ^ readonly) & (FS_COMPR_FL | FS_ENCRYPT_FL)) + return -EOPNOTSUPP; + allowed |= readonly; + + /* Case folding is a read-only, mount-wide property. */ + if (!NVolCaseSensitive(ni->vol)) + allowed |= FS_CASEFOLD_FL; + if (fa->flags & ~allowed) + return -EOPNOTSUPP; + + forced_immutable = ntfs_is_forced_immutable(vi); + if (!(fa->flags & FS_IMMUTABLE_FL) && forced_immutable) + return -EPERM; + + if (fa->flags & FS_IMMUTABLE_FL) { + new_fl |= S_IMMUTABLE; + /* Do not persist an immutable bit derived from the mount. */ + if (!forced_immutable || + (ni->linuxflags & NTFS_LINUXFLAGS_IMMUTABLE)) + linuxflags |= NTFS_LINUXFLAGS_IMMUTABLE; + } + + mutex_lock(&ni->mrec_lock); + err = ntfs_ea_set_linuxflags(vi, linuxflags); + mutex_unlock(&ni->mrec_lock); + if (err) + return err; + + inode_set_flags(vi, new_fl, S_IMMUTABLE); + inode_set_ctime_current(vi); + mark_inode_dirty(vi); + return 0; +} + struct ntfs_fsync_attr { struct list_head list; struct inode *inode; @@ -1456,9 +1537,10 @@ const struct file_operations ntfs_file_ops = { }; const struct inode_operations ntfs_file_inode_ops = { - .fileattr_get = ntfs_fileattr_get, .setattr = ntfs_setattr, .getattr = ntfs_getattr, + .fileattr_get = ntfs_fileattr_get, + .fileattr_set = ntfs_fileattr_set, .listxattr = ntfs_listxattr, .get_acl = ntfs_get_acl, .set_acl = ntfs_set_acl, @@ -1466,17 +1548,19 @@ const struct inode_operations ntfs_file_inode_ops = { }; const struct inode_operations ntfs_symlink_inode_operations = { - .fileattr_get = ntfs_fileattr_get, .get_link = ntfs_get_link, .setattr = ntfs_setattr, .listxattr = ntfs_listxattr, + .fileattr_get = ntfs_fileattr_get, + .fileattr_set = ntfs_fileattr_set, }; const struct inode_operations ntfs_special_inode_operations = { - .fileattr_get = ntfs_fileattr_get, .setattr = ntfs_setattr, .getattr = ntfs_getattr, .listxattr = ntfs_listxattr, + .fileattr_get = ntfs_fileattr_get, + .fileattr_set = ntfs_fileattr_set, .get_acl = ntfs_get_acl, .set_acl = ntfs_set_acl, }; diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index 8d9475788845..206db4e99ef1 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -537,6 +537,7 @@ void __ntfs_init_inode(struct super_block *sb, struct ntfs_inode *ni) ni->reparse_tag = 0; ni->reparse_flags = 0; ni->target = NULL; + ni->linuxflags = 0; ni->i_dealloc_clusters = 0; } @@ -727,10 +728,8 @@ static bool ntfs_non_resident_sizes_inconsistent(struct inode *vi, * Q: What locks are held when the function is called? * A: i_state has I_NEW set, hence the inode is locked, also * i_count is set to 1, so it is not going to go away - * i_flags is set to 0 and we have no business touching it. Only an ioctl() - * is allowed to write to them. We should of course be honouring them but - * we need to do that using the IS_* macros defined in include/linux/fs.h. - * In any case ntfs_read_locked_inode() has nothing to do with i_flags. + * i_flags is initialized from the persisted Linux file attributes and + * the driver's system-file protection policy before publishing the inode. * * Return 0 on success and -errno on error. */ @@ -934,6 +933,11 @@ static int ntfs_read_locked_inode(struct inode *vi) if (!err) { NInoSetHasEA(ni); ntfs_ea_get_wsl_inode(vi, &dev, flags, &has_lxmod); + err = ntfs_ea_get_linuxflags(vi); + if (err) + goto unm_err_out; + } else if (err != -ENOENT) { + goto unm_err_out; } if (ni->flags & FILE_ATTR_REPARSE_POINT) { diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h index 824cd6f79b7e..a18cbcf1c631 100644 --- a/fs/ntfs/inode.h +++ b/fs/ntfs/inode.h @@ -95,6 +95,7 @@ enum ntfs_inode_mutex_lock_class { * belongs. * @i_dealloc_clusters: delayed allocated clusters. * @target: symlink buffer. + * @linuxflags: Linux-specific file attributes loaded from the $LINUXFLAGS EA. */ struct ntfs_inode { rwlock_t size_lock; @@ -146,6 +147,7 @@ struct ntfs_inode { __le32 reparse_tag; __le32 reparse_flags; char *target; + u32 linuxflags; }; /* diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index c9dc26906617..62b01646c262 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -1660,7 +1660,6 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir, * Inode operations for directories. */ const struct inode_operations ntfs_dir_inode_ops = { - .fileattr_get = ntfs_fileattr_get, .lookup = ntfs_lookup, /* VFS: Lookup directory. */ .create = ntfs_create, .unlink = ntfs_unlink, @@ -1672,6 +1671,8 @@ const struct inode_operations ntfs_dir_inode_ops = { .listxattr = ntfs_listxattr, .setattr = ntfs_setattr, .getattr = ntfs_getattr, + .fileattr_get = ntfs_fileattr_get, + .fileattr_set = ntfs_fileattr_set, .symlink = ntfs_symlink, .mknod = ntfs_mknod, .link = ntfs_link, diff --git a/fs/ntfs/ntfs.h b/fs/ntfs/ntfs.h index 8c344794f87e..fac72d40f583 100644 --- a/fs/ntfs/ntfs.h +++ b/fs/ntfs/ntfs.h @@ -186,6 +186,8 @@ extern const struct file_operations ntfs_empty_file_ops; extern const struct inode_operations ntfs_empty_inode_ops; int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); +int ntfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, + struct file_kattr *fa); extern const struct export_operations ntfs_export_ops;