<feed xmlns='http://www.w3.org/2005/Atom'>
<title>kernel/git/stable/linux.git/fs/ceph, branch master</title>
<subtitle>Linux kernel stable tree</subtitle>
<id>https://git.rulkc.org/pub/scm/linux/kernel/git/stable/linux.git/atom?h=master</id>
<link rel='self' href='https://git.rulkc.org/pub/scm/linux/kernel/git/stable/linux.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.rulkc.org/pub/scm/linux/kernel/git/stable/linux.git/'/>
<updated>2026-08-28T18:51:05+00:00</updated>
<entry>
<title>Merge tag 'ceph-for-7.3-rc1' of https://github.com/ceph/ceph-client</title>
<updated>2026-08-28T18:51:05+00:00</updated>
<author>
<name>Linus Torvalds</name>
<email>torvalds@linux-foundation.org</email>
</author>
<published>2026-08-28T18:51:05+00:00</published>
<link rel='alternate' type='text/html' href='https://git.rulkc.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=548e7bcd0c5460ddcbca9600cea603ebeebf4da7'/>
<id>urn:sha1:548e7bcd0c5460ddcbca9600cea603ebeebf4da7</id>
<content type='text'>
Pull ceph updates from Ilya Dryomov:
 "A wide variety of mostly CephFS fixes and cleanups, split between
  changes that address edge cases (Sam, Xiubo, Matthew), efficiency
  improvements (Max) and AI-assisted hardening (Michael, Jeremy).

  One thing that stands out is Alex's change to how CephFS behaves in
  NEARFULL scenarios: the long-standing "make all writes synchronous"
  behavior has become opt-in. It was always somewhat controversial and
  doesn't make much sense for modern deployments; the new default is to
  continue normal operation (i.e. buffer writes as MDS allows, etc). The
  behavior in case the cluster reaches any FULL state remains the same
  as before"

* tag 'ceph-for-7.3-rc1' of https://github.com/ceph/ceph-client: (32 commits)
  ceph: force a cap message when a deferred revoke can't be acked immediately
  libceph: reject buckets with mismatched CRUSH ids
  ceph: reject export_targets ranks &gt;= CEPH_MAX_MDS in mdsmap decode
  ceph: fix leaked inode reference on writeback abort at umount
  libceph: remove ceph_put_page_vector()
  libceph: validate banner payload length
  ceph: make nearfull sync writes opt-in
  ceph: do not repeat ceph_trim_dentries() if no progress possible
  ceph: drop mdsc-&gt;mutex before decoding the MDS reply
  ceph: fix UAF in check_new_map() on session freed during unlock
  ceph: fix UAF in __kick_flushing_caps() on cf entry freed during unlock
  ceph: pass inode pointer around instead of reloading it
  ceph: mark cap remove with RB_CLEAR_NODE() instead of setting ci=NULL
  ceph: add helper function ceph_cap_is_removed()
  ceph: make __ceph_remove_cap() static
  ceph: cap delegated inode count in ceph_parse_deleg_inos()
  ceph: bound num_export_targets array for mds info v2/v3
  ceph: bound MDSCapAuth path and fs_name decode in handle_session()
  ceph: bound xattr value length in __build_xattrs()
  ceph: bound copied dentry name length in NFS export get_name
  ...
</content>
</entry>
<entry>
<title>ceph: force a cap message when a deferred revoke can't be acked immediately</title>
<updated>2026-08-26T17:57:29+00:00</updated>
<author>
<name>Max Kellermann</name>
<email>max.kellermann@ionos.com</email>
</author>
<published>2026-08-18T18:40:05+00:00</published>
<link rel='alternate' type='text/html' href='https://git.rulkc.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=8fdf946445732c2bcd685abc8bd0e509d2ebc158'/>
<id>urn:sha1:8fdf946445732c2bcd685abc8bd0e509d2ebc158</id>
<content type='text'>
When the MDS revokes capabilities, handle_cap_grant() normally
guarantees a response by setting `CHECK_CAPS_FLUSH_FORCE` (see
commit 31634d7597d8 ("ceph: force sending a cap update msg back to MDS
for revoke op")), so ceph_check_caps() sends a cap message even if the
client would otherwise decide it has nothing to do.  That guarantee is
skipped whenever the revoke has to be deferred (via revoke_wait):
revoking Fb while dirty data is still buffered (writeback is queued
first) or revoking Fc while pages are cached (async invalidation is
queued first).

In those cases, the ack is left to the deferred completion
(ceph_put_wrbuffer_cap_refs() after writeback, or the invalidate
worker after invalidation); both of which call ceph_check_caps(ci,0)
i.e.  without `CHECK_CAPS_FLUSH_FORCE`.  Nothing gets sent under one
of the following conditions:

- the inode is retaining caps because the file was used recently
  (file_wanted != 0; retain |= CEPH_CAP_ANY)

- the revoked cap is still used because the page was re-cached (e.g. a
  file being re-read)

- the MDS has meanwhile re-granted, so `issued==implemented` and the
  client sees nothing being revoked

The client then never emits the cap message which the MDS is waiting
for.  The MDS blocks on the revoke indefinitely and logs, for minutes
or hours:

  client.NNN isn't responding to mclientcaps(revoke), ino 0x... pending
  pAsxLsXsxFsxcrwb issued pAsxLsXsxFsxcrwb, sent 964.899182 seconds ago

The client-side state at that point shows the full cap set still
issued, nothing in the revoking/flushing sets.  Thus nothing gets
sent.

This patch fixes it by remembering that a forced response is expected.
When a revoke is deferred, set `CEPH_I_FLUSH_FORCE` on the inode.
ceph_check_caps() replays it as `CHECK_CAPS_FLUSH_FORCE`, so whichever
path re-checks the inode next (the writeback/invalidate completion,
the delayed worker, or any other caller) is guaranteed to send a cap
message to the MDS.  __prep_cap() clears the flag once a message is
actually built.

This is the deferred-path counterpart of the existing
`CHECK_CAPS_FLUSH_FORCE` handling; a normal (non-deferred) revoke
still forces the response inline as before.

Cc: stable@vger.kernel.org
Fixes: 31634d7597d8 ("ceph: force sending a cap update msg back to MDS for revoke op")
Fixes: 257e6172ab36 ("ceph: don't let check_caps skip sending responses for revoke msgs")
Signed-off-by: Max Kellermann &lt;max.kellermann@ionos.com&gt;
Reviewed-by: Alex Markuze &lt;amarkuze@redhat.com&gt;
Signed-off-by: Ilya Dryomov &lt;idryomov@gmail.com&gt;
</content>
</entry>
<entry>
<title>ceph: reject export_targets ranks &gt;= CEPH_MAX_MDS in mdsmap decode</title>
<updated>2026-08-26T17:57:29+00:00</updated>
<author>
<name>Jérémy Jean</name>
<email>Jeremy.Jean@oss.cyber.gouv.fr</email>
</author>
<published>2026-08-13T12:00:00+00:00</published>
<link rel='alternate' type='text/html' href='https://git.rulkc.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=aedc9053d909508a5f56c3f49f885fc030df4730'/>
<id>urn:sha1:aedc9053d909508a5f56c3f49f885fc030df4730</id>
<content type='text'>
MDSMap export_targets entries are monitor controlled. check_new_map()
uses each entry as a bit number in a fixed stack bitmap, so a rank
outside the protocol namespace can make set_bit() write past the end of
the array.

Reject ranks outside CEPH_MAX_MDS while decoding the map. Do not
validate against possible_max_rank here because maps may legitimately
reference ranks beyond a temporarily reduced max_mds.

Cc: stable@vger.kernel.org
Fixes: d517b3983dd3 ("ceph: reconnect to the export targets on new mdsmaps")
Signed-off-by: Jérémy Jean &lt;Jeremy.Jean@oss.cyber.gouv.fr&gt;
Reviewed-by: Alex Markuze &lt;amarkuze@redhat.com&gt;
Signed-off-by: Alex Markuze &lt;amarkuze@redhat.com&gt;
Signed-off-by: Ilya Dryomov &lt;idryomov@gmail.com&gt;
</content>
</entry>
<entry>
<title>ceph: fix leaked inode reference on writeback abort at umount</title>
<updated>2026-08-26T17:57:28+00:00</updated>
<author>
<name>Matthew Brown</name>
<email>matthew@bargrove.com</email>
</author>
<published>2026-08-12T17:13:21+00:00</published>
<link rel='alternate' type='text/html' href='https://git.rulkc.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=c25aee9c630fb86f98d79eccb75765067079b972'/>
<id>urn:sha1:c25aee9c630fb86f98d79eccb75765067079b972</id>
<content type='text'>
ceph_dirty_folio() takes a wrbuffer claim on each newly dirtied folio: it
bumps i_wrbuffer_ref (taking an ihold() on the 0-&gt;1 transition) and
attaches the snap_context to folio-&gt;private.  That claim is released only
by ceph_put_wrbuffer_cap_refs(), which for a submitted write runs from
writepages_finish().

In ceph_submit_write(), if ceph_inc_osd_stopping_blocker() fails -- which
happens during umount -- the request is aborted before submission: the
already-collected folios are only redirtied and unlocked, so
writepages_finish() never runs and the claim is leaked.
redirty_page_for_writepage() -&gt; folio_redirty_for_writepage() -&gt;
filemap_dirty_folio() sets PG_dirty directly and does not go through
-&gt;dirty_folio, so ceph_dirty_folio() is not re-entered to rebalance it.
Because every subsequent writeback also fails the osd_stopping_blocker,
i_wrbuffer_ref never returns to 0, the ihold() is never dropped, and the
inode cannot be evicted:

  VFS: Busy inodes after unmount of ceph
  kernel BUG at fs/super.c:650!

Release the orphaned claim in the abort path before redirtying, via
ceph_undo_wrbuffer_claim(): detach the snap_context, drop the wrbuffer
reference (letting i_wrbuffer_ref reach 0 and iput() the inode), and drop
the snap_context reference -- i.e. do what writepages_finish() would have
done for these never-submitted folios.

Only the locked_pages entries are undone; folios still in the fbatch were
never dirty-cleared by this call (folio_clear_dirty_for_io() is the
ownership-transfer point, and a successful move NULLs the fbatch slot), so
they hold no claim this call owns.

Cc: stable@vger.kernel.org
Fixes: fd7449d937e7 ("ceph: fix generic/421 test failure")
Signed-off-by: Matthew Brown &lt;matthew@bargrove.com&gt;
Reviewed-by: Xiubo Li &lt;xiubo.li@clyso.com&gt;
Signed-off-by: Ilya Dryomov &lt;idryomov@gmail.com&gt;
</content>
</entry>
<entry>
<title>libceph: remove ceph_put_page_vector()</title>
<updated>2026-08-26T17:57:28+00:00</updated>
<author>
<name>Tal Zussman</name>
<email>tz2294@columbia.edu</email>
</author>
<published>2026-08-17T19:29:17+00:00</published>
<link rel='alternate' type='text/html' href='https://git.rulkc.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=2a2f98e17e1d322a94027daf0c6df88af2f9af50'/>
<id>urn:sha1:2a2f98e17e1d322a94027daf0c6df88af2f9af50</id>
<content type='text'>
ceph_put_page_vector() was paired with ceph_get_direct_page_vector(),
which was removed in commit 97a385e55829 ("libceph: remove
ceph_get_direct_page_vector()"). Its only remaining caller,
finish_netfs_read(), uses it to put a page vector allocated with
iov_iter_get_pages_alloc2(), which is confusing. Open-code the
put_page() loop and kvfree() there instead.

The caller passed dirty = false, so this also removes the dead dirty
branch and with it a call to the deprecated set_page_dirty_lock().

Signed-off-by: Tal Zussman &lt;tz2294@columbia.edu&gt;
Reviewed-by: Ilya Dryomov &lt;idryomov@gmail.com&gt;
Signed-off-by: Ilya Dryomov &lt;idryomov@gmail.com&gt;
</content>
</entry>
<entry>
<title>ceph: make nearfull sync writes opt-in</title>
<updated>2026-08-26T17:57:28+00:00</updated>
<author>
<name>Alex Markuze</name>
<email>amarkuze@redhat.com</email>
</author>
<published>2026-07-06T13:11:27+00:00</published>
<link rel='alternate' type='text/html' href='https://git.rulkc.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=5f074d7f2938d7461facbbd073b9394b1496e73e'/>
<id>urn:sha1:5f074d7f2938d7461facbbd073b9394b1496e73e</id>
<content type='text'>
The kernel CephFS client has historically treated a cluster or pool
NEARFULL condition as a request to force successful writes through
generic_write_sync().  That effectively turns otherwise buffered writes
into synchronous writes and can cause a severe throughput drop as soon
as a single OSD or the file data pool crosses the nearfull threshold.

On modern large clusters, NEARFULL is primarily an operator health
signal rather than an immediate client-side capacity failure.  Operators
can still have substantial usable capacity while a cluster is
rebalancing, splitting PGs, or expanding onto new devices.  RBD, RGW and
the userspace CephFS client do not impose this extra client-side
sync-write throttle, so the kernel client behavior is surprising and
operationally painful.

Change the default behavior so NEARFULL no longer changes normal
write-sync semantics.  FULL and pool FULL still fail with -ENOSPC, and
explicitly synchronous writes continue to be synced by
generic_write_sync().

Add a nearfull_sync mount option for deployments that want the legacy
backpressure behavior.  When this option is set, successful writes are
promoted to IOCB_DSYNC if the cluster or file data pool is marked
NEARFULL, preserving the old behavior for conservative deployments.

Link: https://tracker.ceph.com/issues/74849
Signed-off-by: Alex Markuze &lt;amarkuze@redhat.com&gt;
Reviewed-by: Xiubo Li &lt;xiubo.li@clyso.com&gt;
Signed-off-by: Ilya Dryomov &lt;idryomov@gmail.com&gt;
</content>
</entry>
<entry>
<title>ceph: do not repeat ceph_trim_dentries() if no progress possible</title>
<updated>2026-08-26T17:57:28+00:00</updated>
<author>
<name>Max Kellermann</name>
<email>max.kellermann@ionos.com</email>
</author>
<published>2026-07-07T21:42:28+00:00</published>
<link rel='alternate' type='text/html' href='https://git.rulkc.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=e7d7aa7b730178278109c41fa1b17b06873065d5'/>
<id>urn:sha1:e7d7aa7b730178278109c41fa1b17b06873065d5</id>
<content type='text'>
ceph_cap_reclaim_work() re-queues itself for as long as
ceph_trim_dentries() returns -EAGAIN, which happens whenever a lease
walk exhausts its `nr_to_scan` budget.  This creates a busy loop that
consumes CPU without making any progress when there is nothing to
reclaim: with no cap pressure (`count==0`) and every scanned lease
still valid, each pass runs the full scan budget down to zero and
returns `-EAGAIN`, only to be queued again immediately.

The dir-lease walk made this worse.  When `expire_dir_lease` is
`false` (i.e. we have no intention of reclaiming dir leases),
__dir_lease_check() returned `TOUCH` for every valid lease.  `TOUCH`
moves the dentry to the tail of the list and resets `di-&gt;time` via
__dentry_dir_lease_touch(), so a walk over N valid leases pointlessly
rewrote the list, refreshed the timestamps (preventing them from ever
aging out) and always drained `nr_to_scan`, guaranteeing the `-EAGAIN`
requeue.

Fix this in three steps:

 - Return `KEEP` instead of `TOUCH` when `expire_dir_lease` is
   `false`.  If we are not going to reclaim the lease, leave it in
   place instead of churning the list and resetting its timestamp; the
   walk then terminates naturally (or via `STOP` at the first fresh
   lease).

 - Only return `-EAGAIN` from the first (dentry-lease) walk when something
   was actually freed.  A full batch that frees nothing means retrying
   the same list immediately is futile; fall through to the dir-lease
   walk instead.

 - After both walks, bail out with success (0) when nothing was freed
   and there is no cap pressure (`count==0`).  There is no reason to
   keep retrying when we are not over the cap limit and made no
   progress.

Under real cap pressure (`count&gt;0`) the reclaim path is unchanged and
still retries via `-EAGAIN`.

Without this patch, I saw 500 ceph_trim_dentries() calls per second on
our web servers.  This is very visible in `/proc/lock_stat` (5 minute
capture):

              class name    con-bounces    contentions   waittime-min   waittime-max waittime-total   waittime-avg    acq-bounces   acquisitions   holdtime-min   holdtime-max holdtime-total   holdtime-avg

 &amp;mdsc-&gt;dentry_list_lock:        126180         128218           0.04        8063.44    15986965.20         124.69        1573354        5296812           0.04        8291.28    74164526.48          14.00
 -----------------------
 &amp;mdsc-&gt;dentry_list_lock         111736          [&lt;000000007b11e319&gt;] __ceph_dentry_dir_lease_touch+0x7c/0xa8
 &amp;mdsc-&gt;dentry_list_lock           2631          [&lt;0000000050597999&gt;] __dentry_leases_walk+0x64/0x2c8
 &amp;mdsc-&gt;dentry_list_lock           3878          [&lt;00000000c0022f62&gt;] __ceph_dentry_lease_touch+0x5c/0xa8
 &amp;mdsc-&gt;dentry_list_lock           9973          [&lt;000000002f27cb6f&gt;] __dentry_lease_unlist+0x50/0xa0
 -----------------------
 &amp;mdsc-&gt;dentry_list_lock         123621          [&lt;0000000050597999&gt;] __dentry_leases_walk+0x64/0x2c8
 &amp;mdsc-&gt;dentry_list_lock           1822          [&lt;000000007b11e319&gt;] __ceph_dentry_dir_lease_touch+0x7c/0xa8
 &amp;mdsc-&gt;dentry_list_lock           2720          [&lt;000000002f27cb6f&gt;] __dentry_lease_unlist+0x50/0xa0
 &amp;mdsc-&gt;dentry_list_lock             55          [&lt;00000000c0022f62&gt;] __ceph_dentry_lease_touch+0x5c/0xa8

With this patch:

              class name    con-bounces    contentions   waittime-min   waittime-max waittime-total   waittime-avg    acq-bounces   acquisitions   holdtime-min   holdtime-max holdtime-total   holdtime-avg

 &amp;mdsc-&gt;dentry_list_lock:          1203           1215           0.16         408.88       33082.88          27.23        4320501        7357389           0.04         500.64     1961578.00           0.27
 -----------------------
 &amp;mdsc-&gt;dentry_list_lock           1029          [&lt;000000003c9aea8a&gt;] __ceph_dentry_dir_lease_touch+0x7c/0xa8
 &amp;mdsc-&gt;dentry_list_lock            169          [&lt;000000002038c577&gt;] __dentry_lease_unlist+0x50/0xa0
 &amp;mdsc-&gt;dentry_list_lock             16          [&lt;00000000c991106d&gt;] __ceph_dentry_lease_touch+0x5c/0xa8
 &amp;mdsc-&gt;dentry_list_lock              1          [&lt;00000000612fe15f&gt;] __dentry_leases_walk+0x64/0x2c8
 -----------------------
 &amp;mdsc-&gt;dentry_list_lock            158          [&lt;000000002038c577&gt;] __dentry_lease_unlist+0x50/0xa0
 &amp;mdsc-&gt;dentry_list_lock            858          [&lt;000000003c9aea8a&gt;] __ceph_dentry_dir_lease_touch+0x7c/0xa8
 &amp;mdsc-&gt;dentry_list_lock            182          [&lt;00000000612fe15f&gt;] __dentry_leases_walk+0x64/0x2c8
 &amp;mdsc-&gt;dentry_list_lock             17          [&lt;00000000c991106d&gt;] __ceph_dentry_lease_touch+0x5c/0xa8

__dentry_leases_walk() is almost gone.  The total wait time is reduced
by a factor of 483.  That will give some latency gains to
ceph_readdir().

Cc: stable@vger.kernel.org
Fixes: 37c4efc1ddf9 ("ceph: periodically trim stale dentries")
Signed-off-by: Max Kellermann &lt;max.kellermann@ionos.com&gt;
Reviewed-by: Alex Markuze &lt;amarkuze@redhat.com&gt;
Signed-off-by: Ilya Dryomov &lt;idryomov@gmail.com&gt;
</content>
</entry>
<entry>
<title>ceph: drop mdsc-&gt;mutex before decoding the MDS reply</title>
<updated>2026-08-26T17:57:28+00:00</updated>
<author>
<name>Max Kellermann</name>
<email>max.kellermann@ionos.com</email>
</author>
<published>2026-07-08T20:40:22+00:00</published>
<link rel='alternate' type='text/html' href='https://git.rulkc.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=1319b97dfe9eaa0e15132af95efe6513718f9123'/>
<id>urn:sha1:1319b97dfe9eaa0e15132af95efe6513718f9123</id>
<content type='text'>
handle_reply() held `mdsc-&gt;mutex` across parse_reply_info(),
i.e. across the full decode of the reply message.  For large replies
(a big readdir allocates and parses many dir_entries), this can take a
while and blocks ceph_mdsc_submit_request() calls meanwhile.

The decode does not need `mdsc-&gt;mutex`: parse_reply_info() mostly
fills the request's `r_reply_info`.  Create replies may also add
delegated inode numbers to the session xarray, but that xarray is
protected by its own lock and is not serialized by `mdsc-&gt;mutex`
today.  By the time we reach parse_reply_info(), all
`mdsc-&gt;mutex`-protected state has already been updated under the lock
(the request has either been unregistered (safe reply) or added to the
session's unsafe list (unsafe reply)) and the request is pinned by the
reference taken in lookup_get_request().

Drop `mdsc-&gt;mutex` before calling parse_reply_info() so reply decoding
no longer blocks request submission.  This only widens the existing
unlocked window that already covers the heavier ceph_fill_trace() /
ceph_readdir_prepopulate() processing, so no new races are introduced.

Signed-off-by: Max Kellermann &lt;max.kellermann@ionos.com&gt;
Reviewed-by: Xiubo Li &lt;xiubo.li@clyso.com&gt;
Signed-off-by: Ilya Dryomov &lt;idryomov@gmail.com&gt;
</content>
</entry>
<entry>
<title>ceph: fix UAF in check_new_map() on session freed during unlock</title>
<updated>2026-08-26T17:57:28+00:00</updated>
<author>
<name>Xiubo Li</name>
<email>xiubo.li@clyso.com</email>
</author>
<published>2026-07-14T08:13:44+00:00</published>
<link rel='alternate' type='text/html' href='https://git.rulkc.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=ee611a7509554c4ca1f54f6aefe592fb1df7ea70'/>
<id>urn:sha1:ee611a7509554c4ca1f54f6aefe592fb1df7ea70</id>
<content type='text'>
check_new_map() iterates mdsc-&gt;sessions[] and for each active session
drops mdsc-&gt;mutex to perform per-session operations.  The forced-close
path (rank removed from map) correctly takes a reference on s via
ceph_get_mds_session() before releasing mdsc-&gt;mutex, but three other
paths do not:

  Path A (address changed):  mutex_unlock → mutex_lock(&amp;s-&gt;s_mutex)
  Path B (reconnect):        mutex_unlock → send_mds_reconnect(mdsc, s)
  Path C (active transition): mutex_unlock → mutex_lock(&amp;s-&gt;s_mutex)

Without the extra reference, another thread can acquire mdsc-&gt;mutex
during the unlock window, call __unregister_session() which drops the
last reference on s, and free it.  The original thread then accesses
freed memory via s-&gt;s_mutex.

Fix by adding ceph_get_mds_session(s) before each mutex_unlock and
ceph_put_mds_session(s) after the corresponding mutex_lock, matching
the pattern already used in the forced-close path.

Race timeline (Path A):

  Thread A (check_new_map)             Thread B (another map update
    holds mdsc-&gt;mutex                      or session teardown)
  --------------------------           --------------------------
  s = mdsc-&gt;sessions[i]
  (refcount == 1, held only by
   sessions[] array)

  mutex_unlock(&amp;mdsc-&gt;mutex)
                               ---&gt;    acquires mdsc-&gt;mutex
                                       __unregister_session(mdsc, s)
                                         sessions[i] = NULL
                                         ceph_put_mds_session(s)
                                           refcount: 1 -&gt; 0
                                           kfree(s)  &lt;--- freed!

  mutex_lock(&amp;s-&gt;s_mutex)
  UAF on freed s-&gt;s_mutex

Cc: stable@vger.kernel.org
Signed-off-by: Xiubo Li &lt;xiubo.li@clyso.com&gt;
Reviewed-by: Viacheslav Dubeyko &lt;slava@dubeyko.com&gt;
Signed-off-by: Ilya Dryomov &lt;idryomov@gmail.com&gt;
</content>
</entry>
<entry>
<title>ceph: fix UAF in __kick_flushing_caps() on cf entry freed during unlock</title>
<updated>2026-08-26T17:57:28+00:00</updated>
<author>
<name>Xiubo Li</name>
<email>xiubo.li@clyso.com</email>
</author>
<published>2026-07-14T08:13:43+00:00</published>
<link rel='alternate' type='text/html' href='https://git.rulkc.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=7af4c4f01305b0935adf6d4301b1ec407025485d'/>
<id>urn:sha1:7af4c4f01305b0935adf6d4301b1ec407025485d</id>
<content type='text'>
list_for_each_entry() iterates ci-&gt;i_cap_flush_list but drops
i_ceph_lock to send cap messages.  During the unlock window,
handle_cap_flush_ack() can acquire i_ceph_lock, detach cf entries
with tid &lt;= flush_tid from the list, release i_ceph_lock, and free
them via ceph_free_cap_flush() outside any lock.  When the original
thread reacquires i_ceph_lock and the for-loop macro advances via
cf = list_next_entry(cf, i_list), it dereferences cf-&gt;i_list.next
on freed memory.

The race timeline:

  __kick_flushing_caps()              handle_cap_flush_ack()
  -----------------------             -----------------------
  holds i_ceph_lock        &lt;---
  iterates to cf (tid=10)
  prepares FLUSH message
  drops i_ceph_lock        &lt;---
  __send_cap() ── FLUSH(tid=10)
	                              MDS sends FLUSH_ACK(tid=10)
                           ---&gt;       acquires i_ceph_lock
                                      cf-&gt;tid(10) &lt;= flush_tid(10),
                                      detaches cf from i_cap_flush_list
                                      drops i_ceph_lock
                                      ceph_free_cap_flush(cf) &lt;- frees it!
  acquires i_ceph_lock     &lt;---
  for-loop advances:
    cf = list_next_entry(cf, i_list)
      -- UAF on freed cf-&gt;i_list.next

The cf was just sent by __kick_flushing_caps itself via __send_cap().
The MDS may respond with FLUSH_ACK quickly enough that
handle_cap_flush_ack() frees cf before __kick_flushing_caps can
finish the iteration.

Fix by converting to a manual while loop: save the next pointer
under i_ceph_lock before dropping it, then use the saved pointer
after reacquiring, so the potentially-freed cf is never accessed again.

Cc: stable@vger.kernel.org
Signed-off-by: Xiubo Li &lt;xiubo.li@clyso.com&gt;
Reviewed-by: Viacheslav Dubeyko &lt;slava@dubeyko.com&gt;
Signed-off-by: Ilya Dryomov &lt;idryomov@gmail.com&gt;
</content>
</entry>
</feed>
