cache: fix pwd use-after-free in setting up fallback

Since the code exits smr section prior to calling pwd_hold, the used
pwd can be freed and a new one allocated with the same address, making
the comparison erroneously true.

Note it is very unlikely anyone ran into it.
This commit is contained in:
Mateusz Guzik 2020-10-05 19:38:51 +00:00
parent 9e9be081d8
commit 4e2266100d
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=366462
3 changed files with 20 additions and 10 deletions

View File

@ -3344,6 +3344,17 @@ pwd_hold_filedesc(struct filedesc *fdp)
return (pwd);
}
bool
pwd_hold_smr(struct pwd *pwd)
{
MPASS(pwd != NULL);
if (__predict_true(refcount_acquire_if_not_zero(&pwd->pwd_refcount))) {
return (true);
}
return (false);
}
struct pwd *
pwd_hold(struct thread *td)
{
@ -3354,8 +3365,7 @@ pwd_hold(struct thread *td)
vfs_smr_enter();
pwd = vfs_smr_entered_load(&fdp->fd_pwd);
MPASS(pwd != NULL);
if (__predict_true(refcount_acquire_if_not_zero(&pwd->pwd_refcount))) {
if (pwd_hold_smr(pwd)) {
vfs_smr_exit();
return (pwd);
}

View File

@ -3484,25 +3484,24 @@ cache_fplookup_partial_setup(struct cache_fpl *fpl)
ndp = fpl->ndp;
cnp = fpl->cnp;
pwd = fpl->pwd;
dvp = fpl->dvp;
dvp_seqc = fpl->dvp_seqc;
dvs = vget_prep_smr(dvp);
if (__predict_false(dvs == VGET_NONE)) {
if (!pwd_hold_smr(pwd)) {
cache_fpl_smr_exit(fpl);
return (cache_fpl_aborted(fpl));
}
dvs = vget_prep_smr(dvp);
cache_fpl_smr_exit(fpl);
vget_finish_ref(dvp, dvs);
if (!vn_seqc_consistent(dvp, dvp_seqc)) {
vrele(dvp);
if (__predict_false(dvs == VGET_NONE)) {
pwd_drop(pwd);
return (cache_fpl_aborted(fpl));
}
pwd = pwd_hold(curthread);
if (fpl->pwd != pwd) {
vget_finish_ref(dvp, dvs);
if (!vn_seqc_consistent(dvp, dvp_seqc)) {
vrele(dvp);
pwd_drop(pwd);
return (cache_fpl_aborted(fpl));

View File

@ -302,6 +302,7 @@ void pwd_ensure_dirs(void);
void pwd_set_rootvnode(void);
struct pwd *pwd_hold_filedesc(struct filedesc *fdp);
bool pwd_hold_smr(struct pwd *pwd);
struct pwd *pwd_hold(struct thread *td);
void pwd_drop(struct pwd *pwd);
static inline void