ntfs: respect per-file chmod mode over mount masks

fmask and dmask provide the default permissions for files without WSL
metadata. Once chmod stores a mode in $LXMOD, however, that per-file mode
must take precedence so selected files can retain permissions such as
execute across remounts.

Record whether $LXMOD was found while loading an inode and apply the mount
masks only when it is absent. Do not remask the in-memory mode after
setattr persists it. Continue loading $LXMOD even when optional $LXUID or
$LXGID metadata is missing, since chmod may create only $LXMOD.

Fixes: fc053f05ca ("ntfs: add reparse and ea operations")
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Namjae Jeon 2026-07-27 18:07:15 +09:00
parent 71feaa7686
commit 4d730a4dd7
5 changed files with 24 additions and 28 deletions

View File

@ -406,37 +406,35 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
* Check for the presence of an EA "$LXDEV" (used by WSL)
* and return its value as a device address
*/
int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags)
int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags,
bool *has_lxmod)
{
int err;
__le32 v;
*has_lxmod = false;
if (!(flags & NTFS_VOL_UID)) {
/* Load uid to lxuid EA */
err = ntfs_get_ea(inode, "$LXUID", sizeof("$LXUID") - 1, &v,
sizeof(v));
if (err < 0)
return err;
if (err != sizeof(v))
return -EIO;
i_uid_write(inode, le32_to_cpu(v));
if (err == sizeof(v))
i_uid_write(inode, le32_to_cpu(v));
}
if (!(flags & NTFS_VOL_GID)) {
/* Load gid to lxgid EA */
err = ntfs_get_ea(inode, "$LXGID", sizeof("$LXGID") - 1, &v,
sizeof(v));
if (err < 0)
return err;
if (err != sizeof(v))
return -EIO;
i_gid_write(inode, le32_to_cpu(v));
if (err == sizeof(v))
i_gid_write(inode, le32_to_cpu(v));
}
/* Load mode to lxmod EA */
err = ntfs_get_ea(inode, "$LXMOD", sizeof("$LXMOD") - 1, &v, sizeof(v));
if (err == sizeof(v)) {
inode->i_mode = le32_to_cpu(v);
*has_lxmod = true;
} else {
/* Everyone gets all permissions. */
inode->i_mode |= 0777;

View File

@ -10,7 +10,8 @@
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);
int ntfs_ea_get_wsl_inode(struct inode *inode, dev_t *rdevp, unsigned int flags);
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);
ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size);

View File

@ -342,14 +342,12 @@ int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
if (ia_valid & ATTR_MODE)
flags |= NTFS_EA_MODE;
if (S_ISDIR(vi->i_mode))
vi->i_mode &= ~vol->dmask;
else
vi->i_mode &= ~vol->fmask;
mutex_lock(&ni->mrec_lock);
ntfs_ea_set_wsl_inode(vi, 0, NULL, flags);
err = ntfs_ea_set_wsl_inode(vi, 0, NULL, flags);
mutex_unlock(&ni->mrec_lock);
if (err)
goto out;
}
mark_inode_dirty(vi);

View File

@ -682,6 +682,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
unsigned int name_len = 4, flags = 0;
int extend_sys = 0;
dev_t dev = 0;
bool has_lxmod = false;
bool vol_err = true;
ntfs_debug("Entering for i_ino 0x%llx.", ni->mft_no);
@ -862,7 +863,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
err = ntfs_attr_lookup(AT_EA_INFORMATION, NULL, 0, 0, 0, NULL, 0, ctx);
if (!err) {
NInoSetHasEA(ni);
ntfs_ea_get_wsl_inode(vi, &dev, flags);
ntfs_ea_get_wsl_inode(vi, &dev, flags, &has_lxmod);
}
if (ni->flags & FILE_ATTR_REPARSE_POINT) {
@ -886,16 +887,18 @@ static int ntfs_read_locked_inode(struct inode *vi)
if (S_ISDIR(vi->i_mode)) {
/*
* Apply the directory permissions mask set in the mount
* options.
* Apply the directory permissions mask set in the mount options
* when no per-file WSL mode is present.
*/
vi->i_mode &= ~vol->dmask;
if (!has_lxmod)
vi->i_mode &= ~vol->dmask;
/* Things break without this kludge! */
if (vi->i_nlink > 1)
set_nlink(vi, 1);
} else {
/* Apply the file permissions mask set in the mount options. */
vi->i_mode &= ~vol->fmask;
/* Apply the file permissions mask when no WSL mode is present. */
if (!has_lxmod)
vi->i_mode &= ~vol->fmask;
}
/*

View File

@ -424,8 +424,6 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
* directories, also setup the index values to the defaults.
*/
if (S_ISDIR(mode)) {
mode &= ~vol->dmask;
NInoSetMstProtected(ni);
ni->itype.index.block_size = 4096;
ni->itype.index.block_size_bits = ntfs_ffs(4096) - 1;
@ -439,8 +437,6 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
ni->itype.index.vcn_size_bits =
vol->sector_size_bits;
}
} else {
mode &= ~vol->fmask;
}
if (IS_RDONLY(vi))