Skip to content

Commit 01b39dc

Browse files
amir73ilMiklos Szeredi
authored andcommitted
ovl: use inode_insert5() to hash a newly created inode
Currently, there is a small window where ovl_obtain_alias() can race with ovl_instantiate() and create two different overlay inodes with the same underlying real non-dir non-hardlink inode. The race requires an adversary to guess the file handle of the yet to be created upper inode and decode the guessed file handle after ovl_creat_real(), but before ovl_instantiate(). This race does not affect overlay directory inodes, because those are decoded via ovl_lookup_real() and not with ovl_obtain_alias(). This patch fixes the race, by using inode_insert5() to add a newly created inode to cache. If the newly created inode apears to already exist in cache (hashed by the same real upper inode), we instantiate the dentry with the old inode and drop the new inode, instead of silently not hashing the new inode. Signed-off-by: Amir Goldstein <[email protected]> Signed-off-by: Miklos Szeredi <[email protected]>
1 parent ac6a52e commit 01b39dc

File tree

3 files changed

+60
-12
lines changed

3 files changed

+60
-12
lines changed

fs/overlayfs/dir.c

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -229,24 +229,54 @@ static int ovl_set_opaque(struct dentry *dentry, struct dentry *upperdentry)
229229
return ovl_set_opaque_xerr(dentry, upperdentry, -EIO);
230230
}
231231

232-
/* Common operations required to be done after creation of file on upper */
233-
static void ovl_instantiate(struct dentry *dentry, struct inode *inode,
234-
struct dentry *newdentry, bool hardlink)
232+
/*
233+
* Common operations required to be done after creation of file on upper.
234+
* If @hardlink is false, then @inode is a pre-allocated inode, we may or
235+
* may not use to instantiate the new dentry.
236+
*/
237+
static int ovl_instantiate(struct dentry *dentry, struct inode *inode,
238+
struct dentry *newdentry, bool hardlink)
235239
{
240+
struct ovl_inode_params oip = {
241+
.upperdentry = newdentry,
242+
.newinode = inode,
243+
};
244+
236245
ovl_dentry_version_inc(dentry->d_parent, false);
237246
ovl_dentry_set_upper_alias(dentry);
238247
if (!hardlink) {
239-
ovl_inode_update(inode, newdentry);
240-
ovl_copyattr(newdentry->d_inode, inode);
248+
/*
249+
* ovl_obtain_alias() can be called after ovl_create_real()
250+
* and before we get here, so we may get an inode from cache
251+
* with the same real upperdentry that is not the inode we
252+
* pre-allocated. In this case we will use the cached inode
253+
* to instantiate the new dentry.
254+
*
255+
* XXX: if we ever use ovl_obtain_alias() to decode directory
256+
* file handles, need to use ovl_get_inode_locked() and
257+
* d_instantiate_new() here to prevent from creating two
258+
* hashed directory inode aliases.
259+
*/
260+
inode = ovl_get_inode(dentry->d_sb, &oip);
261+
if (WARN_ON(IS_ERR(inode)))
262+
return PTR_ERR(inode);
241263
} else {
242264
WARN_ON(ovl_inode_real(inode) != d_inode(newdentry));
243265
dput(newdentry);
244266
inc_nlink(inode);
245267
}
268+
246269
d_instantiate(dentry, inode);
270+
if (inode != oip.newinode) {
271+
pr_warn_ratelimited("overlayfs: newly created inode found in cache (%pd2)\n",
272+
dentry);
273+
}
274+
247275
/* Force lookup of new upper hardlink to find its lower */
248276
if (hardlink)
249277
d_drop(dentry);
278+
279+
return 0;
250280
}
251281

252282
static bool ovl_type_merge(struct dentry *dentry)
@@ -285,11 +315,17 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode,
285315
ovl_set_opaque(dentry, newdentry);
286316
}
287317

288-
ovl_instantiate(dentry, inode, newdentry, !!attr->hardlink);
289-
err = 0;
318+
err = ovl_instantiate(dentry, inode, newdentry, !!attr->hardlink);
319+
if (err)
320+
goto out_cleanup;
290321
out_unlock:
291322
inode_unlock(udir);
292323
return err;
324+
325+
out_cleanup:
326+
ovl_cleanup(udir, newdentry);
327+
dput(newdentry);
328+
goto out_unlock;
293329
}
294330

295331
static struct dentry *ovl_clear_empty(struct dentry *dentry,
@@ -474,8 +510,9 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
474510
if (err)
475511
goto out_cleanup;
476512
}
477-
ovl_instantiate(dentry, inode, newdentry, hardlink);
478-
err = 0;
513+
err = ovl_instantiate(dentry, inode, newdentry, hardlink);
514+
if (err)
515+
goto out_cleanup;
479516
out_dput:
480517
dput(upper);
481518
out_unlock:
@@ -558,6 +595,7 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev,
558595
if (err)
559596
goto out;
560597

598+
/* Preallocate inode to be used by ovl_get_inode() */
561599
err = -ENOMEM;
562600
inode = ovl_new_inode(dentry->d_sb, mode, rdev);
563601
if (!inode)
@@ -567,7 +605,8 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev,
567605
attr.mode = inode->i_mode;
568606

569607
err = ovl_create_or_link(dentry, inode, &attr, false);
570-
if (err)
608+
/* Did we end up using the preallocated inode? */
609+
if (inode != d_inode(dentry))
571610
iput(inode);
572611

573612
out_drop_write:

fs/overlayfs/inode.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,15 @@ static bool ovl_hash_bylower(struct super_block *sb, struct dentry *upper,
749749
return true;
750750
}
751751

752+
static struct inode *ovl_iget5(struct super_block *sb, struct inode *newinode,
753+
struct inode *key)
754+
{
755+
return newinode ? inode_insert5(newinode, (unsigned long) key,
756+
ovl_inode_test, ovl_inode_set, key) :
757+
iget5_locked(sb, (unsigned long) key,
758+
ovl_inode_test, ovl_inode_set, key);
759+
}
760+
752761
struct inode *ovl_get_inode(struct super_block *sb,
753762
struct ovl_inode_params *oip)
754763
{
@@ -776,8 +785,7 @@ struct inode *ovl_get_inode(struct super_block *sb,
776785
upperdentry);
777786
unsigned int nlink = is_dir ? 1 : realinode->i_nlink;
778787

779-
inode = iget5_locked(sb, (unsigned long) key, ovl_inode_test,
780-
ovl_inode_set, key);
788+
inode = ovl_iget5(sb, oip->newinode, key);
781789
if (!inode)
782790
goto out_nomem;
783791
if (!(inode->i_state & I_NEW)) {

fs/overlayfs/overlayfs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ int ovl_update_time(struct inode *inode, struct timespec *ts, int flags);
329329
bool ovl_is_private_xattr(const char *name);
330330

331331
struct ovl_inode_params {
332+
struct inode *newinode;
332333
struct dentry *upperdentry;
333334
struct ovl_path *lowerpath;
334335
struct dentry *index;

0 commit comments

Comments
 (0)