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

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