* [PATCH v4] smb: client: fix potential UAF in smb2_close_cached_fid()
@ 2025-11-03 22:52 Henrique Carvalho
2025-11-04 14:58 ` Steve French
2025-11-04 22:42 ` Steve French
0 siblings, 2 replies; 8+ messages in thread
From: Henrique Carvalho @ 2025-11-03 22:52 UTC (permalink / raw)
To: sfrench
Cc: pc, ronniesahlberg, sprasad, tom, bharathsm, ematsumiya, jaeshin,
linux-cifs, Henrique Carvalho
find_or_create_cached_dir() could grab a new reference after kref_put()
had seen the refcount drop to zero but before cfid_list_lock is acquired
in smb2_close_cached_fid(), leading to use-after-free.
Switch to kref_put_lock() so cfid_release() is called with
cfid_list_lock held, closing that gap.
Fixes: ebe98f1447bb ("cifs: enable caching of directories for which a lease is held")
Cc: stable@vger.kernel.org
Reported-by: Jay Shin <jaeshin@redhat.com>
Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org>
Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com>
---
V3 -> V4: rebase, added Reviewed-by and Reported-by, add
lockdep_assert_held in smb2_close_cached_fid, change commit subject (was
"smb: client: fix race in smb2_close_cached_fid()") to clearly state the
bug class (UAF)
V2 -> V3: rebase, remove unneeded comments, modify commit subject
V1 -> V2: kept the original function names and added __releases annotation
to silence sparse warning
---
fs/smb/client/cached_dir.c | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index b8ac7b7faf61..018055fd2cdb 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -388,11 +388,11 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
* lease. Release one here, and the second below.
*/
cfid->has_lease = false;
- kref_put(&cfid->refcount, smb2_close_cached_fid);
+ close_cached_dir(cfid);
}
spin_unlock(&cfids->cfid_list_lock);
- kref_put(&cfid->refcount, smb2_close_cached_fid);
+ close_cached_dir(cfid);
} else {
*ret_cfid = cfid;
atomic_inc(&tcon->num_remote_opens);
@@ -438,12 +438,14 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon,
static void
smb2_close_cached_fid(struct kref *ref)
+__releases(&cfid->cfids->cfid_list_lock)
{
struct cached_fid *cfid = container_of(ref, struct cached_fid,
refcount);
int rc;
- spin_lock(&cfid->cfids->cfid_list_lock);
+ lockdep_assert_held(&cfid->cfids->cfid_list_lock);
+
if (cfid->on_list) {
list_del(&cfid->entry);
cfid->on_list = false;
@@ -478,7 +480,7 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon,
spin_lock(&cfid->cfids->cfid_list_lock);
if (cfid->has_lease) {
cfid->has_lease = false;
- kref_put(&cfid->refcount, smb2_close_cached_fid);
+ close_cached_dir(cfid);
}
spin_unlock(&cfid->cfids->cfid_list_lock);
close_cached_dir(cfid);
@@ -487,7 +489,7 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon,
void close_cached_dir(struct cached_fid *cfid)
{
- kref_put(&cfid->refcount, smb2_close_cached_fid);
+ kref_put_lock(&cfid->refcount, smb2_close_cached_fid, &cfid->cfids->cfid_list_lock);
}
/*
@@ -596,7 +598,7 @@ cached_dir_offload_close(struct work_struct *work)
WARN_ON(cfid->on_list);
- kref_put(&cfid->refcount, smb2_close_cached_fid);
+ close_cached_dir(cfid);
cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cached_close);
}
@@ -762,7 +764,7 @@ static void cfids_laundromat_worker(struct work_struct *work)
* Drop the ref-count from above, either the lease-ref (if there
* was one) or the extra one acquired.
*/
- kref_put(&cfid->refcount, smb2_close_cached_fid);
+ close_cached_dir(cfid);
}
queue_delayed_work(cfid_put_wq, &cfids->laundromat_work,
dir_cache_timeout * HZ);
--
2.50.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH v4] smb: client: fix potential UAF in smb2_close_cached_fid() 2025-11-03 22:52 [PATCH v4] smb: client: fix potential UAF in smb2_close_cached_fid() Henrique Carvalho @ 2025-11-04 14:58 ` Steve French 2025-11-04 22:42 ` Steve French 1 sibling, 0 replies; 8+ messages in thread From: Steve French @ 2025-11-04 14:58 UTC (permalink / raw) To: Henrique Carvalho Cc: pc, ronniesahlberg, sprasad, tom, bharathsm, ematsumiya, jaeshin, linux-cifs Added to for-next . thx On Mon, Nov 3, 2025 at 5:00 PM Henrique Carvalho <henrique.carvalho@suse.com> wrote: > > find_or_create_cached_dir() could grab a new reference after kref_put() > had seen the refcount drop to zero but before cfid_list_lock is acquired > in smb2_close_cached_fid(), leading to use-after-free. > > Switch to kref_put_lock() so cfid_release() is called with > cfid_list_lock held, closing that gap. > > Fixes: ebe98f1447bb ("cifs: enable caching of directories for which a lease is held") > Cc: stable@vger.kernel.org > Reported-by: Jay Shin <jaeshin@redhat.com> > Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org> > Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com> > --- > V3 -> V4: rebase, added Reviewed-by and Reported-by, add > lockdep_assert_held in smb2_close_cached_fid, change commit subject (was > "smb: client: fix race in smb2_close_cached_fid()") to clearly state the > bug class (UAF) > V2 -> V3: rebase, remove unneeded comments, modify commit subject > V1 -> V2: kept the original function names and added __releases annotation > to silence sparse warning > --- > fs/smb/client/cached_dir.c | 16 +++++++++------- > 1 file changed, 9 insertions(+), 7 deletions(-) > > diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c > index b8ac7b7faf61..018055fd2cdb 100644 > --- a/fs/smb/client/cached_dir.c > +++ b/fs/smb/client/cached_dir.c > @@ -388,11 +388,11 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, > * lease. Release one here, and the second below. > */ > cfid->has_lease = false; > - kref_put(&cfid->refcount, smb2_close_cached_fid); > + close_cached_dir(cfid); > } > spin_unlock(&cfids->cfid_list_lock); > > - kref_put(&cfid->refcount, smb2_close_cached_fid); > + close_cached_dir(cfid); > } else { > *ret_cfid = cfid; > atomic_inc(&tcon->num_remote_opens); > @@ -438,12 +438,14 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon, > > static void > smb2_close_cached_fid(struct kref *ref) > +__releases(&cfid->cfids->cfid_list_lock) > { > struct cached_fid *cfid = container_of(ref, struct cached_fid, > refcount); > int rc; > > - spin_lock(&cfid->cfids->cfid_list_lock); > + lockdep_assert_held(&cfid->cfids->cfid_list_lock); > + > if (cfid->on_list) { > list_del(&cfid->entry); > cfid->on_list = false; > @@ -478,7 +480,7 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon, > spin_lock(&cfid->cfids->cfid_list_lock); > if (cfid->has_lease) { > cfid->has_lease = false; > - kref_put(&cfid->refcount, smb2_close_cached_fid); > + close_cached_dir(cfid); > } > spin_unlock(&cfid->cfids->cfid_list_lock); > close_cached_dir(cfid); > @@ -487,7 +489,7 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon, > > void close_cached_dir(struct cached_fid *cfid) > { > - kref_put(&cfid->refcount, smb2_close_cached_fid); > + kref_put_lock(&cfid->refcount, smb2_close_cached_fid, &cfid->cfids->cfid_list_lock); > } > > /* > @@ -596,7 +598,7 @@ cached_dir_offload_close(struct work_struct *work) > > WARN_ON(cfid->on_list); > > - kref_put(&cfid->refcount, smb2_close_cached_fid); > + close_cached_dir(cfid); > cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cached_close); > } > > @@ -762,7 +764,7 @@ static void cfids_laundromat_worker(struct work_struct *work) > * Drop the ref-count from above, either the lease-ref (if there > * was one) or the extra one acquired. > */ > - kref_put(&cfid->refcount, smb2_close_cached_fid); > + close_cached_dir(cfid); > } > queue_delayed_work(cfid_put_wq, &cfids->laundromat_work, > dir_cache_timeout * HZ); > -- > 2.50.1 > > -- Thanks, Steve ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4] smb: client: fix potential UAF in smb2_close_cached_fid() 2025-11-03 22:52 [PATCH v4] smb: client: fix potential UAF in smb2_close_cached_fid() Henrique Carvalho 2025-11-04 14:58 ` Steve French @ 2025-11-04 22:42 ` Steve French 2025-11-05 13:30 ` Henrique Carvalho 1 sibling, 1 reply; 8+ messages in thread From: Steve French @ 2025-11-04 22:42 UTC (permalink / raw) To: Henrique Carvalho Cc: pc, ronniesahlberg, sprasad, tom, bharathsm, ematsumiya, jaeshin, linux-cifs One minor comment that AI had on this was on the change to fs/smb/client/cached_dir.c (line 447). See below. -spin_lock(&cfid->cfids->cfid_list_lock); +lockdep_assert_held(&cfid->cfids->cfid_list_lock); The lockdep_assert_held validates that the lock is held on entry, but the function signature __releases(&cfid->cfids->cfid_list_lock) indicates the lock is released inside. Consider adding a corresponding __acquires annotation to the call sites that call this function via kref_put_lock, or document why callers must hold the lock before calling kref_put_lock. On Mon, Nov 3, 2025 at 5:00 PM Henrique Carvalho <henrique.carvalho@suse.com> wrote: > > find_or_create_cached_dir() could grab a new reference after kref_put() > had seen the refcount drop to zero but before cfid_list_lock is acquired > in smb2_close_cached_fid(), leading to use-after-free. > > Switch to kref_put_lock() so cfid_release() is called with > cfid_list_lock held, closing that gap. > > Fixes: ebe98f1447bb ("cifs: enable caching of directories for which a lease is held") > Cc: stable@vger.kernel.org > Reported-by: Jay Shin <jaeshin@redhat.com> > Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org> > Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com> > --- > V3 -> V4: rebase, added Reviewed-by and Reported-by, add > lockdep_assert_held in smb2_close_cached_fid, change commit subject (was > "smb: client: fix race in smb2_close_cached_fid()") to clearly state the > bug class (UAF) > V2 -> V3: rebase, remove unneeded comments, modify commit subject > V1 -> V2: kept the original function names and added __releases annotation > to silence sparse warning > --- > fs/smb/client/cached_dir.c | 16 +++++++++------- > 1 file changed, 9 insertions(+), 7 deletions(-) > > diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c > index b8ac7b7faf61..018055fd2cdb 100644 > --- a/fs/smb/client/cached_dir.c > +++ b/fs/smb/client/cached_dir.c > @@ -388,11 +388,11 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, > * lease. Release one here, and the second below. > */ > cfid->has_lease = false; > - kref_put(&cfid->refcount, smb2_close_cached_fid); > + close_cached_dir(cfid); > } > spin_unlock(&cfids->cfid_list_lock); > > - kref_put(&cfid->refcount, smb2_close_cached_fid); > + close_cached_dir(cfid); > } else { > *ret_cfid = cfid; > atomic_inc(&tcon->num_remote_opens); > @@ -438,12 +438,14 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon, > > static void > smb2_close_cached_fid(struct kref *ref) > +__releases(&cfid->cfids->cfid_list_lock) > { > struct cached_fid *cfid = container_of(ref, struct cached_fid, > refcount); > int rc; > > - spin_lock(&cfid->cfids->cfid_list_lock); > + lockdep_assert_held(&cfid->cfids->cfid_list_lock); > + > if (cfid->on_list) { > list_del(&cfid->entry); > cfid->on_list = false; > @@ -478,7 +480,7 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon, > spin_lock(&cfid->cfids->cfid_list_lock); > if (cfid->has_lease) { > cfid->has_lease = false; > - kref_put(&cfid->refcount, smb2_close_cached_fid); > + close_cached_dir(cfid); > } > spin_unlock(&cfid->cfids->cfid_list_lock); > close_cached_dir(cfid); > @@ -487,7 +489,7 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon, > > void close_cached_dir(struct cached_fid *cfid) > { > - kref_put(&cfid->refcount, smb2_close_cached_fid); > + kref_put_lock(&cfid->refcount, smb2_close_cached_fid, &cfid->cfids->cfid_list_lock); > } > > /* > @@ -596,7 +598,7 @@ cached_dir_offload_close(struct work_struct *work) > > WARN_ON(cfid->on_list); > > - kref_put(&cfid->refcount, smb2_close_cached_fid); > + close_cached_dir(cfid); > cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cached_close); > } > > @@ -762,7 +764,7 @@ static void cfids_laundromat_worker(struct work_struct *work) > * Drop the ref-count from above, either the lease-ref (if there > * was one) or the extra one acquired. > */ > - kref_put(&cfid->refcount, smb2_close_cached_fid); > + close_cached_dir(cfid); > } > queue_delayed_work(cfid_put_wq, &cfids->laundromat_work, > dir_cache_timeout * HZ); > -- > 2.50.1 > > -- Thanks, Steve ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4] smb: client: fix potential UAF in smb2_close_cached_fid() 2025-11-04 22:42 ` Steve French @ 2025-11-05 13:30 ` Henrique Carvalho 2025-11-05 16:16 ` Henrique Carvalho 0 siblings, 1 reply; 8+ messages in thread From: Henrique Carvalho @ 2025-11-05 13:30 UTC (permalink / raw) To: Steve French Cc: pc, ronniesahlberg, sprasad, tom, bharathsm, ematsumiya, jaeshin, linux-cifs Hi Steve, On 11/4/25 7:42 PM, Steve French wrote: > One minor comment that AI had on this was on the change to > fs/smb/client/cached_dir.c (line 447). See below. > > -spin_lock(&cfid->cfids->cfid_list_lock); > +lockdep_assert_held(&cfid->cfids->cfid_list_lock); > > > The lockdep_assert_held validates that the lock is held on entry, but > the function signature __releases(&cfid->cfids->cfid_list_lock) > indicates the lock is released inside. Consider adding a corresponding > __acquires annotation to the call sites that call this function via > kref_put_lock, or document why callers must hold the lock before > calling kref_put_lock. But we don't actually acquire the lock every time in close_cached_dir. Only when (a) refcount is 1; and (b) refcount did not increase after we acquired the lock. So where should __acquires annotation go? One option would be adding documentation to smb2_close_cached_fid, though since it's a release function that shouldn't be called elsewhere, I'm not sure that adds much value. Do you have thoughts? > > On Mon, Nov 3, 2025 at 5:00 PM Henrique Carvalho > <henrique.carvalho@suse.com> wrote: >> >> find_or_create_cached_dir() could grab a new reference after kref_put() >> had seen the refcount drop to zero but before cfid_list_lock is acquired >> in smb2_close_cached_fid(), leading to use-after-free. >> >> Switch to kref_put_lock() so cfid_release() is called with >> cfid_list_lock held, closing that gap. >> >> Fixes: ebe98f1447bb ("cifs: enable caching of directories for which a lease is held") >> Cc: stable@vger.kernel.org >> Reported-by: Jay Shin <jaeshin@redhat.com> >> Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org> >> Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com> >> --- >> V3 -> V4: rebase, added Reviewed-by and Reported-by, add >> lockdep_assert_held in smb2_close_cached_fid, change commit subject (was >> "smb: client: fix race in smb2_close_cached_fid()") to clearly state the >> bug class (UAF) >> V2 -> V3: rebase, remove unneeded comments, modify commit subject >> V1 -> V2: kept the original function names and added __releases annotation >> to silence sparse warning >> --- >> fs/smb/client/cached_dir.c | 16 +++++++++------- >> 1 file changed, 9 insertions(+), 7 deletions(-) >> >> diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c >> index b8ac7b7faf61..018055fd2cdb 100644 >> --- a/fs/smb/client/cached_dir.c >> +++ b/fs/smb/client/cached_dir.c >> @@ -388,11 +388,11 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, >> * lease. Release one here, and the second below. >> */ >> cfid->has_lease = false; >> - kref_put(&cfid->refcount, smb2_close_cached_fid); >> + close_cached_dir(cfid); >> } >> spin_unlock(&cfids->cfid_list_lock); >> >> - kref_put(&cfid->refcount, smb2_close_cached_fid); >> + close_cached_dir(cfid); >> } else { >> *ret_cfid = cfid; >> atomic_inc(&tcon->num_remote_opens); >> @@ -438,12 +438,14 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon, >> >> static void >> smb2_close_cached_fid(struct kref *ref) >> +__releases(&cfid->cfids->cfid_list_lock) >> { >> struct cached_fid *cfid = container_of(ref, struct cached_fid, >> refcount); >> int rc; >> >> - spin_lock(&cfid->cfids->cfid_list_lock); >> + lockdep_assert_held(&cfid->cfids->cfid_list_lock); >> + >> if (cfid->on_list) { >> list_del(&cfid->entry); >> cfid->on_list = false; >> @@ -478,7 +480,7 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon, >> spin_lock(&cfid->cfids->cfid_list_lock); >> if (cfid->has_lease) { >> cfid->has_lease = false; >> - kref_put(&cfid->refcount, smb2_close_cached_fid); >> + close_cached_dir(cfid); >> } >> spin_unlock(&cfid->cfids->cfid_list_lock); >> close_cached_dir(cfid); >> @@ -487,7 +489,7 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon, >> >> void close_cached_dir(struct cached_fid *cfid) >> { >> - kref_put(&cfid->refcount, smb2_close_cached_fid); >> + kref_put_lock(&cfid->refcount, smb2_close_cached_fid, &cfid->cfids->cfid_list_lock); >> } >> >> /* >> @@ -596,7 +598,7 @@ cached_dir_offload_close(struct work_struct *work) >> >> WARN_ON(cfid->on_list); >> >> - kref_put(&cfid->refcount, smb2_close_cached_fid); >> + close_cached_dir(cfid); >> cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cached_close); >> } >> >> @@ -762,7 +764,7 @@ static void cfids_laundromat_worker(struct work_struct *work) >> * Drop the ref-count from above, either the lease-ref (if there >> * was one) or the extra one acquired. >> */ >> - kref_put(&cfid->refcount, smb2_close_cached_fid); >> + close_cached_dir(cfid); >> } >> queue_delayed_work(cfid_put_wq, &cfids->laundromat_work, >> dir_cache_timeout * HZ); >> -- >> 2.50.1 >> >> > > -- Henrique SUSE Labs ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4] smb: client: fix potential UAF in smb2_close_cached_fid() 2025-11-05 13:30 ` Henrique Carvalho @ 2025-11-05 16:16 ` Henrique Carvalho [not found] ` <CAH2r5mvkefptGUvjarOmOW=hQA7_ZRFLwOk_jptOSAyzuMUGuw@mail.gmail.com> 0 siblings, 1 reply; 8+ messages in thread From: Henrique Carvalho @ 2025-11-05 16:16 UTC (permalink / raw) To: Steve French Cc: pc, ronniesahlberg, sprasad, tom, bharathsm, ematsumiya, jaeshin, linux-cifs On 11/5/25 10:30 AM, Henrique Carvalho wrote: > Hi Steve, > > On 11/4/25 7:42 PM, Steve French wrote: >> One minor comment that AI had on this was on the change to >> fs/smb/client/cached_dir.c (line 447). See below. >> >> -spin_lock(&cfid->cfids->cfid_list_lock); >> +lockdep_assert_held(&cfid->cfids->cfid_list_lock); >> >> >> The lockdep_assert_held validates that the lock is held on entry, but >> the function signature __releases(&cfid->cfids->cfid_list_lock) >> indicates the lock is released inside. Consider adding a corresponding >> __acquires annotation to the call sites that call this function via >> kref_put_lock, or document why callers must hold the lock before >> calling kref_put_lock. > > But we don't actually acquire the lock every time in close_cached_dir. > Only when (a) refcount is 1; and (b) refcount did not increase after we > acquired the lock. So where should __acquires annotation go? > > One option would be adding documentation to smb2_close_cached_fid, > though since it's a release function that shouldn't be called elsewhere, > I'm not sure that adds much value. Do you have thoughts? > I think it's worth adding that kref_put_lock uses refcount_dec_and_lock, which is defined as extern __must_check bool refcount_dec_and_lock(refcount_t *r, spinlock_t *lock) __cond_acquires(lock); __cond_acquires() was introduced in commit 4a557a5d1a614 ("sparse: introduce conditional lock acquire function attribute"). So in a sense, if I understand it correctly, the "acquires" is already there to make the sparse __releases annotation work. This should be enough to satisfy the comment the AI raised. >> >> On Mon, Nov 3, 2025 at 5:00 PM Henrique Carvalho >> <henrique.carvalho@suse.com> wrote: >>> >>> find_or_create_cached_dir() could grab a new reference after kref_put() >>> had seen the refcount drop to zero but before cfid_list_lock is acquired >>> in smb2_close_cached_fid(), leading to use-after-free. >>> >>> Switch to kref_put_lock() so cfid_release() is called with >>> cfid_list_lock held, closing that gap. >>> >>> Fixes: ebe98f1447bb ("cifs: enable caching of directories for which a lease is held") >>> Cc: stable@vger.kernel.org >>> Reported-by: Jay Shin <jaeshin@redhat.com> >>> Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org> >>> Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com> >>> --- >>> V3 -> V4: rebase, added Reviewed-by and Reported-by, add >>> lockdep_assert_held in smb2_close_cached_fid, change commit subject (was >>> "smb: client: fix race in smb2_close_cached_fid()") to clearly state the >>> bug class (UAF) >>> V2 -> V3: rebase, remove unneeded comments, modify commit subject >>> V1 -> V2: kept the original function names and added __releases annotation >>> to silence sparse warning >>> --- >>> fs/smb/client/cached_dir.c | 16 +++++++++------- >>> 1 file changed, 9 insertions(+), 7 deletions(-) >>> >>> diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c >>> index b8ac7b7faf61..018055fd2cdb 100644 >>> --- a/fs/smb/client/cached_dir.c >>> +++ b/fs/smb/client/cached_dir.c >>> @@ -388,11 +388,11 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, >>> * lease. Release one here, and the second below. >>> */ >>> cfid->has_lease = false; >>> - kref_put(&cfid->refcount, smb2_close_cached_fid); >>> + close_cached_dir(cfid); >>> } >>> spin_unlock(&cfids->cfid_list_lock); >>> >>> - kref_put(&cfid->refcount, smb2_close_cached_fid); >>> + close_cached_dir(cfid); >>> } else { >>> *ret_cfid = cfid; >>> atomic_inc(&tcon->num_remote_opens); >>> @@ -438,12 +438,14 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon, >>> >>> static void >>> smb2_close_cached_fid(struct kref *ref) >>> +__releases(&cfid->cfids->cfid_list_lock) >>> { >>> struct cached_fid *cfid = container_of(ref, struct cached_fid, >>> refcount); >>> int rc; >>> >>> - spin_lock(&cfid->cfids->cfid_list_lock); >>> + lockdep_assert_held(&cfid->cfids->cfid_list_lock); >>> + >>> if (cfid->on_list) { >>> list_del(&cfid->entry); >>> cfid->on_list = false; >>> @@ -478,7 +480,7 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon, >>> spin_lock(&cfid->cfids->cfid_list_lock); >>> if (cfid->has_lease) { >>> cfid->has_lease = false; >>> - kref_put(&cfid->refcount, smb2_close_cached_fid); >>> + close_cached_dir(cfid); >>> } >>> spin_unlock(&cfid->cfids->cfid_list_lock); >>> close_cached_dir(cfid); >>> @@ -487,7 +489,7 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon, >>> >>> void close_cached_dir(struct cached_fid *cfid) >>> { >>> - kref_put(&cfid->refcount, smb2_close_cached_fid); >>> + kref_put_lock(&cfid->refcount, smb2_close_cached_fid, &cfid->cfids->cfid_list_lock); >>> } >>> >>> /* >>> @@ -596,7 +598,7 @@ cached_dir_offload_close(struct work_struct *work) >>> >>> WARN_ON(cfid->on_list); >>> >>> - kref_put(&cfid->refcount, smb2_close_cached_fid); >>> + close_cached_dir(cfid); >>> cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cached_close); >>> } >>> >>> @@ -762,7 +764,7 @@ static void cfids_laundromat_worker(struct work_struct *work) >>> * Drop the ref-count from above, either the lease-ref (if there >>> * was one) or the extra one acquired. >>> */ >>> - kref_put(&cfid->refcount, smb2_close_cached_fid); >>> + close_cached_dir(cfid); >>> } >>> queue_delayed_work(cfid_put_wq, &cfids->laundromat_work, >>> dir_cache_timeout * HZ); >>> -- >>> 2.50.1 >>> >>> >> >> > -- Henrique SUSE Labs ^ permalink raw reply [flat|nested] 8+ messages in thread
[parent not found: <CAH2r5mvkefptGUvjarOmOW=hQA7_ZRFLwOk_jptOSAyzuMUGuw@mail.gmail.com>]
* Re: [PATCH v4] smb: client: fix potential UAF in smb2_close_cached_fid() [not found] ` <CAH2r5mvkefptGUvjarOmOW=hQA7_ZRFLwOk_jptOSAyzuMUGuw@mail.gmail.com> @ 2025-11-05 21:16 ` Henrique Carvalho 0 siblings, 0 replies; 8+ messages in thread From: Henrique Carvalho @ 2025-11-05 21:16 UTC (permalink / raw) To: Steve French Cc: Paulo Alcantara, ronnie sahlberg, Shyam Prasad N, Tom Talpey, Bharath S M, Enzo Matsumiya, Jay Shin, linux-cifs Hi Steve, On 11/5/25 1:20 PM, Steve French wrote: > I can run it through again if you semd mean updated patch > > Thanks, > > Steve I don't think there's anything to update here. Adding a separate __acquires at call sites would be misleading. Not only the call site is correctly annotated -- refcount_dec_and_lock() is annotated with __cond_acquires(lock). Sparse reports no warnings, so there's no missing annotation to fix. Likewise, documenting that callers must hold the lock before calling kref_put_lock() would be incorrect -- they must not. No one should call kref_put_lock() directly. Likewise, no one should call smb2_close_cached_fid() directly. lockdep_assert_held() is there simply to ensure the invariant at runtime and as a hint to other developers. If you think it would help, I can add a short comment in smb2_close_cached_fid() noting that the lock is held via kref_put_lock(). Let me know if you prefer that comment and I'll send a v5. > > On Wed, Nov 5, 2025, 10:18 AM Henrique Carvalho <henrique.carvalho@suse.com> > wrote: > >> >> >> On 11/5/25 10:30 AM, Henrique Carvalho wrote: >>> Hi Steve, >>> >>> On 11/4/25 7:42 PM, Steve French wrote: >>>> One minor comment that AI had on this was on the change to >>>> fs/smb/client/cached_dir.c (line 447). See below. >>>> >>>> -spin_lock(&cfid->cfids->cfid_list_lock); >>>> +lockdep_assert_held(&cfid->cfids->cfid_list_lock); >>>> >>>> >>>> The lockdep_assert_held validates that the lock is held on entry, but >>>> the function signature __releases(&cfid->cfids->cfid_list_lock) >>>> indicates the lock is released inside. Consider adding a corresponding >>>> __acquires annotation to the call sites that call this function via >>>> kref_put_lock, or document why callers must hold the lock before >>>> calling kref_put_lock. >>> >>> But we don't actually acquire the lock every time in close_cached_dir. >>> Only when (a) refcount is 1; and (b) refcount did not increase after we >>> acquired the lock. So where should __acquires annotation go? >>> >>> One option would be adding documentation to smb2_close_cached_fid, >>> though since it's a release function that shouldn't be called elsewhere, >>> I'm not sure that adds much value. Do you have thoughts? >>> >> >> I think it's worth adding that kref_put_lock uses refcount_dec_and_lock, >> which is defined as >> >> extern __must_check bool refcount_dec_and_lock(refcount_t *r, spinlock_t >> *lock) __cond_acquires(lock); >> >> __cond_acquires() was introduced in commit 4a557a5d1a614 ("sparse: >> introduce conditional lock acquire function attribute"). >> >> So in a sense, if I understand it correctly, the "acquires" is already >> there to make the sparse __releases annotation work. This should be >> enough to satisfy the comment the AI raised. >> >>>> >>>> On Mon, Nov 3, 2025 at 5:00 PM Henrique Carvalho >>>> <henrique.carvalho@suse.com> wrote: >>>>> >>>>> find_or_create_cached_dir() could grab a new reference after kref_put() >>>>> had seen the refcount drop to zero but before cfid_list_lock is >> acquired >>>>> in smb2_close_cached_fid(), leading to use-after-free. >>>>> >>>>> Switch to kref_put_lock() so cfid_release() is called with >>>>> cfid_list_lock held, closing that gap. >>>>> >>>>> Fixes: ebe98f1447bb ("cifs: enable caching of directories for which a >> lease is held") >>>>> Cc: stable@vger.kernel.org >>>>> Reported-by: Jay Shin <jaeshin@redhat.com> >>>>> Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org> >>>>> Signed-off-by: Henrique Carvalho <henrique.carvalho@suse.com> >>>>> --- >>>>> V3 -> V4: rebase, added Reviewed-by and Reported-by, add >>>>> lockdep_assert_held in smb2_close_cached_fid, change commit subject >> (was >>>>> "smb: client: fix race in smb2_close_cached_fid()") to clearly state >> the >>>>> bug class (UAF) >>>>> V2 -> V3: rebase, remove unneeded comments, modify commit subject >>>>> V1 -> V2: kept the original function names and added __releases >> annotation >>>>> to silence sparse warning >>>>> --- >>>>> fs/smb/client/cached_dir.c | 16 +++++++++------- >>>>> 1 file changed, 9 insertions(+), 7 deletions(-) >>>>> >>>>> diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c >>>>> index b8ac7b7faf61..018055fd2cdb 100644 >>>>> --- a/fs/smb/client/cached_dir.c >>>>> +++ b/fs/smb/client/cached_dir.c >>>>> @@ -388,11 +388,11 @@ int open_cached_dir(unsigned int xid, struct >> cifs_tcon *tcon, >>>>> * lease. Release one here, and the second >> below. >>>>> */ >>>>> cfid->has_lease = false; >>>>> - kref_put(&cfid->refcount, >> smb2_close_cached_fid); >>>>> + close_cached_dir(cfid); >>>>> } >>>>> spin_unlock(&cfids->cfid_list_lock); >>>>> >>>>> - kref_put(&cfid->refcount, smb2_close_cached_fid); >>>>> + close_cached_dir(cfid); >>>>> } else { >>>>> *ret_cfid = cfid; >>>>> atomic_inc(&tcon->num_remote_opens); >>>>> @@ -438,12 +438,14 @@ int open_cached_dir_by_dentry(struct cifs_tcon >> *tcon, >>>>> >>>>> static void >>>>> smb2_close_cached_fid(struct kref *ref) >>>>> +__releases(&cfid->cfids->cfid_list_lock) >>>>> { >>>>> struct cached_fid *cfid = container_of(ref, struct cached_fid, >>>>> refcount); >>>>> int rc; >>>>> >>>>> - spin_lock(&cfid->cfids->cfid_list_lock); >>>>> + lockdep_assert_held(&cfid->cfids->cfid_list_lock); >>>>> + >>>>> if (cfid->on_list) { >>>>> list_del(&cfid->entry); >>>>> cfid->on_list = false; >>>>> @@ -478,7 +480,7 @@ void drop_cached_dir_by_name(const unsigned int >> xid, struct cifs_tcon *tcon, >>>>> spin_lock(&cfid->cfids->cfid_list_lock); >>>>> if (cfid->has_lease) { >>>>> cfid->has_lease = false; >>>>> - kref_put(&cfid->refcount, smb2_close_cached_fid); >>>>> + close_cached_dir(cfid); >>>>> } >>>>> spin_unlock(&cfid->cfids->cfid_list_lock); >>>>> close_cached_dir(cfid); >>>>> @@ -487,7 +489,7 @@ void drop_cached_dir_by_name(const unsigned int >> xid, struct cifs_tcon *tcon, >>>>> >>>>> void close_cached_dir(struct cached_fid *cfid) >>>>> { >>>>> - kref_put(&cfid->refcount, smb2_close_cached_fid); >>>>> + kref_put_lock(&cfid->refcount, smb2_close_cached_fid, >> &cfid->cfids->cfid_list_lock); >>>>> } >>>>> >>>>> /* >>>>> @@ -596,7 +598,7 @@ cached_dir_offload_close(struct work_struct *work) >>>>> >>>>> WARN_ON(cfid->on_list); >>>>> >>>>> - kref_put(&cfid->refcount, smb2_close_cached_fid); >>>>> + close_cached_dir(cfid); >>>>> cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cached_close); >>>>> } >>>>> >>>>> @@ -762,7 +764,7 @@ static void cfids_laundromat_worker(struct >> work_struct *work) >>>>> * Drop the ref-count from above, either the >> lease-ref (if there >>>>> * was one) or the extra one acquired. >>>>> */ >>>>> - kref_put(&cfid->refcount, >> smb2_close_cached_fid); >>>>> + close_cached_dir(cfid); >>>>> } >>>>> queue_delayed_work(cfid_put_wq, &cfids->laundromat_work, >>>>> dir_cache_timeout * HZ); >>>>> -- >>>>> 2.50.1 >>>>> >>>>> >>>> >>>> >>> >> >> -- >> Henrique >> SUSE Labs >> > -- Henrique SUSE Labs ^ permalink raw reply [flat|nested] 8+ messages in thread
[parent not found: <baf7ee5f-aa34-41f3-a00c-8e3b7686d566@suse.com>]
* Re: [PATCH v4] smb: client: fix potential UAF in smb2_close_cached_fid() [not found] <baf7ee5f-aa34-41f3-a00c-8e3b7686d566@suse.com> @ 2025-11-08 13:44 ` Henrique Carvalho 2025-11-08 18:25 ` Paulo Alcantara 0 siblings, 1 reply; 8+ messages in thread From: Henrique Carvalho @ 2025-11-08 13:44 UTC (permalink / raw) To: Steve French, Paulo Alcantara Cc: ronnie sahlberg, Shyam Prasad N, Tom Talpey, Bharath S M, Enzo Matsumiya, Jay Shin, linux-cifs Resending to include linux-cifs@vger.kernel.org. Since there were concerns about a potential deadlock in this path (from Enzo and copilot), I reworked the patch a bit. In theory the existing code is fine as long as the refcount invariant holds, but we've already been proven wrong in this area a few times. So I'd rather make the deadlock impossible and WARN if the invariant is violated instead of relying on it being perfect. @Paulo, what do you think about this? diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c index 018055fd2cdb..50074c9f125f 100644 --- a/fs/smb/client/cached_dir.c +++ b/fs/smb/client/cached_dir.c @@ -16,6 +16,7 @@ static struct cached_fid *init_cached_dir(const char *path); static void free_cached_dir(struct cached_fid *cfid); static void smb2_close_cached_fid(struct kref *ref); static void cfids_laundromat_worker(struct work_struct *work); +static void close_cached_dir_locked(struct cached_fid *cfid); struct cached_dir_dentry { struct list_head entry; @@ -388,7 +389,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon, * lease. Release one here, and the second below. */ cfid->has_lease = false; - close_cached_dir(cfid); + close_cached_dir_locked(cfid); } spin_unlock(&cfids->cfid_list_lock); @@ -480,18 +481,52 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon, spin_lock(&cfid->cfids->cfid_list_lock); if (cfid->has_lease) { cfid->has_lease = false; - close_cached_dir(cfid); + close_cached_dir_locked(cfid); } spin_unlock(&cfid->cfids->cfid_list_lock); close_cached_dir(cfid); } - +/** + * close_cached_dir - drop a reference of a cached dir + * + * The release function will be called with cfid_list_lock held to remove the + * cached dirs from the list before any other thread can take another @cfid + * ref. Must not be called with cfid_list_lock held; use + * close_cached_dir_locked() called instead. + * + * @cfid: cached dir + */ void close_cached_dir(struct cached_fid *cfid) { + lockdep_assert_not_held(&cfid->cfids->cfid_list_lock); kref_put_lock(&cfid->refcount, smb2_close_cached_fid, &cfid->cfids->cfid_list_lock); } +/** + * close_cached_dir_locked - put a reference of a cached dir with + * cfid_list_lock held + * + * Calling close_cached_dir() with cfid_list_lock held has the potential effect + * of causing a deadlock if the invariant of refcount >= 2 is false. + * + * This function is used in paths that hold cfid_list_lock and expect at least + * two references. If that invariant is violated, WARNs and returns without + * dropping a reference; the final put must still go through + * close_cached_dir(). + * + * @cfid: cached dir struct + */ +static void close_cached_dir_locked(struct cached_fid *cfid) +{ + lockdep_assert_held(&cfid->cfids->cfid_list_lock); + + if (WARN_ON(kref_read(&cfid->refcount) < 2)) + return; + + kref_put(&cfid->refcount, smb2_close_cached_fid); +} + /* * Called from cifs_kill_sb when we unmount a share */ -- Henrique SUSE Labs ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v4] smb: client: fix potential UAF in smb2_close_cached_fid() 2025-11-08 13:44 ` Henrique Carvalho @ 2025-11-08 18:25 ` Paulo Alcantara 0 siblings, 0 replies; 8+ messages in thread From: Paulo Alcantara @ 2025-11-08 18:25 UTC (permalink / raw) To: Henrique Carvalho, Steve French Cc: ronnie sahlberg, Shyam Prasad N, Tom Talpey, Bharath S M, Enzo Matsumiya, Jay Shin, linux-cifs Henrique Carvalho <henrique.carvalho@suse.com> writes: > Resending to include linux-cifs@vger.kernel.org. > > Since there were concerns about a potential deadlock in this path (from Enzo > and copilot), I reworked the patch a bit. > > In theory the existing code is fine as long as the refcount invariant holds, > but we've already been proven wrong in this area a few times. So I'd rather > make the deadlock impossible and WARN if the invariant is violated > instead of > relying on it being perfect. > > @Paulo, what do you think about this? Sounds good. ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2025-11-08 18:26 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-03 22:52 [PATCH v4] smb: client: fix potential UAF in smb2_close_cached_fid() Henrique Carvalho
2025-11-04 14:58 ` Steve French
2025-11-04 22:42 ` Steve French
2025-11-05 13:30 ` Henrique Carvalho
2025-11-05 16:16 ` Henrique Carvalho
[not found] ` <CAH2r5mvkefptGUvjarOmOW=hQA7_ZRFLwOk_jptOSAyzuMUGuw@mail.gmail.com>
2025-11-05 21:16 ` Henrique Carvalho
[not found] <baf7ee5f-aa34-41f3-a00c-8e3b7686d566@suse.com>
2025-11-08 13:44 ` Henrique Carvalho
2025-11-08 18:25 ` Paulo Alcantara
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox