Revert patches causing inode collision problems

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE/IPbcYBuWt0zoYhOq06b7GqY5nAFAmccC7UACgkQq06b7GqY
 5nBYbA/7BP/Me80+ofClszxd0F3nlXCGJPe31vC36EeFsap5p1TrIM7jvj0iR0Zr
 HXLxyTwocrubFgSE28rUrSS2nhmk016g3EUEVeoaO+hYSbvtdrRhoTSCubEwb4N5
 68vUz1ebGqZ35oT9oVZeAd+xdwxpKPktqrN6zCJsyWbSiZevE8YMkV+dgYrGde+Y
 j7BMypcxtQ2+u8donJ1PnGz04k0RNhqcqGGED0VuNw1Sp6quY8n0EWvHEeYjrb+N
 hFYB1lSeA245fVm8bmnUFHLJ2w47bxfJZ8hANbO9C0zLI98vlmH6xItXQnGofc4P
 1VtCHxoZnIFMMjK/iTPjvi4JsanNr0mS1Aa5w8HAHqyHYeIoBCpn78XFf79IhFAl
 pO3RV6nxE8fELXBM39Yyq7I3rQfyFmNBeob0UWIbazQ7cC54xBHpu0RDMj2gD6xy
 TBbZL2euRfGEswKWfrdR32U1jzSCv+jTAWew9sdx+z0RX9inRyaNBHFVsjOnVFZS
 iLJn20cotgw0WU2OhghWvgsV4o/Ckx4eJAC9oNxZ9pT1s7BqSvW1qg/Zlpn9wYgf
 UGQGXrCdXGY6XZ3W53c2joS+QWtmU6N8mAC6jCeqQm8pCQQeiTHE1QycyNc0xPGd
 ELhQYzCA1hEiNDki3e/vqzbPScpAer9dc/6hMKUT55SVfjcCBoc=
 =O6o1
 -----END PGP SIGNATURE-----

Merge tag '9p-for-6.12-rc5' of https://github.com/martinetd/linux

Pull more 9p reverts from Dominique Martinet:
 "Revert patches causing inode collision problems.

  The code simplification introduced significant regressions on servers
  that do not remap inode numbers when exporting multiple underlying
  filesystems with colliding inodes. See the top-most revert (commit
  be2ca38253) for details.

  This problem had been ignored for too long and the reverts will also
  head to stable (6.9+).

  I'm confident this set of patches gets us back to previous behaviour
  (another related patch had already been reverted back in April and
  we're almost back to square 1, and the rest didn't touch inode
  lifecycle)"

* tag '9p-for-6.12-rc5' of https://github.com/martinetd/linux:
  Revert "fs/9p: simplify iget to remove unnecessary paths"
  Revert "fs/9p: fix uaf in in v9fs_stat2inode_dotl"
  Revert "fs/9p: remove redundant pointer v9ses"
  Revert " fs/9p: mitigate inode collisions"
This commit is contained in:
Linus Torvalds 2024-10-25 15:25:02 -07:00
commit 850925a813
5 changed files with 192 additions and 87 deletions

View File

@ -179,14 +179,16 @@ extern int v9fs_vfs_rename(struct mnt_idmap *idmap,
struct inode *old_dir, struct dentry *old_dentry, struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry, struct inode *new_dir, struct dentry *new_dentry,
unsigned int flags); unsigned int flags);
extern struct inode *v9fs_fid_iget(struct super_block *sb, struct p9_fid *fid, extern struct inode *v9fs_inode_from_fid(struct v9fs_session_info *v9ses,
bool new); struct p9_fid *fid,
struct super_block *sb, int new);
extern const struct inode_operations v9fs_dir_inode_operations_dotl; extern const struct inode_operations v9fs_dir_inode_operations_dotl;
extern const struct inode_operations v9fs_file_inode_operations_dotl; extern const struct inode_operations v9fs_file_inode_operations_dotl;
extern const struct inode_operations v9fs_symlink_inode_operations_dotl; extern const struct inode_operations v9fs_symlink_inode_operations_dotl;
extern const struct netfs_request_ops v9fs_req_ops; extern const struct netfs_request_ops v9fs_req_ops;
extern struct inode *v9fs_fid_iget_dotl(struct super_block *sb, extern struct inode *v9fs_inode_from_fid_dotl(struct v9fs_session_info *v9ses,
struct p9_fid *fid, bool new); struct p9_fid *fid,
struct super_block *sb, int new);
/* other default globals */ /* other default globals */
#define V9FS_PORT 564 #define V9FS_PORT 564
@ -225,12 +227,30 @@ static inline int v9fs_proto_dotl(struct v9fs_session_info *v9ses)
*/ */
static inline struct inode * static inline struct inode *
v9fs_get_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid, v9fs_get_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid,
struct super_block *sb, bool new) struct super_block *sb)
{ {
if (v9fs_proto_dotl(v9ses)) if (v9fs_proto_dotl(v9ses))
return v9fs_fid_iget_dotl(sb, fid, new); return v9fs_inode_from_fid_dotl(v9ses, fid, sb, 0);
else else
return v9fs_fid_iget(sb, fid, new); return v9fs_inode_from_fid(v9ses, fid, sb, 0);
}
/**
* v9fs_get_new_inode_from_fid - Helper routine to populate an inode by
* issuing a attribute request
* @v9ses: session information
* @fid: fid to issue attribute request for
* @sb: superblock on which to create inode
*
*/
static inline struct inode *
v9fs_get_new_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid,
struct super_block *sb)
{
if (v9fs_proto_dotl(v9ses))
return v9fs_inode_from_fid_dotl(v9ses, fid, sb, 1);
else
return v9fs_inode_from_fid(v9ses, fid, sb, 1);
} }
#endif #endif

View File

@ -42,7 +42,7 @@ struct inode *v9fs_alloc_inode(struct super_block *sb);
void v9fs_free_inode(struct inode *inode); void v9fs_free_inode(struct inode *inode);
void v9fs_set_netfs_context(struct inode *inode); void v9fs_set_netfs_context(struct inode *inode);
int v9fs_init_inode(struct v9fs_session_info *v9ses, int v9fs_init_inode(struct v9fs_session_info *v9ses,
struct inode *inode, struct p9_qid *qid, umode_t mode, dev_t rdev); struct inode *inode, umode_t mode, dev_t rdev);
void v9fs_evict_inode(struct inode *inode); void v9fs_evict_inode(struct inode *inode);
#if (BITS_PER_LONG == 32) #if (BITS_PER_LONG == 32)
#define QID2INO(q) ((ino_t) (((q)->path+2) ^ (((q)->path) >> 32))) #define QID2INO(q) ((ino_t) (((q)->path+2) ^ (((q)->path) >> 32)))

View File

@ -256,12 +256,9 @@ void v9fs_set_netfs_context(struct inode *inode)
} }
int v9fs_init_inode(struct v9fs_session_info *v9ses, int v9fs_init_inode(struct v9fs_session_info *v9ses,
struct inode *inode, struct p9_qid *qid, umode_t mode, dev_t rdev) struct inode *inode, umode_t mode, dev_t rdev)
{ {
int err = 0; int err = 0;
struct v9fs_inode *v9inode = V9FS_I(inode);
memcpy(&v9inode->qid, qid, sizeof(struct p9_qid));
inode_init_owner(&nop_mnt_idmap, inode, NULL, mode); inode_init_owner(&nop_mnt_idmap, inode, NULL, mode);
inode->i_blocks = 0; inode->i_blocks = 0;
@ -365,59 +362,105 @@ void v9fs_evict_inode(struct inode *inode)
clear_inode(inode); clear_inode(inode);
} }
struct inode * static int v9fs_test_inode(struct inode *inode, void *data)
v9fs_fid_iget(struct super_block *sb, struct p9_fid *fid, bool new) {
int umode;
dev_t rdev;
struct v9fs_inode *v9inode = V9FS_I(inode);
struct p9_wstat *st = (struct p9_wstat *)data;
struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode);
umode = p9mode2unixmode(v9ses, st, &rdev);
/* don't match inode of different type */
if (inode_wrong_type(inode, umode))
return 0;
/* compare qid details */
if (memcmp(&v9inode->qid.version,
&st->qid.version, sizeof(v9inode->qid.version)))
return 0;
if (v9inode->qid.type != st->qid.type)
return 0;
if (v9inode->qid.path != st->qid.path)
return 0;
return 1;
}
static int v9fs_test_new_inode(struct inode *inode, void *data)
{
return 0;
}
static int v9fs_set_inode(struct inode *inode, void *data)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
struct p9_wstat *st = (struct p9_wstat *)data;
memcpy(&v9inode->qid, &st->qid, sizeof(st->qid));
return 0;
}
static struct inode *v9fs_qid_iget(struct super_block *sb,
struct p9_qid *qid,
struct p9_wstat *st,
int new)
{ {
dev_t rdev; dev_t rdev;
int retval; int retval;
umode_t umode; umode_t umode;
struct inode *inode; struct inode *inode;
struct p9_wstat *st;
struct v9fs_session_info *v9ses = sb->s_fs_info; struct v9fs_session_info *v9ses = sb->s_fs_info;
int (*test)(struct inode *inode, void *data);
inode = iget_locked(sb, QID2INO(&fid->qid)); if (new)
if (unlikely(!inode)) test = v9fs_test_new_inode;
else
test = v9fs_test_inode;
inode = iget5_locked(sb, QID2INO(qid), test, v9fs_set_inode, st);
if (!inode)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
if (!(inode->i_state & I_NEW)) { if (!(inode->i_state & I_NEW))
if (!new) { return inode;
goto done;
} else {
p9_debug(P9_DEBUG_VFS, "WARNING: Inode collision %ld\n",
inode->i_ino);
iput(inode);
remove_inode_hash(inode);
inode = iget_locked(sb, QID2INO(&fid->qid));
WARN_ON(!(inode->i_state & I_NEW));
}
}
/* /*
* initialize the inode with the stat info * initialize the inode with the stat info
* FIXME!! we may need support for stale inodes * FIXME!! we may need support for stale inodes
* later. * later.
*/ */
st = p9_client_stat(fid); inode->i_ino = QID2INO(qid);
if (IS_ERR(st)) {
retval = PTR_ERR(st);
goto error;
}
umode = p9mode2unixmode(v9ses, st, &rdev); umode = p9mode2unixmode(v9ses, st, &rdev);
retval = v9fs_init_inode(v9ses, inode, &fid->qid, umode, rdev); retval = v9fs_init_inode(v9ses, inode, umode, rdev);
v9fs_stat2inode(st, inode, sb, 0);
p9stat_free(st);
kfree(st);
if (retval) if (retval)
goto error; goto error;
v9fs_stat2inode(st, inode, sb, 0);
v9fs_set_netfs_context(inode); v9fs_set_netfs_context(inode);
v9fs_cache_inode_get_cookie(inode); v9fs_cache_inode_get_cookie(inode);
unlock_new_inode(inode); unlock_new_inode(inode);
done:
return inode; return inode;
error: error:
iget_failed(inode); iget_failed(inode);
return ERR_PTR(retval); return ERR_PTR(retval);
}
struct inode *
v9fs_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid,
struct super_block *sb, int new)
{
struct p9_wstat *st;
struct inode *inode = NULL;
st = p9_client_stat(fid);
if (IS_ERR(st))
return ERR_CAST(st);
inode = v9fs_qid_iget(sb, &st->qid, st, new);
p9stat_free(st);
kfree(st);
return inode;
} }
/** /**
@ -449,15 +492,8 @@ static int v9fs_at_to_dotl_flags(int flags)
*/ */
static void v9fs_dec_count(struct inode *inode) static void v9fs_dec_count(struct inode *inode)
{ {
if (!S_ISDIR(inode->i_mode) || inode->i_nlink > 2) { if (!S_ISDIR(inode->i_mode) || inode->i_nlink > 2)
if (inode->i_nlink) { drop_nlink(inode);
drop_nlink(inode);
} else {
p9_debug(P9_DEBUG_VFS,
"WARNING: unexpected i_nlink zero %d inode %ld\n",
inode->i_nlink, inode->i_ino);
}
}
} }
/** /**
@ -508,9 +544,6 @@ static int v9fs_remove(struct inode *dir, struct dentry *dentry, int flags)
} else } else
v9fs_dec_count(inode); v9fs_dec_count(inode);
if (inode->i_nlink <= 0) /* no more refs unhash it */
remove_inode_hash(inode);
v9fs_invalidate_inode_attr(inode); v9fs_invalidate_inode_attr(inode);
v9fs_invalidate_inode_attr(dir); v9fs_invalidate_inode_attr(dir);
@ -576,7 +609,7 @@ v9fs_create(struct v9fs_session_info *v9ses, struct inode *dir,
/* /*
* instantiate inode and assign the unopened fid to the dentry * instantiate inode and assign the unopened fid to the dentry
*/ */
inode = v9fs_get_inode_from_fid(v9ses, fid, dir->i_sb, true); inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb);
if (IS_ERR(inode)) { if (IS_ERR(inode)) {
err = PTR_ERR(inode); err = PTR_ERR(inode);
p9_debug(P9_DEBUG_VFS, p9_debug(P9_DEBUG_VFS,
@ -704,8 +737,10 @@ struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry,
inode = NULL; inode = NULL;
else if (IS_ERR(fid)) else if (IS_ERR(fid))
inode = ERR_CAST(fid); inode = ERR_CAST(fid);
else if (v9ses->cache & (CACHE_META|CACHE_LOOSE))
inode = v9fs_get_inode_from_fid(v9ses, fid, dir->i_sb);
else else
inode = v9fs_get_inode_from_fid(v9ses, fid, dir->i_sb, false); inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb);
/* /*
* If we had a rename on the server and a parallel lookup * If we had a rename on the server and a parallel lookup
* for the new name, then make sure we instantiate with * for the new name, then make sure we instantiate with

View File

@ -52,50 +52,80 @@ static kgid_t v9fs_get_fsgid_for_create(struct inode *dir_inode)
return current_fsgid(); return current_fsgid();
} }
static int v9fs_test_inode_dotl(struct inode *inode, void *data)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
struct p9_stat_dotl *st = (struct p9_stat_dotl *)data;
/* don't match inode of different type */
if (inode_wrong_type(inode, st->st_mode))
return 0;
struct inode * if (inode->i_generation != st->st_gen)
v9fs_fid_iget_dotl(struct super_block *sb, struct p9_fid *fid, bool new) return 0;
/* compare qid details */
if (memcmp(&v9inode->qid.version,
&st->qid.version, sizeof(v9inode->qid.version)))
return 0;
if (v9inode->qid.type != st->qid.type)
return 0;
if (v9inode->qid.path != st->qid.path)
return 0;
return 1;
}
/* Always get a new inode */
static int v9fs_test_new_inode_dotl(struct inode *inode, void *data)
{
return 0;
}
static int v9fs_set_inode_dotl(struct inode *inode, void *data)
{
struct v9fs_inode *v9inode = V9FS_I(inode);
struct p9_stat_dotl *st = (struct p9_stat_dotl *)data;
memcpy(&v9inode->qid, &st->qid, sizeof(st->qid));
inode->i_generation = st->st_gen;
return 0;
}
static struct inode *v9fs_qid_iget_dotl(struct super_block *sb,
struct p9_qid *qid,
struct p9_fid *fid,
struct p9_stat_dotl *st,
int new)
{ {
int retval; int retval;
struct inode *inode; struct inode *inode;
struct p9_stat_dotl *st;
struct v9fs_session_info *v9ses = sb->s_fs_info; struct v9fs_session_info *v9ses = sb->s_fs_info;
int (*test)(struct inode *inode, void *data);
inode = iget_locked(sb, QID2INO(&fid->qid)); if (new)
if (unlikely(!inode)) test = v9fs_test_new_inode_dotl;
else
test = v9fs_test_inode_dotl;
inode = iget5_locked(sb, QID2INO(qid), test, v9fs_set_inode_dotl, st);
if (!inode)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
if (!(inode->i_state & I_NEW)) { if (!(inode->i_state & I_NEW))
if (!new) { return inode;
goto done;
} else { /* deal with race condition in inode number reuse */
p9_debug(P9_DEBUG_ERROR, "WARNING: Inode collision %lx\n",
inode->i_ino);
iput(inode);
remove_inode_hash(inode);
inode = iget_locked(sb, QID2INO(&fid->qid));
WARN_ON(!(inode->i_state & I_NEW));
}
}
/* /*
* initialize the inode with the stat info * initialize the inode with the stat info
* FIXME!! we may need support for stale inodes * FIXME!! we may need support for stale inodes
* later. * later.
*/ */
st = p9_client_getattr_dotl(fid, P9_STATS_BASIC | P9_STATS_GEN); inode->i_ino = QID2INO(qid);
if (IS_ERR(st)) { retval = v9fs_init_inode(v9ses, inode,
retval = PTR_ERR(st);
goto error;
}
retval = v9fs_init_inode(v9ses, inode, &fid->qid,
st->st_mode, new_decode_dev(st->st_rdev)); st->st_mode, new_decode_dev(st->st_rdev));
v9fs_stat2inode_dotl(st, inode, 0);
kfree(st);
if (retval) if (retval)
goto error; goto error;
v9fs_stat2inode_dotl(st, inode, 0);
v9fs_set_netfs_context(inode); v9fs_set_netfs_context(inode);
v9fs_cache_inode_get_cookie(inode); v9fs_cache_inode_get_cookie(inode);
retval = v9fs_get_acl(inode, fid); retval = v9fs_get_acl(inode, fid);
@ -103,11 +133,27 @@ v9fs_fid_iget_dotl(struct super_block *sb, struct p9_fid *fid, bool new)
goto error; goto error;
unlock_new_inode(inode); unlock_new_inode(inode);
done:
return inode; return inode;
error: error:
iget_failed(inode); iget_failed(inode);
return ERR_PTR(retval); return ERR_PTR(retval);
}
struct inode *
v9fs_inode_from_fid_dotl(struct v9fs_session_info *v9ses, struct p9_fid *fid,
struct super_block *sb, int new)
{
struct p9_stat_dotl *st;
struct inode *inode = NULL;
st = p9_client_getattr_dotl(fid, P9_STATS_BASIC | P9_STATS_GEN);
if (IS_ERR(st))
return ERR_CAST(st);
inode = v9fs_qid_iget_dotl(sb, &st->qid, fid, st, new);
kfree(st);
return inode;
} }
struct dotl_openflag_map { struct dotl_openflag_map {
@ -259,7 +305,7 @@ v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry,
p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n", err); p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n", err);
goto out; goto out;
} }
inode = v9fs_fid_iget_dotl(dir->i_sb, fid, true); inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb);
if (IS_ERR(inode)) { if (IS_ERR(inode)) {
err = PTR_ERR(inode); err = PTR_ERR(inode);
p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n", err); p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n", err);
@ -309,6 +355,7 @@ static int v9fs_vfs_mkdir_dotl(struct mnt_idmap *idmap,
umode_t omode) umode_t omode)
{ {
int err; int err;
struct v9fs_session_info *v9ses;
struct p9_fid *fid = NULL, *dfid = NULL; struct p9_fid *fid = NULL, *dfid = NULL;
kgid_t gid; kgid_t gid;
const unsigned char *name; const unsigned char *name;
@ -318,6 +365,7 @@ static int v9fs_vfs_mkdir_dotl(struct mnt_idmap *idmap,
struct posix_acl *dacl = NULL, *pacl = NULL; struct posix_acl *dacl = NULL, *pacl = NULL;
p9_debug(P9_DEBUG_VFS, "name %pd\n", dentry); p9_debug(P9_DEBUG_VFS, "name %pd\n", dentry);
v9ses = v9fs_inode2v9ses(dir);
omode |= S_IFDIR; omode |= S_IFDIR;
if (dir->i_mode & S_ISGID) if (dir->i_mode & S_ISGID)
@ -352,7 +400,7 @@ static int v9fs_vfs_mkdir_dotl(struct mnt_idmap *idmap,
} }
/* instantiate inode and assign the unopened fid to the dentry */ /* instantiate inode and assign the unopened fid to the dentry */
inode = v9fs_fid_iget_dotl(dir->i_sb, fid, true); inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb);
if (IS_ERR(inode)) { if (IS_ERR(inode)) {
err = PTR_ERR(inode); err = PTR_ERR(inode);
p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n", p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n",
@ -749,6 +797,7 @@ v9fs_vfs_mknod_dotl(struct mnt_idmap *idmap, struct inode *dir,
kgid_t gid; kgid_t gid;
const unsigned char *name; const unsigned char *name;
umode_t mode; umode_t mode;
struct v9fs_session_info *v9ses;
struct p9_fid *fid = NULL, *dfid = NULL; struct p9_fid *fid = NULL, *dfid = NULL;
struct inode *inode; struct inode *inode;
struct p9_qid qid; struct p9_qid qid;
@ -758,6 +807,7 @@ v9fs_vfs_mknod_dotl(struct mnt_idmap *idmap, struct inode *dir,
dir->i_ino, dentry, omode, dir->i_ino, dentry, omode,
MAJOR(rdev), MINOR(rdev)); MAJOR(rdev), MINOR(rdev));
v9ses = v9fs_inode2v9ses(dir);
dfid = v9fs_parent_fid(dentry); dfid = v9fs_parent_fid(dentry);
if (IS_ERR(dfid)) { if (IS_ERR(dfid)) {
err = PTR_ERR(dfid); err = PTR_ERR(dfid);
@ -788,7 +838,7 @@ v9fs_vfs_mknod_dotl(struct mnt_idmap *idmap, struct inode *dir,
err); err);
goto error; goto error;
} }
inode = v9fs_fid_iget_dotl(dir->i_sb, fid, true); inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb);
if (IS_ERR(inode)) { if (IS_ERR(inode)) {
err = PTR_ERR(inode); err = PTR_ERR(inode);
p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n", p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n",

View File

@ -139,7 +139,7 @@ static struct dentry *v9fs_mount(struct file_system_type *fs_type, int flags,
else else
sb->s_d_op = &v9fs_dentry_operations; sb->s_d_op = &v9fs_dentry_operations;
inode = v9fs_get_inode_from_fid(v9ses, fid, sb, true); inode = v9fs_get_new_inode_from_fid(v9ses, fid, sb);
if (IS_ERR(inode)) { if (IS_ERR(inode)) {
retval = PTR_ERR(inode); retval = PTR_ERR(inode);
goto release_sb; goto release_sb;