Linux FSCRYPT development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Alex Markuze" <amarkuze@redhat.com>
Cc: idryomov@gmail.com, xiubo.li@clyso.com, slava@dubeyko.com,
	viro@zeniv.linux.org.uk, brauner@kernel.org, jack@suse.cz,
	linux-fscrypt@vger.kernel.org, ebiggers@kernel.org,
	tytso@mit.edu, jaegeuk@kernel.org
Subject: Re: [PATCH v6 4/5] ceph: record subvolume quarantine from MDS cap updates
Date: Sun, 25 Oct 2026 19:12:16 +0000	[thread overview]
Message-ID: <sashiko-outbox-184356@kernel.org> (raw)
In-Reply-To: <1b6167c472fe05e2cd650bde2ffc9421892ad4cc.20261025T182649Z.ca9ae7a18449.git.amarkuze@redhat.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] ceph: epoch self-invalidation in ceph_quarantine_reply_can_clear()
- [High] ceph: __ceph_remove_cap() resets quarantine mark during cap migration
--

commit 8a2e69935d967838e52792bb963b124cfe229b88
Author: Alex Markuze <amarkuze@redhat.com>

ceph: record subvolume quarantine from MDS cap updates

This commit decodes the signed oserrno from MClientCaps v14 messages to
record a sticky subvolume quarantine mark and reporting MDS rank on each
inode, along with epoch-based caching and RCU parent walks to resolve
inherited quarantine state.

> diff --git a/fs/ceph/caps.c b/fs/ceph/caps.c
> index b27bcdc24d29f..b07814d0038cd 100644
> --- a/fs/ceph/caps.c
> +++ b/fs/ceph/caps.c

[ ... ]

> @@ -1264,6 +1265,11 @@ static void __ceph_remove_cap(struct ceph_inode_info *ci, struct ceph_cap *cap,
>  
>  	mdsc = ceph_inode_to_fs_client(&ci->netfs.inode)->mdsc;
>  
> +	/* Losing the reporting cap requires fresh MDS revalidation. */
> +	if (READ_ONCE(ci->i_qtine_mds) == cap->mds &&
> +	    ceph_quarantine_reset(ci))
> +		wake_up_all(&ci->i_cap_wq);
> +
>  	/* remove from inode's cap rbtree, and clear auth cap */
>  	rb_erase(&cap->ci_node, &ci->i_caps);
>  	if (ci->i_auth_cap == cap)
> @@ -3729,6 +3735,9 @@ struct cap_extra_info {
>  	u8 *fscrypt_auth;
>  	u32 fscrypt_auth_len;
>  	u64 fscrypt_file_size;
> +	/* MClientCaps v14 oserrno; valid only when oserrno_valid is set */
> +	s32 oserrno;
> +	bool oserrno_valid;
>  };
>  
>  /*
> @@ -3757,6 +3766,46 @@ static int parse_inline_data(void **p, void *end,
>  	return -EIO;
>  }
>  
> +/*
> + * Record quarantine state carried by an MClientCaps oserrno.
> + * Caller holds i_ceph_lock. Returns true when waiters should be woken
> + * because quarantine changed or a pending reset was resolved.
> + *
> + * -EQUARANTINED refreshes the sticky flag and its reporting MDS rank.
> + * A current non-IMPORT grant above PIN with errno 0 clears that rank's mark.
> + * IMPORT may carry a default zero without assessing quarantine permission.
> + * Another rank needs a successful metadata request for the current epoch.
> + * A PIN-only update may zero qtine_errno while leaving the flag set.
> + */
> +static bool ceph_apply_quarantine(struct ceph_inode_info *ci, int mds,
> +				  int newcaps, int oserrno, bool is_import)
> +{
> +	bool was, now, pending;
> +
> +	was = READ_ONCE(ci->i_qtine_errno) == -CEPH_EQUARANTINED ||
> +	      test_bit(CEPH_I_QUARANTINE_BIT, &ci->i_ceph_flags);
> +	pending = test_bit(CEPH_I_QUARANTINE_PENDING_BIT, &ci->i_ceph_flags);
> +
> +	if (!is_import && (newcaps & ~CEPH_CAP_PIN) && was && oserrno == 0 &&
> +	    READ_ONCE(ci->i_qtine_mds) == mds)
> +		ceph_quarantine_clear(ci);
> +
> +	if (oserrno == -CEPH_EQUARANTINED) {
> +		ceph_quarantine_set(ci);
> +		WRITE_ONCE(ci->i_qtine_mds, mds);
> +	}

[Severity: High]
When a quarantined inode's capability is migrated between MDS ranks via
handle_cap_export() or handle_cap_import(), removing the exporting rank's
cap calls ceph_remove_cap() -> __ceph_remove_cap(), which sees
ci->i_qtine_mds == cap->mds and calls ceph_quarantine_reset(ci).

When handle_cap_grant() then processes the CEPH_CAP_OP_IMPORT message from
the new authoritative rank with the default oserrno == 0,
ceph_apply_quarantine() leaves CEPH_I_QUARANTINE_PENDING_BIT set and keeps
ci->i_qtine_mds set to the old exporting rank.

As a result:
1. ceph_inode_quarantine_direct() returns false and
   ceph_quarantine_walk_once() breaks out with CEPH_QWALK_INCOMPLETE instead
   of reporting the retained quarantine mark as CEPH_Q_UNDER.
2. Subsequent normal CEPH_CAP_OP_GRANT messages (above CEPH_CAP_PIN with
   oserrno == 0) and CEPH_MSG_CLIENT_QUARANTINE_DISABLE messages from the
   new authoritative rank cannot clear the mark in ceph_apply_quarantine()
   or ceph_handle_quarantine_disable() because ci->i_qtine_mds still holds
   the pre-migration rank.

Should capability migration preserve the active mark and transfer
ci->i_qtine_mds to the importing rank instead of resetting the mark to
pending when the old rank's cap is removed?

[ ... ]

> diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c
> index 94f5215f8eed5..048e0d502eb4e 100644
> --- a/fs/ceph/inode.c
> +++ b/fs/ceph/inode.c

[ ... ]

> +static bool ceph_quarantine_reply_can_clear(struct ceph_inode_info *ci,
> +					    struct ceph_mds_session *session)
> +{
> +	struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(ci->netfs.inode.i_sb);
> +	struct ceph_mds_request *req;
> +	u64 epoch;
> +
> +	if (READ_ONCE(ci->i_qtine_mds) == session->s_mds)
> +		return true;
> +	/* Different ranks have independent cap sequences. */
> +	req = ceph_current_mds_request(mdsc->fsc);
> +	epoch = (u64)atomic64_read_acquire(&mdsc->quarantine_epoch);
> +	return req && !(epoch & 1) && req->r_quarantine_epoch == epoch &&
> +		!atomic_read(&mdsc->quarantine_splices);
> +}

[ ... ]

> @@ -1407,7 +1873,7 @@ int ceph_fill_inode(struct inode *inode, struct folio *locked_folio,
>  	/* were we issued a capability? */
>  	if (info_caps) {
>  		if (!ceph_in_snap(inode)) {
> -			ceph_add_cap(inode, session,
> +			bool cap_current = ceph_add_cap(inode, session,
>  				     le64_to_cpu(info->cap.cap_id),
>  				     info_caps,
>  				     le32_to_cpu(info->cap.wanted),
> @@ -1416,6 +1882,14 @@ int ceph_fill_inode(struct inode *inode, struct folio *locked_folio,
>  				     le64_to_cpu(info->cap.realm),
>  				     info->cap.flags, &new_cap);
>  
> +			/*
> +			 * Caps above PIN mean quarantine has been lifted,
> +			 * including when the disable message was lost.
> +			 */
> +			if (cap_current && (info_caps & ~CEPH_CAP_PIN) &&
> +			    ceph_quarantine_reply_can_clear(ci, session))
> +				ceph_quarantine_clear(ci);

[Severity: High]
Can ceph_quarantine_reply_can_clear() fail to clear quarantine on target
inodes and readdir entries when the reply comes from a rank different from
ci->i_qtine_mds because reply processing itself increments
mdsc->quarantine_epoch?

req->r_quarantine_epoch is sampled once in ceph_mdsc_create_request() and
never updated:

  req->r_quarantine_epoch = atomic64_read_acquire(&mdsc->quarantine_epoch);

During reply handling, ceph_fill_trace() processes parent_dir before
req->r_target_inode:

  ceph_fill_trace()
    if (parent_dir)
      ceph_fill_inode(parent_dir, ...)
        ceph_quarantine_clear(ci) -> advances mdsc->quarantine_epoch by 2
    ...
    if (rinfo->head->is_target)
      ceph_fill_inode(in, ...)
        ceph_quarantine_reply_can_clear(ci, session)

If both parent_dir and req->r_target_inode had CEPH_I_QUARANTINE_BIT set on
another rank, clearing parent_dir increments mdsc->quarantine_epoch by 2.
When ceph_fill_inode() then runs for req->r_target_inode,
req->r_quarantine_epoch == epoch is false, leaving the target inode's
quarantine mark set.

The same self-invalidation happens in handle_reply() ->
ceph_readdir_prepopulate(), where each entry in the loop calls
ceph_fill_inode() and splice_dentry() (which calls
ceph_quarantine_splice_start() and ceph_quarantine_splice_finish(),
advancing mdsc->quarantine_epoch by 4), preventing
ceph_quarantine_reply_can_clear() from succeeding on subsequent entries in
the same readdir reply.

In addition, any unrelated dentry splice or cross-directory rename on the
superblock while the MDS request is in flight (or sampling an odd
mdsc->quarantine_epoch in ceph_mdsc_create_request()) will cause
req->r_quarantine_epoch == epoch to fail even when no new quarantine mark was
set.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.20261025T182649Z.ca9ae7a18449.git.amarkuze@redhat.com?part=4

  reply	other threads:[~2026-10-25 19:12 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-25 18:42 [PATCH v6 0/5] ceph: support subvolume quarantine Alex Markuze
2026-10-25 18:42 ` [PATCH v6 1/5] dcache: export d_find_alias_rcu() for modular filesystems Alex Markuze
2026-10-25 18:42 ` [PATCH v6 2/5] fscrypt: allow filesystems to disable symlink target caching Alex Markuze
2026-10-25 18:42 ` [PATCH v6 3/5] ceph: add subvolume quarantine wire definitions Alex Markuze
2026-10-25 18:42 ` [PATCH v6 4/5] ceph: record subvolume quarantine from MDS cap updates Alex Markuze
2026-10-25 19:12   ` sashiko-bot [this message]
2026-10-25 18:42 ` [PATCH v6 5/5] ceph: fail quarantined subvolume access with EACCES Alex Markuze
2026-10-25 19:48   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-184356@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=amarkuze@redhat.com \
    --cc=brauner@kernel.org \
    --cc=ebiggers@kernel.org \
    --cc=idryomov@gmail.com \
    --cc=jack@suse.cz \
    --cc=jaegeuk@kernel.org \
    --cc=linux-fscrypt@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=slava@dubeyko.com \
    --cc=tytso@mit.edu \
    --cc=viro@zeniv.linux.org.uk \
    --cc=xiubo.li@clyso.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox