mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Viacheslav Dubeyko <Slava.Dubeyko@ibm.com>
To: "idryomov@gmail.com" <idryomov@gmail.com>,
	Alex Markuze <amarkuze@redhat.com>,
	"michael.bommarito@gmail.com" <michael.bommarito@gmail.com>,
	"slava@dubeyko.com" <slava@dubeyko.com>
Cc: "ceph-devel@vger.kernel.org" <ceph-devel@vger.kernel.org>,
	"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>
Subject: Re:  [PATCH v3 4/4] ceph: cap delegated inode count in ceph_parse_deleg_inos()
Date: Tue, 7 Jul 2026 18:30:50 +0000	[thread overview]
Message-ID: <47199f35e992c25f4c85fe2b6e5b194197856fba.camel@ibm.com> (raw)
In-Reply-To: <6a37b446fa15089850b123d47484c3663036b6cf.1783447231.git.michael.bommarito@gmail.com>

Hi Michael,

Sorry for delay with the review.

On Tue, 2026-07-07 at 14:06 -0400, Michael Bommarito wrote:
> ceph_parse_deleg_inos() decodes interval sets of delegated inode numbers
> from an MDS create-with-delegation reply. For each set it reads a 64-bit
> start and a 64-bit len with ceph_decode_64_safe(), which only validates
> that the eight bytes are present in the message, not the value, and then
> loops over len while inserting entries into s_delegated_inos.
> 
> len is fully attacker controlled. A malicious or compromised MDS can send
> one huge interval, many intervals in one reply, duplicate intervals, or
> repeated replies that accumulate delegated inodes on the same session.
> The original code bounded none of these and could spin the insert loop or
> grow the xarray without limit.
> 
> Bound both dimensions with a single enforcement point. Track the number
> of delegated inodes held by each MDS session in an atomic counter and
> grow it only in ceph_insert_deleg_ino(), which uses atomic_add_unless()
> to refuse to push the count past CEPH_MAX_DELEG_INOS. Because that helper
> is the only place the counter grows, the per-session population can never
> exceed the cap, so no separate per-session pre-check is needed. The
> counter is decremented when async create consumes a delegated inode or
> when an insert fails, incremented when a delegated inode is restored,
> initialized with the session xarray, and reset when reconnect destroys
> the xarray.
> 
> A per-session cap alone still lets one reply spin the insert loop on
> duplicate ranges without growing the counter, so also cap the aggregate
> interval length accepted from a single reply. Together these bound both
> the loop trip count per reply and the xarray population across replies.
> 
> The cap is a fixed, client-chosen constant rather than a value derived
> from the MDS. mds_client_prealloc_inos is a userspace MDS configuration
> option; it is never sent to the kernel client on the wire, and a
> server-supplied bound could not be trusted for a defensive limit in any
> case. The constant is set well above that option's documented default of
> 1000 (a generous multiple), so legitimate refill behavior is unaffected
> while the CPU and xarray memory a malformed delegation stream can consume
> stays bounded.
> 
> Impact: a malicious or compromised Ceph MDS can no longer make a client
> spin through an unbounded delegated-inode interval or grow one session's
> delegated-inode xarray without limit.
> 
> Fixes: d48464878708 ("ceph: decode interval_sets for delegated inos")
> Cc: stable@vger.kernel.org
> Suggested-by: Viacheslav Dubeyko <Slava.Dubeyko@ibm.com>
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
> ---
>  fs/ceph/mds_client.c | 59 +++++++++++++++++++++++++++++++++++++++-----
>  fs/ceph/mds_client.h |  1 +
>  fs/ceph/super.h      |  9 +++++++
>  3 files changed, 63 insertions(+), 6 deletions(-)
> 
> diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c
> index 94f76e9fe4ead..7b00b83371f7e 100644
> --- a/fs/ceph/mds_client.c
> +++ b/fs/ceph/mds_client.c
> @@ -618,10 +618,36 @@ static int parse_reply_info_filelock(void **p, void *end,
>  
>  #define DELEGATED_INO_AVAILABLE		xa_mk_value(1)
>  
> +static int ceph_insert_deleg_ino(struct ceph_mds_session *s, u64 ino)
> +{
> +	struct ceph_client *cl = s->s_mdsc->fsc->client;
> +	int err;
> +
> +	/*
> +	 * Cap how many delegated inodes a single session may hold. This is
> +	 * the only place that grows the count, so atomic_add_unless() bounds
> +	 * it at exactly CEPH_MAX_DELEG_INOS; s_num_deleg_inos can never exceed
> +	 * that.
> +	 */
> +	if (!atomic_add_unless(&s->s_num_deleg_inos, 1, CEPH_MAX_DELEG_INOS)) {
> +		pr_warn_ratelimited_client(cl,
> +			"MDS session already holds %d delegated inodes\n",
> +			CEPH_MAX_DELEG_INOS);
> +		return -EOVERFLOW;
> +	}
> +
> +	err = xa_insert(&s->s_delegated_inos, ino, DELEGATED_INO_AVAILABLE,
> +			GFP_KERNEL);
> +	if (err)
> +		atomic_dec(&s->s_num_deleg_inos);
> +	return err;
> +}
> +
>  static int ceph_parse_deleg_inos(void **p, void *end,
>  				 struct ceph_mds_session *s)
>  {
>  	struct ceph_client *cl = s->s_mdsc->fsc->client;
> +	u64 msg_deleg_inos = 0;
>  	u32 sets;
>  
>  	ceph_decode_32_safe(p, end, sets, bad);
> @@ -639,16 +665,34 @@ static int ceph_parse_deleg_inos(void **p, void *end,
>  				start, len);
>  			continue;
>  		}
> +
> +		/*
> +		 * Bound the number of inodes one reply may delegate.
> +		 * ceph_insert_deleg_ino() separately caps the per-session
> +		 * population, so this only has to stop one reply from spinning
> +		 * the insert loop under an attacker-controlled len.
> +		 */
> +		if (len > (u64)CEPH_MAX_DELEG_INOS ||
> +		    msg_deleg_inos > (u64)CEPH_MAX_DELEG_INOS - len) {
> +			pr_warn_ratelimited_client(cl,
> +				"MDS reply delegates too many inodes (have %llu, +%llu, max %d)\n",
> +				msg_deleg_inos, len, CEPH_MAX_DELEG_INOS);
> +			return -EIO;
> +		}
> +		msg_deleg_inos += len;
> +
>  		while (len--) {
> -			int err = xa_insert(&s->s_delegated_inos, start++,
> -					    DELEGATED_INO_AVAILABLE,
> -					    GFP_KERNEL);
> +			int err = ceph_insert_deleg_ino(s, start++);
> +
>  			if (!err) {
>  				doutc(cl, "added delegated inode 0x%llx\n", start - 1);
>  			} else if (err == -EBUSY) {
>  				pr_warn_client(cl,
>  					"MDS delegated inode 0x%llx more than once.\n",
>  					start - 1);
> +			} else if (err == -EOVERFLOW) {
> +				/* ceph_insert_deleg_ino() already warned. */
> +				return -EIO;
>  			} else {
>  				return err;
>  			}
> @@ -666,16 +710,17 @@ u64 ceph_get_deleg_ino(struct ceph_mds_session *s)
>  
>  	xa_for_each(&s->s_delegated_inos, ino, val) {
>  		val = xa_erase(&s->s_delegated_inos, ino);
> -		if (val == DELEGATED_INO_AVAILABLE)
> +		if (val == DELEGATED_INO_AVAILABLE) {
> +			atomic_dec(&s->s_num_deleg_inos);
>  			return ino;
> +		}
>  	}
>  	return 0;
>  }
>  
>  int ceph_restore_deleg_ino(struct ceph_mds_session *s, u64 ino)
>  {
> -	return xa_insert(&s->s_delegated_inos, ino, DELEGATED_INO_AVAILABLE,
> -			 GFP_KERNEL);
> +	return ceph_insert_deleg_ino(s, ino);
>  }
>  #else /* BITS_PER_LONG == 64 */
>  /*
> @@ -1062,6 +1107,7 @@ static struct ceph_mds_session *register_session(struct ceph_mds_client *mdsc,
>  	INIT_LIST_HEAD(&s->s_waiting);
>  	INIT_LIST_HEAD(&s->s_unsafe);
>  	xa_init(&s->s_delegated_inos);
> +	atomic_set(&s->s_num_deleg_inos, 0);
>  	INIT_LIST_HEAD(&s->s_cap_releases);
>  	INIT_WORK(&s->s_cap_release_work, ceph_cap_release_work);
>  
> @@ -5103,6 +5149,7 @@ static int send_mds_reconnect(struct ceph_mds_client *mdsc,
>  
>  	/* Serialized by s_mutex against concurrent ceph_get_deleg_ino(). */
>  	xa_destroy(&session->s_delegated_inos);
> +	atomic_set(&session->s_num_deleg_inos, 0);
>  	if (session->s_state == CEPH_MDS_SESSION_CLOSED ||
>  	    session->s_state == CEPH_MDS_SESSION_REJECTED) {
>  		pr_info_client(cl, "mds%d skipping reconnect, session %s\n",
> diff --git a/fs/ceph/mds_client.h b/fs/ceph/mds_client.h
> index 731d6ad04956d..d1a10c4bd4d2d 100644
> --- a/fs/ceph/mds_client.h
> +++ b/fs/ceph/mds_client.h
> @@ -299,6 +299,7 @@ struct ceph_mds_session {
>  	struct list_head  s_waiting;  /* waiting requests */
>  	struct list_head  s_unsafe;   /* unsafe requests */
>  	struct xarray	  s_delegated_inos;
> +	atomic_t	  s_num_deleg_inos;
>  };
>  
>  /*
> diff --git a/fs/ceph/super.h b/fs/ceph/super.h
> index 3737ea7ed88b3..fb2b0a4b7bb3e 100644
> --- a/fs/ceph/super.h
> +++ b/fs/ceph/super.h
> @@ -642,6 +642,15 @@ static inline int ceph_ino_compare(struct inode *inode, void *data)
>  #define CEPH_MDS_INO_LOG_OFFSET		(2 * CEPH_MAX_MDS)
>  #define CEPH_INO_SYSTEM_BASE		((6*CEPH_MAX_MDS) + (CEPH_MAX_MDS * CEPH_NUM_STRAY))
>  
> +/*
> + * Upper bound on the number of delegated inodes a single MDS session may
> + * hold. The MDS normally hands out a small preallocation window (the
> + * userspace mds_client_prealloc_inos option defaults to 1000) and refills
> + * it as the client consumes entries. This leaves generous headroom while
> + * bounding the CPU and memory a malformed delegation interval can consume.
> + */
> +#define CEPH_MAX_DELEG_INOS		8192
> +
>  static inline bool ceph_vino_is_reserved(const struct ceph_vino vino)
>  {
>  	if (vino.ino >= CEPH_INO_SYSTEM_BASE ||

Looks good.

Reviewed-by: Viacheslav Dubeyko <Slava.Dubeyko@ibm.com>

Thanks,
Slava.

      reply	other threads:[~2026-07-07 18:30 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-07 18:05 [PATCH v3 0/4] ceph: bound untrusted MDS and monitor reply decoders Michael Bommarito
2026-07-07 18:05 ` [PATCH v3 1/4] ceph: bound xattr value length in __build_xattrs() Michael Bommarito
2026-07-07 18:05 ` [PATCH v3 2/4] ceph: bound MDSCapAuth path and fs_name decode in handle_session() Michael Bommarito
2026-07-07 18:05 ` [PATCH v3 3/4] ceph: bound num_export_targets array for mds info v2/v3 Michael Bommarito
2026-07-07 18:06 ` [PATCH v3 4/4] ceph: cap delegated inode count in ceph_parse_deleg_inos() Michael Bommarito
2026-07-07 18:30   ` Viacheslav Dubeyko [this message]

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=47199f35e992c25f4c85fe2b6e5b194197856fba.camel@ibm.com \
    --to=slava.dubeyko@ibm.com \
    --cc=amarkuze@redhat.com \
    --cc=ceph-devel@vger.kernel.org \
    --cc=idryomov@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=michael.bommarito@gmail.com \
    --cc=slava@dubeyko.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