Linux filesystems cannot set extra file attributes (stx_attributes as per statx(2)) on a symbolic link. To set extra file attributes you issue ioctl(2) with FS_IOC_SETFLAGS, *all* ioctl(2) calls on a symbolic link yield EBADF. This is because ioctl(2) tries to obtain struct fd from the symbolic link file descriptor passed using fdget(), fdget() in turn always returns no file set when a file descriptor is open with O_PATH. As per symlink(2) O_PATH and O_NOFOLLOW must *always* be used when you want to get the file descriptor of a symbolic link, and this holds true for Linux, as such extra file attributes cannot possibly be set on symbolic links on Linux. Filesystems repair utilities should be updated to detect this as corruption and correct this, however, the VFS *does* respect these extra attributes on symlinks for removal. Since we cannot set these attributes we should special-case the immutable/append on delete for symlinks, this would be consistent with what we *do* allow on Linux for all filesystems. The userspace utility chattr(1) cannot set these attributes on symlinks *and* other special files as well: # chattr -a symlink chattr: Operation not supported while reading flags on b The reason for this is different though. Refer to commit 023d111e92195 ("chattr.1.in: Document the compression attribute flags E, X, and ...") merged on e2fsprogs v1.28 since August 2002. This commit prevented issuing the ioctl() for symlink *and* special files in consideration for a buggy DRM driver where issuing lsattr on their special files crashed the system. For details refer to Debian bug 152029 [0]. You can craft your own tool to query the extra file attributes with the new shiny statx(2) tool, statx(2) will list the attributes if they were set for instance on a corrupt filesystem. However statx(2) is only used for *querying* -- not for setting the attributes. If you implement issuing your own ioctl() for FS_IOC_FSGETXATTR or FS_IOC_FSSETXATTR on special files (block, char, fifo) it will fail returning -1 and errno is set to ENOTTY (Inappropriate ioctl for device). The reason for this is different than for symlinks. For special files this fails on vfs_ioctl() when the filesystem f_op callbacks are not set for these special files: long vfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int error = -ENOTTY; if (!filp->f_op->unlocked_ioctl) goto out; error = filp->f_op->unlocked_ioctl(filp, cmd, arg); if (error == -ENOIOCTLCMD) error = -ENOTTY; out: return error; } The same applies to PF_LOCAL named sockets. Since this varies by filesystem for special files, only make a special rule to respect the immutable and append attribute on symlinks. [0] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=152029 Signed-off-by: Luis R. Rodriguez <mcgrof@xxxxxxxxxx> --- As discussed at LSF/MM -- I'd follow up on this low hanging fruit as the discussion had stalled on linux-xfs on review of the respective xfs_repair changes. This addresses the general API question, and as such I think could help establish order in how we split up patches for those changes. This requires some other eyeballs, and it also requires a putting it through xfstests which I can do in the next few days, hence the RFC. But better put it out for review already. I'd also like feedback from the linux-api folks to see if this matches their own known / documented expectations. fs/namei.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 186bd2464fd5..0f9069468cfb 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2719,6 +2719,14 @@ int __check_sticky(struct inode *dir, struct inode *inode) } EXPORT_SYMBOL(__check_sticky); +/* Process extra file attributes only when they make sense */ +static bool may_delete_stx_attributes(struct inode *inode) +{ + if (!S_ISLNK(inode->i_mode) && (IS_APPEND(inode) || IS_IMMUTABLE(inode))) + return false; + return true; +} + /* * Check whether we can remove a link victim from directory dir, check * whether the type of victim is right. @@ -2757,8 +2765,8 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir) if (IS_APPEND(dir)) return -EPERM; - if (check_sticky(dir, inode) || IS_APPEND(inode) || - IS_IMMUTABLE(inode) || IS_SWAPFILE(inode) || HAS_UNMAPPED_ID(inode)) + if (check_sticky(dir, inode) || !may_delete_stx_attributes(inode) || + IS_SWAPFILE(inode) || HAS_UNMAPPED_ID(inode)) return -EPERM; if (isdir) { if (!d_is_dir(victim)) -- 2.17.0