Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

fs: Pass AT_GETATTR_NOSEC flag to getattr interface function

When vfs_getattr_nosec() calls a filesystem's getattr interface function
then the 'nosec' should propagate into this function so that
vfs_getattr_nosec() can again be called from the filesystem's gettattr
rather than vfs_getattr(). The latter would add unnecessary security
checks that the initial vfs_getattr_nosec() call wanted to avoid.
Therefore, introduce the getattr flag GETATTR_NOSEC and allow to pass
with the new getattr_flags parameter to the getattr interface function.
In overlayfs and ecryptfs use this flag to determine which one of the
two functions to call.

In a recent code change introduced to IMA vfs_getattr_nosec() ended up
calling vfs_getattr() in overlayfs, which in turn called
security_inode_getattr() on an exiting process that did not have
current->fs set anymore, which then caused a kernel NULL pointer
dereference. With this change the call to security_inode_getattr() can
be avoided, thus avoiding the NULL pointer dereference.

Reported-by: <syzbot+a67fc5321ffb4b311c98@syzkaller.appspotmail.com>
Fixes: db1d1e8b9867 ("IMA: use vfs_getattr_nosec to get the i_version")
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: <linux-fsdevel@vger.kernel.org>
Cc: Miklos Szeredi <miklos@szeredi.hu>
Cc: Amir Goldstein <amir73il@gmail.com>
Cc: Tyler Hicks <code@tyhicks.com>
Cc: Mimi Zohar <zohar@linux.ibm.com>
Suggested-by: Christian Brauner <brauner@kernel.org>
Co-developed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
Link: https://lore.kernel.org/r/20231002125733.1251467-1-stefanb@linux.vnet.ibm.com
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Christian Brauner <brauner@kernel.org>

authored by

Stefan Berger and committed by
Christian Brauner
8a924db2 b85ea95d

+31 -8
+10 -2
fs/ecryptfs/inode.c
··· 998 998 return rc; 999 999 } 1000 1000 1001 + static int ecryptfs_do_getattr(const struct path *path, struct kstat *stat, 1002 + u32 request_mask, unsigned int flags) 1003 + { 1004 + if (flags & AT_GETATTR_NOSEC) 1005 + return vfs_getattr_nosec(path, stat, request_mask, flags); 1006 + return vfs_getattr(path, stat, request_mask, flags); 1007 + } 1008 + 1001 1009 static int ecryptfs_getattr(struct mnt_idmap *idmap, 1002 1010 const struct path *path, struct kstat *stat, 1003 1011 u32 request_mask, unsigned int flags) ··· 1014 1006 struct kstat lower_stat; 1015 1007 int rc; 1016 1008 1017 - rc = vfs_getattr(ecryptfs_dentry_to_lower_path(dentry), &lower_stat, 1018 - request_mask, flags); 1009 + rc = ecryptfs_do_getattr(ecryptfs_dentry_to_lower_path(dentry), 1010 + &lower_stat, request_mask, flags); 1019 1011 if (!rc) { 1020 1012 fsstack_copy_attr_all(d_inode(dentry), 1021 1013 ecryptfs_inode_to_lower(d_inode(dentry)));
+5 -5
fs/overlayfs/inode.c
··· 171 171 172 172 type = ovl_path_real(dentry, &realpath); 173 173 old_cred = ovl_override_creds(dentry->d_sb); 174 - err = vfs_getattr(&realpath, stat, request_mask, flags); 174 + err = ovl_do_getattr(&realpath, stat, request_mask, flags); 175 175 if (err) 176 176 goto out; 177 177 ··· 196 196 (!is_dir ? STATX_NLINK : 0); 197 197 198 198 ovl_path_lower(dentry, &realpath); 199 - err = vfs_getattr(&realpath, &lowerstat, 200 - lowermask, flags); 199 + err = ovl_do_getattr(&realpath, &lowerstat, lowermask, 200 + flags); 201 201 if (err) 202 202 goto out; 203 203 ··· 249 249 250 250 ovl_path_lowerdata(dentry, &realpath); 251 251 if (realpath.dentry) { 252 - err = vfs_getattr(&realpath, &lowerdatastat, 253 - lowermask, flags); 252 + err = ovl_do_getattr(&realpath, &lowerdatastat, 253 + lowermask, flags); 254 254 if (err) 255 255 goto out; 256 256 } else {
+8
fs/overlayfs/overlayfs.h
··· 408 408 return ((OPEN_FMODE(flags) & FMODE_WRITE) || (flags & O_TRUNC)); 409 409 } 410 410 411 + static inline int ovl_do_getattr(const struct path *path, struct kstat *stat, 412 + u32 request_mask, unsigned int flags) 413 + { 414 + if (flags & AT_GETATTR_NOSEC) 415 + return vfs_getattr_nosec(path, stat, request_mask, flags); 416 + return vfs_getattr(path, stat, request_mask, flags); 417 + } 418 + 411 419 /* util.c */ 412 420 int ovl_get_write_access(struct dentry *dentry); 413 421 void ovl_put_write_access(struct dentry *dentry);
+5 -1
fs/stat.c
··· 133 133 idmap = mnt_idmap(path->mnt); 134 134 if (inode->i_op->getattr) 135 135 return inode->i_op->getattr(idmap, path, stat, 136 - request_mask, query_flags); 136 + request_mask, 137 + query_flags | AT_GETATTR_NOSEC); 137 138 138 139 generic_fillattr(idmap, request_mask, inode, stat); 139 140 return 0; ··· 166 165 u32 request_mask, unsigned int query_flags) 167 166 { 168 167 int retval; 168 + 169 + if (WARN_ON_ONCE(query_flags & AT_GETATTR_NOSEC)) 170 + return -EPERM; 169 171 170 172 retval = security_inode_getattr(path); 171 173 if (retval)
+3
include/uapi/linux/fcntl.h
··· 116 116 #define AT_HANDLE_FID AT_REMOVEDIR /* file handle is needed to 117 117 compare object identity and may not 118 118 be usable to open_by_handle_at(2) */ 119 + #if defined(__KERNEL__) 120 + #define AT_GETATTR_NOSEC 0x80000000 121 + #endif 119 122 120 123 #endif /* _UAPI_LINUX_FCNTL_H */