Re: [Security][Ceph] OSDMap new_state decode overflow causes out-of-bounds read in kernel client

Ilya Dryomov <[email protected]> Sat, 25 Jul 2026 11:27:52 +0200
Newsgroups org.kernel.vger.ceph-devel
Message-ID <CAOi1vP-180-2L6LxR7X3s=MJJ+rBRZCi0ZjFqg9OS8t=wbK0RA@mail.gmail.com>
On Sat, Jul 25, 2026 at 11:19=E2=80=AFAM Baul Lee <[email protected]> wrote=
:
>
> [PATCH] libceph: fix out-of-bounds read in decode_new_up_state_weight()
>
> decode_new_up_state_weight() parses the new_up_client, new_state and
> new_weight arrays of an incremental OSDMap received from the monitor.
>
> The new_weight loop validates every entry with ceph_decode_need()
> before consuming the 2 * sizeof(u32) it reads.  The new_state loop does
> not: it relies solely on the earlier
>
> ceph_decode_32_safe(p, end, len, e_inval);
> len *=3D sizeof(u32) + (struct_v >=3D 5 ? sizeof(u32) : sizeof(u8));
> ceph_decode_need(p, end, len, e_inval);
>
> pre-check.  That product is computed in 32-bit arithmetic, so a large
> entry count overflows u32 and wraps to a small value; ceph_decode_need()
> then succeeds for the truncated size while the loop still iterates the
> full, attacker-controlled count, decoding an osd id (u32) plus an
> xorstate (u32 or u8) per entry past the end of the map buffer.
>
> A malicious or compromised monitor can thus make a client read out of
> bounds while applying an incremental OSDMap.
>
> Validate each new_state entry against the buffer end before decoding it,
> mirroring the existing new_weight loop.
>
> Fixes: 930c53286977 ("libceph: apply new_state before new_up_client on in=
crementals")
> Reported-by: Federico Kirschbaum <[email protected]>
> Reported-by: Baul Lee <[email protected]>
> ---
>  net/ceph/osdmap.c | 3 +++
>  1 file changed, 3 insertions(+)
>
> diff --git a/net/ceph/osdmap.c b/net/ceph/osdmap.c
> index 8b5b0587a0cf..f09a1c99690e 100644
> --- a/net/ceph/osdmap.c
> +++ b/net/ceph/osdmap.c
> @@ -1902,6 +1902,9 @@ static int decode_new_up_state_weight(void **p, voi=
d *end, u8 struct_v,
>   s32 osd;
>   u32 xorstate;
>
> + ceph_decode_need(p, end, sizeof(u32) +
> + (struct_v >=3D 5 ? sizeof(u32) : sizeof(u8)),
> + e_inval);
>   osd =3D ceph_decode_32(p);
>   if (osd >=3D map->max_osd)
>   goto e_inval;
> --

Hi Federico, Baul,

I believe this is already fixed by a patch that prevents the
"first-pass truncation" (as it's referred to in the previous messages)
from happening in the first place:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?=
id=3D98917a499ec7064c14fc56d180a4fd636fc2784c

Thanks,

                Ilya