diff --git a/exfat_fs.h b/exfat_fs.h index b79f2fe2bea5..97ee6843c17e 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -162,6 +162,12 @@ enum { #define DIR_CACHE_SIZE \ (DIV_ROUND_UP(EXFAT_DEN_TO_B(ES_MAX_ENTRY_NUM), SECTOR_SIZE) + 1) +/* + * attribute ioctls, same as their FAT equivalents. + */ +#define EXFAT_IOCTL_GET_ATTRIBUTES _IOR('r', 0x10, __u32) +#define EXFAT_IOCTL_SET_ATTRIBUTES _IOW('r', 0x11, __u32) + struct exfat_dentry_namebuf { char *lfn; int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */ diff --git a/file.c b/file.c index e35998f5aa8c..216c38b981e1 100644 --- a/file.c +++ b/file.c @@ -9,8 +9,11 @@ #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) #include #endif +#include #include #include +#include +#include #include "exfat_raw.h" #include "exfat_fs.h" @@ -390,6 +393,130 @@ out: return error; } +/* + * modified ioctls from fat/file.c by Welmer Almesberger + */ +static int exfat_ioctl_get_attributes(struct inode *inode, u32 __user *user_attr) +{ + u32 attr; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + inode_lock_shared(inode); +#else + mutex_lock(&inode->i_mutex); +#endif + attr = exfat_make_attr(inode); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + inode_unlock_shared(inode); +#else + mutex_unlock(&inode->i_mutex); +#endif + + return put_user(attr, user_attr); +} + +static int exfat_ioctl_set_attributes(struct file *file, u32 __user *user_attr) +{ + struct inode *inode = file_inode(file); + struct exfat_sb_info *sbi = EXFAT_SB(inode->i_sb); + int is_dir = S_ISDIR(inode->i_mode); + u32 attr, oldattr; + struct iattr ia; + int err; + + err = get_user(attr, user_attr); + if (err) + goto out; + + err = mnt_want_write_file(file); + if (err) + goto out; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) + inode_lock(inode); +#else + mutex_lock(&inode->i_mutex); +#endif + + oldattr = exfat_make_attr(inode); + + /* + * Mask attributes so we don't set reserved fields. + */ + attr &= (ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_ARCHIVE); + attr |= (is_dir ? ATTR_SUBDIR : 0); + + /* Equivalent to a chmod() */ + ia.ia_valid = ATTR_MODE | ATTR_CTIME; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) + ia.ia_ctime = current_time(inode); +#else + ia.ia_ctime = current_fs_time(inode->i_sb); +#endif + if (is_dir) + ia.ia_mode = exfat_make_mode(sbi, attr, 0777); + else + ia.ia_mode = exfat_make_mode(sbi, attr, 0666 | (inode->i_mode & 0111)); + + /* The root directory has no attributes */ + if (inode->i_ino == EXFAT_ROOT_INO && attr != ATTR_SUBDIR) { + err = -EINVAL; + goto out_unlock_inode; + } + + if (((attr | oldattr) & ATTR_SYSTEM) && + !capable(CAP_LINUX_IMMUTABLE)) { + err = -EPERM; + goto out_unlock_inode; + } + + /* + * The security check is questionable... We single + * out the RO attribute for checking by the security + * module, just because it maps to a file mode. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + err = security_inode_setattr(file_mnt_idmap(file), + file->f_path.dentry, &ia); +#else + err = security_inode_setattr(file_mnt_user_ns(file), + file->f_path.dentry, &ia); +#endif +#else + err = security_inode_setattr(file->f_path.dentry, &ia); +#endif + if (err) + goto out_unlock_inode; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + /* This MUST be done before doing anything irreversible... */ + err = exfat_setattr(file_mnt_idmap(file), file->f_path.dentry, &ia); +#else + err = exfat_setattr(file_mnt_user_ns(file), file->f_path.dentry, &ia); +#endif +#else + err = exfat_setattr(file->f_path.dentry, &ia); +#endif + if (err) + goto out_unlock_inode; + + fsnotify_change(file->f_path.dentry, ia.ia_valid); + + exfat_save_attr(inode, attr); + mark_inode_dirty(inode); +out_unlock_inode: +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0) + inode_unlock(inode); +#else + mutex_unlock(&inode->i_mutex); +#endif + mnt_drop_write_file(file); +out: + return err; +} + static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) { #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) @@ -432,8 +559,13 @@ static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct inode *inode = file_inode(filp); + u32 __user *user_attr = (u32 __user *)arg; switch (cmd) { + case EXFAT_IOCTL_GET_ATTRIBUTES: + return exfat_ioctl_get_attributes(inode, user_attr); + case EXFAT_IOCTL_SET_ATTRIBUTES: + return exfat_ioctl_set_attributes(filp, user_attr); case FITRIM: return exfat_ioctl_fitrim(inode, arg); default: