ovl: support encoding fid from inode with no alias

Dmitry Safonov reported that a WARN_ON() assertion can be trigered by
userspace when calling inotify_show_fdinfo() for an overlayfs watched
inode, whose dentry aliases were discarded with drop_caches.

The WARN_ON() assertion in inotify_show_fdinfo() was removed, because
it is possible for encoding file handle to fail for other reason, but
the impact of failing to encode an overlayfs file handle goes beyond
this assertion.

As shown in the LTP test case mentioned in the link below, failure to
encode an overlayfs file handle from a non-aliased inode also leads to
failure to report an fid with FAN_DELETE_SELF fanotify events.

As Dmitry notes in his analyzis of the problem, ovl_encode_fh() fails
if it cannot find an alias for the inode, but this failure can be fixed.
ovl_encode_fh() seldom uses the alias and in the case of non-decodable
file handles, as is often the case with fanotify fid info,
ovl_encode_fh() never needs to use the alias to encode a file handle.

Defer finding an alias until it is actually needed so ovl_encode_fh()
will not fail in the common case of FAN_DELETE_SELF fanotify events.

Fixes: 16aac5ad1fa9 ("ovl: support encoding non-decodable file handles")
Reported-by: Dmitry Safonov <dima@arista.com>
Closes: https://lore.kernel.org/linux-fsdevel/CAOQ4uxiie81voLZZi2zXS1BziXZCM24nXqPAxbu8kxXCUWdwOg@mail.gmail.com/
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Link: https://lore.kernel.org/r/20250105162404.357058-3-amir73il@gmail.com
Signed-off-by: Christian Brauner <brauner@kernel.org>

authored by Amir Goldstein and committed by Christian Brauner c45beebf 07aeefae

Changed files
+25 -21
fs
overlayfs
+25 -21
fs/overlayfs/export.c
··· 176 176 * 177 177 * Return 0 for upper file handle, > 0 for lower file handle or < 0 on error. 178 178 */ 179 - static int ovl_check_encode_origin(struct dentry *dentry) 179 + static int ovl_check_encode_origin(struct inode *inode) 180 180 { 181 - struct ovl_fs *ofs = OVL_FS(dentry->d_sb); 181 + struct ovl_fs *ofs = OVL_FS(inode->i_sb); 182 182 bool decodable = ofs->config.nfs_export; 183 + struct dentry *dentry; 184 + int err; 183 185 184 186 /* No upper layer? */ 185 187 if (!ovl_upper_mnt(ofs)) 186 188 return 1; 187 189 188 190 /* Lower file handle for non-upper non-decodable */ 189 - if (!ovl_dentry_upper(dentry) && !decodable) 191 + if (!ovl_inode_upper(inode) && !decodable) 190 192 return 1; 191 193 192 194 /* Upper file handle for pure upper */ 193 - if (!ovl_dentry_lower(dentry)) 195 + if (!ovl_inode_lower(inode)) 194 196 return 0; 195 197 196 198 /* 197 199 * Root is never indexed, so if there's an upper layer, encode upper for 198 200 * root. 199 201 */ 200 - if (dentry == dentry->d_sb->s_root) 202 + if (inode == d_inode(inode->i_sb->s_root)) 201 203 return 0; 202 204 203 205 /* 204 206 * Upper decodable file handle for non-indexed upper. 205 207 */ 206 - if (ovl_dentry_upper(dentry) && decodable && 207 - !ovl_test_flag(OVL_INDEX, d_inode(dentry))) 208 + if (ovl_inode_upper(inode) && decodable && 209 + !ovl_test_flag(OVL_INDEX, inode)) 208 210 return 0; 209 211 210 212 /* ··· 215 213 * ovl_connect_layer() will try to make origin's layer "connected" by 216 214 * copying up a "connectable" ancestor. 217 215 */ 218 - if (d_is_dir(dentry) && decodable) 219 - return ovl_connect_layer(dentry); 216 + if (!decodable || !S_ISDIR(inode->i_mode)) 217 + return 1; 218 + 219 + dentry = d_find_any_alias(inode); 220 + if (!dentry) 221 + return -ENOENT; 222 + 223 + err = ovl_connect_layer(dentry); 224 + dput(dentry); 225 + if (err < 0) 226 + return err; 220 227 221 228 /* Lower file handle for indexed and non-upper dir/non-dir */ 222 229 return 1; 223 230 } 224 231 225 - static int ovl_dentry_to_fid(struct ovl_fs *ofs, struct dentry *dentry, 232 + static int ovl_dentry_to_fid(struct ovl_fs *ofs, struct inode *inode, 226 233 u32 *fid, int buflen) 227 234 { 228 - struct inode *inode = d_inode(dentry); 229 235 struct ovl_fh *fh = NULL; 230 236 int err, enc_lower; 231 237 int len; ··· 242 232 * Check if we should encode a lower or upper file handle and maybe 243 233 * copy up an ancestor to make lower file handle connectable. 244 234 */ 245 - err = enc_lower = ovl_check_encode_origin(dentry); 235 + err = enc_lower = ovl_check_encode_origin(inode); 246 236 if (enc_lower < 0) 247 237 goto fail; 248 238 ··· 262 252 return err; 263 253 264 254 fail: 265 - pr_warn_ratelimited("failed to encode file handle (%pd2, err=%i)\n", 266 - dentry, err); 255 + pr_warn_ratelimited("failed to encode file handle (ino=%lu, err=%i)\n", 256 + inode->i_ino, err); 267 257 goto out; 268 258 } 269 259 ··· 271 261 struct inode *parent) 272 262 { 273 263 struct ovl_fs *ofs = OVL_FS(inode->i_sb); 274 - struct dentry *dentry; 275 264 int bytes, buflen = *max_len << 2; 276 265 277 266 /* TODO: encode connectable file handles */ 278 267 if (parent) 279 268 return FILEID_INVALID; 280 269 281 - dentry = d_find_any_alias(inode); 282 - if (!dentry) 283 - return FILEID_INVALID; 284 - 285 - bytes = ovl_dentry_to_fid(ofs, dentry, fid, buflen); 286 - dput(dentry); 270 + bytes = ovl_dentry_to_fid(ofs, inode, fid, buflen); 287 271 if (bytes <= 0) 288 272 return FILEID_INVALID; 289 273