Re: [PATCH 5/5] ceph: narrow mdsc->mutex scope in replay_unsafe_requests

Xiubo Li <[email protected]> Wed, 15 Jul 2026 11:14:02 +0800
Newsgroups org.kernel.vger.ceph-devel,org.kernel.vger.linux-kernel
Message-ID <CAOJNxR+OcsNXJAYGALa4W0jX=joLkt-oge14kWvdHfjS+UCkVg@mail.gmail.com>
On Wed, 15 Jul 2026 at 03:07, Viacheslav Dubeyko <[email protected]> wrote:
>
> On Mon, 2026-07-13 at 17:46 +0800, Xiubo Li via B4 Relay wrote:
> > From: Xiubo Li <[email protected]>
[......]
> > @@ -4673,12 +4673,25 @@ static void replay_unsafe_requests(struct
> > ceph_mds_client *mdsc,
> >  {
> >       struct ceph_mds_request *req, *nreq;
> >       unsigned long idx;
> > +     LIST_HEAD(replay_list);
> >
> >       doutc(mdsc->fsc->client, "mds%d\n", session->s_mds);
> >
> > +     /* collect unsafe requests under the mutex */
> >       mutex_lock(&mdsc->mutex);
> > -     list_for_each_entry_safe(req, nreq, &session->s_unsafe,
> > r_unsafe_item)
> > +     list_for_each_entry_safe(req, nreq, &session->s_unsafe,
> > +                              r_unsafe_item) {
> > +             ceph_mdsc_get_request(req);
> > +             list_move(&req->r_unsafe_item, &replay_list);
> > +     }
> > +     mutex_unlock(&mdsc->mutex);
>
> This loop is done right.
>
> > +
> > +     /* replay unsafe requests — __send_request is lockless */
> > +     list_for_each_entry_safe(req, nreq, &replay_list,
> > r_unsafe_item) {
> >               __send_request(session, req, true);
> > +             list_del_init(&req->r_unsafe_item);
> > +             ceph_mdsc_put_request(req);
> > +     }
>
> However, this one has issue, if I am right. No ceph_mdsc_get_request()
> anywhere in it. It has always relied entirely on the caller holding
> mdsc->mutex for the duration of the loop.
>
> While this loop is inside __send_request() or
> ceph_mdsc_release_dir_caps_async() for one req, nothing stops another
> thread from acquiring mdsc->mutex and completing/timing-out/aborting
> that same request through the now-fully-unlocked
> __do_request()/wait_requests() path, dropping the last reference and
> freeing it — at which point the next loop iteration's xa_find() touches
> freed memory. Am I right here?
>

Good catch. The old-request loop in phase 3 iterates raw pointers from
xa_for_each() / rb_next() without holding a reference. If another
thread completes the request while __send_request() runs without
mdsc->mutex, the request could be freed, leading to UAF.

I will fix it.

Thanks
Xiubo LI