* [PATCH] ceph: fix multiple unsafe decodes in decode_locker()
@ 2026-05-23 8:59 Pavitra Jha
2026-05-26 19:18 ` Viacheslav Dubeyko
0 siblings, 1 reply; 9+ messages in thread
From: Pavitra Jha @ 2026-05-23 8:59 UTC (permalink / raw)
To: idryomov; +Cc: amarkuze, slava, ceph-devel, linux-kernel, stable, Pavitra Jha
decode_locker() in cls_lock_client.c contains three unsafe decode
operations that allow a malicious or compromised OSD to trigger
slab-out-of-bounds reads:
1. ceph_decode_copy() at the locker_id_t name field has no preceding
bounds check. With p == end after ceph_start_decoding() accepts
struct_len=0, this reads sizeof(ceph_entity_name) = 9 bytes past
the validated buffer boundary.
2. *p += sizeof(struct ceph_timespec) after the locker_info_t header
is an unchecked pointer advance. A malicious OSD can position p
past end, causing all subsequent _safe checks to pass against a
bogus boundary.
3. len = ceph_decode_32(p) has no preceding bounds check, and the
immediately following *p += len is uncapped. A malicious OSD can
send len=0xffffffff, advancing p gigabytes past end and escaping
the decode window entirely.
Fix all three by replacing bare operations with their safe variants:
ceph_decode_copy -> ceph_decode_copy_safe
*p += sizeof(...) -> ceph_decode_skip_n
ceph_decode_32(p) -> ceph_decode_32_safe
*p += len -> ceph_decode_skip_n
A new bad: label is added to return -EINVAL on any bounds violation.
KASAN report (kernel 7.0.0-rc7, QEMU/x86_64, KASLR disabled):
[ 26.183969] ceph_oob4_poc: buf=ffff888009e31000 end=ffff888009e31fa0
[ 26.186087] ceph_oob4_poc: struct_v=1 struct_len=0 p==end: 1
[ 26.186738] ceph_oob4_poc: triggering bare ceph_decode_32 past slab boundary...
[ 26.187679] ==================================================================
[ 26.188236] BUG: KASAN: slab-out-of-bounds in ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
[ 26.188236] Read of size 4 at addr ffff888009e31fa0 by task insmod/59
[ 26.188236] CPU: 0 UID: 0 PID: 59 Comm: insmod Tainted: G O 7.0.0-rc7-g9c2abf69da83-dirty #15 PREEMPT(lazy)
[ 26.188236] Tainted: [O]=OOT_MODULE
[ 26.188236] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
[ 26.188236] Call Trace:
[ 26.188236] <TASK>
[ 26.188236] dump_stack_lvl+0x4d/0x70
[ 26.188236] print_report+0x170/0x4f3
[ 26.188236] ? __pfx__raw_spin_lock_irqsave+0x10/0x10
[ 26.188236] kasan_report+0xda/0x110
[ 26.188236] ? ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
[ 26.188236] ? ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
[ 26.188236] ? __pfx_ceph_oob4_init+0x10/0x10 [ceph_oob4_poc]
[ 26.188236] ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
[ 26.188236] do_one_initcall+0x9a/0x3a0
[ 26.188236] ? __pfx_do_one_initcall+0x10/0x10
[ 26.188236] do_init_module+0x27c/0x790
[ 26.188236] load_module+0x4a9a/0x6350
[ 26.188236] init_module_from_file+0x15c/0x180
[ 26.188236] idempotent_init_module+0x21f/0x750
[ 26.188236] __x64_sys_finit_module+0xba/0x120
[ 26.188236] do_syscall_64+0xe2/0x570
[ 26.188236] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 26.188236] </TASK>
[ 26.188236] The buggy address belongs to the object at ffff888009e31000
[ 26.188236] which belongs to the cache kmalloc-4k of size 4096
[ 26.188236] The buggy address is located 0 bytes to the right of
[ 26.188236] allocated 4000-byte region [ffff888009e31000, ffff888009e31fa0)
[ 26.188236] Memory state around the buggy address:
[ 26.188236] ffff888009e31f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 26.188236] >ffff888009e31f80: 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc fc
[ 26.188236] ^
[ 26.188236] ffff888009e32000: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 26.188236] ==================================================================
[ 26.255513] ceph_oob4_poc: len=0xcccccccc (OOB garbage from KASAN redzone)
0xCCCCCCCC is KASAN redzone poison, confirming the read landed in
the slab redzone immediately past the 4000-byte allocation.
Attacker model: a malicious or compromised OSD in a multi-tenant Ceph
deployment can trigger this against any kernel client that issues the
lock.get_info class method (e.g. during RBD exclusive lock acquisition)
without any further privileges beyond OSD session establishment.
Fixes: d4ed4a530562 ("libceph: support for lock.lock_info")
Cc: stable@vger.kernel.org
Signed-off-by: Pavitra Jha <jhapavitra98@gmail.com>
---
net/ceph/cls_lock_client.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/net/ceph/cls_lock_client.c b/net/ceph/cls_lock_client.c
index 78276273c..00f0309a6 100644
--- a/net/ceph/cls_lock_client.c
+++ b/net/ceph/cls_lock_client.c
@@ -259,7 +259,7 @@ static int decode_locker(void **p, void *end, struct ceph_locker *locker)
if (ret)
return ret;
- ceph_decode_copy(p, &locker->id.name, sizeof(locker->id.name));
+ ceph_decode_copy_safe(p, end, &locker->id.name, sizeof(locker->id.name), bad);
s = ceph_extract_encoded_string(p, end, NULL, GFP_NOIO);
if (IS_ERR(s))
return PTR_ERR(s);
@@ -270,19 +270,21 @@ static int decode_locker(void **p, void *end, struct ceph_locker *locker)
if (ret)
return ret;
- *p += sizeof(struct ceph_timespec); /* skip expiration */
+ ceph_decode_skip_n(p, end, sizeof(struct ceph_timespec), bad); /* skip expiration */
ret = ceph_decode_entity_addr(p, end, &locker->info.addr);
if (ret)
return ret;
- len = ceph_decode_32(p);
- *p += len; /* skip description */
+ ceph_decode_32_safe(p, end, len, bad);
+ ceph_decode_skip_n(p, end, len, bad); /* skip description */
dout("%s %s%llu cookie %s addr %s\n", __func__,
ENTITY_NAME(locker->id.name), locker->id.cookie,
ceph_pr_addr(&locker->info.addr));
return 0;
+bad:
+ return -EINVAL;
}
static int decode_lockers(void **p, void *end, u8 *type, char **tag,
--
2.53.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH] ceph: fix multiple unsafe decodes in decode_locker()
2026-05-23 8:59 [PATCH] ceph: fix multiple unsafe decodes in decode_locker() Pavitra Jha
@ 2026-05-26 19:18 ` Viacheslav Dubeyko
2026-05-28 13:01 ` [PATCH v2] " Pavitra Jha
0 siblings, 1 reply; 9+ messages in thread
From: Viacheslav Dubeyko @ 2026-05-26 19:18 UTC (permalink / raw)
To: idryomov, jhapavitra98
Cc: stable, Alex Markuze, slava, linux-kernel, ceph-devel
On Sat, 2026-05-23 at 04:59 -0400, Pavitra Jha wrote:
> decode_locker() in cls_lock_client.c contains three unsafe decode
> operations that allow a malicious or compromised OSD to trigger
> slab-out-of-bounds reads:
>
> 1. ceph_decode_copy() at the locker_id_t name field has no preceding
> bounds check. With p == end after ceph_start_decoding() accepts
> struct_len=0, this reads sizeof(ceph_entity_name) = 9 bytes past
> the validated buffer boundary.
>
> 2. *p += sizeof(struct ceph_timespec) after the locker_info_t header
> is an unchecked pointer advance. A malicious OSD can position p
> past end, causing all subsequent _safe checks to pass against a
> bogus boundary.
>
> 3. len = ceph_decode_32(p) has no preceding bounds check, and the
> immediately following *p += len is uncapped. A malicious OSD can
> send len=0xffffffff, advancing p gigabytes past end and escaping
> the decode window entirely.
>
> Fix all three by replacing bare operations with their safe variants:
> ceph_decode_copy -> ceph_decode_copy_safe
> *p += sizeof(...) -> ceph_decode_skip_n
> ceph_decode_32(p) -> ceph_decode_32_safe
> *p += len -> ceph_decode_skip_n
>
> A new bad: label is added to return -EINVAL on any bounds violation.
>
> KASAN report (kernel 7.0.0-rc7, QEMU/x86_64, KASLR disabled):
>
> [ 26.183969] ceph_oob4_poc: buf=ffff888009e31000 end=ffff888009e31fa0
> [ 26.186087] ceph_oob4_poc: struct_v=1 struct_len=0 p==end: 1
> [ 26.186738] ceph_oob4_poc: triggering bare ceph_decode_32 past slab boundary...
> [ 26.187679] ==================================================================
> [ 26.188236] BUG: KASAN: slab-out-of-bounds in ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
> [ 26.188236] Read of size 4 at addr ffff888009e31fa0 by task insmod/59
> [ 26.188236] CPU: 0 UID: 0 PID: 59 Comm: insmod Tainted: G O 7.0.0-rc7-g9c2abf69da83-dirty #15 PREEMPT(lazy)
> [ 26.188236] Tainted: [O]=OOT_MODULE
> [ 26.188236] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
> [ 26.188236] Call Trace:
> [ 26.188236] <TASK>
> [ 26.188236] dump_stack_lvl+0x4d/0x70
> [ 26.188236] print_report+0x170/0x4f3
> [ 26.188236] ? __pfx__raw_spin_lock_irqsave+0x10/0x10
> [ 26.188236] kasan_report+0xda/0x110
> [ 26.188236] ? ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
> [ 26.188236] ? ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
> [ 26.188236] ? __pfx_ceph_oob4_init+0x10/0x10 [ceph_oob4_poc]
> [ 26.188236] ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
> [ 26.188236] do_one_initcall+0x9a/0x3a0
> [ 26.188236] ? __pfx_do_one_initcall+0x10/0x10
> [ 26.188236] do_init_module+0x27c/0x790
> [ 26.188236] load_module+0x4a9a/0x6350
> [ 26.188236] init_module_from_file+0x15c/0x180
> [ 26.188236] idempotent_init_module+0x21f/0x750
> [ 26.188236] __x64_sys_finit_module+0xba/0x120
> [ 26.188236] do_syscall_64+0xe2/0x570
> [ 26.188236] entry_SYSCALL_64_after_hwframe+0x77/0x7f
> [ 26.188236] </TASK>
> [ 26.188236] The buggy address belongs to the object at ffff888009e31000
> [ 26.188236] which belongs to the cache kmalloc-4k of size 4096
> [ 26.188236] The buggy address is located 0 bytes to the right of
> [ 26.188236] allocated 4000-byte region [ffff888009e31000, ffff888009e31fa0)
> [ 26.188236] Memory state around the buggy address:
> [ 26.188236] ffff888009e31f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
> [ 26.188236] >ffff888009e31f80: 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc fc
> [ 26.188236] ^
> [ 26.188236] ffff888009e32000: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
> [ 26.188236] ==================================================================
> [ 26.255513] ceph_oob4_poc: len=0xcccccccc (OOB garbage from KASAN redzone)
>
> 0xCCCCCCCC is KASAN redzone poison, confirming the read landed in
> the slab redzone immediately past the 4000-byte allocation.
>
> Attacker model: a malicious or compromised OSD in a multi-tenant Ceph
> deployment can trigger this against any kernel client that issues the
> lock.get_info class method (e.g. during RBD exclusive lock acquisition)
> without any further privileges beyond OSD session establishment.
>
> Fixes: d4ed4a530562 ("libceph: support for lock.lock_info")
> Cc: stable@vger.kernel.org
> Signed-off-by: Pavitra Jha <jhapavitra98@gmail.com>
> ---
> net/ceph/cls_lock_client.c | 10 ++++++----
> 1 file changed, 6 insertions(+), 4 deletions(-)
>
> diff --git a/net/ceph/cls_lock_client.c b/net/ceph/cls_lock_client.c
> index 78276273c..00f0309a6 100644
> --- a/net/ceph/cls_lock_client.c
> +++ b/net/ceph/cls_lock_client.c
> @@ -259,7 +259,7 @@ static int decode_locker(void **p, void *end, struct ceph_locker *locker)
> if (ret)
> return ret;
>
> - ceph_decode_copy(p, &locker->id.name, sizeof(locker->id.name));
> + ceph_decode_copy_safe(p, end, &locker->id.name, sizeof(locker->id.name), bad);
Are you sure that this line not longer than 80 symbols?
> s = ceph_extract_encoded_string(p, end, NULL, GFP_NOIO);
> if (IS_ERR(s))
> return PTR_ERR(s);
> @@ -270,19 +270,21 @@ static int decode_locker(void **p, void *end, struct ceph_locker *locker)
> if (ret)
> return ret;
>
> - *p += sizeof(struct ceph_timespec); /* skip expiration */
> + ceph_decode_skip_n(p, end, sizeof(struct ceph_timespec), bad); /* skip expiration */
I don't think that it makes sense to keep comment at the end of line now. Let's
do it in such way:
/* skip expiration */
ceph_decode_skip_n(p, end, sizeof(struct ceph_timespec), bad);
>
> ret = ceph_decode_entity_addr(p, end, &locker->info.addr);
> if (ret)
> return ret;
>
> - len = ceph_decode_32(p);
> - *p += len; /* skip description */
> + ceph_decode_32_safe(p, end, len, bad);
> + ceph_decode_skip_n(p, end, len, bad); /* skip description */
Ditto.
/* skip description */
ceph_decode_skip_n(p, end, len, bad);
>
> dout("%s %s%llu cookie %s addr %s\n", __func__,
> ENTITY_NAME(locker->id.name), locker->id.cookie,
> ceph_pr_addr(&locker->info.addr));
> return 0;
> +bad:
You are not consistent. Now it's bad name but not e_inval. :)
> + return -EINVAL;
I still think that we have not input arguments here. I am not fully sure that
EINVAL is proper error code here.
Thanks,
Slava.
>
>
> }
>
> static int decode_lockers(void **p, void *end, u8 *type, char **tag,
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2] ceph: fix multiple unsafe decodes in decode_locker()
2026-05-26 19:18 ` Viacheslav Dubeyko
@ 2026-05-28 13:01 ` Pavitra Jha
2026-05-28 18:14 ` Viacheslav Dubeyko
0 siblings, 1 reply; 9+ messages in thread
From: Pavitra Jha @ 2026-05-28 13:01 UTC (permalink / raw)
To: idryomov, Slava.Dubeyko; +Cc: ceph-devel, linux-kernel, stable, Pavitra Jha
decode_locker() in cls_lock_client.c contains three unsafe decode
operations that allow a malicious or compromised OSD to trigger
slab-out-of-bounds reads:
1. ceph_decode_copy() at the locker_id_t name field has no preceding
bounds check. With p == end after ceph_start_decoding() accepts
struct_len=0, this reads sizeof(ceph_entity_name) = 9 bytes past
the validated buffer boundary.
2. *p += sizeof(struct ceph_timespec) after the locker_info_t header
is an unchecked pointer advance. A malicious OSD can position p
past end, causing all subsequent _safe checks to pass against a
bogus boundary.
3. len = ceph_decode_32(p) has no preceding bounds check, and the
immediately following *p += len is uncapped. A malicious OSD can
send len=0xffffffff, advancing p gigabytes past end and escaping
the decode window entirely.
Fix all three by replacing bare operations with their safe variants:
ceph_decode_copy -> ceph_decode_copy_safe
*p += sizeof(...) -> ceph_decode_skip_n
ceph_decode_32(p) -> ceph_decode_32_safe
*p += len -> ceph_decode_skip_n
A new out_bad: label is added to return -EINVAL on any bounds
violation. -EINVAL is appropriate here: the data received from the OSD
is structurally malformed, which is an invalid argument to the decode
contract regardless of whether the caller or the wire is at fault.
KASAN report (kernel 7.0.0-rc7, QEMU/x86_64, KASLR disabled):
[ 26.183969] ceph_oob4_poc: buf=ffff888009e31000 end=ffff888009e31fa0
[ 26.186087] ceph_oob4_poc: struct_v=1 struct_len=0 p==end: 1
[ 26.186738] ceph_oob4_poc: triggering bare ceph_decode_32 past slab boundary...
[ 26.187679] ==================================================================
[ 26.188236] BUG: KASAN: slab-out-of-bounds in ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
[ 26.188236] Read of size 4 at addr ffff888009e31fa0 by task insmod/59
[ 26.188236] CPU: 0 UID: 0 PID: 59 Comm: insmod Tainted: G O 7.0.0-rc7-g9c2abf69da83-dirty #15 PREEMPT(lazy)
[ 26.188236] Call Trace:
[ 26.188236] <TASK>
[ 26.188236] dump_stack_lvl+0x4d/0x70
[ 26.188236] print_report+0x170/0x4f3
[ 26.188236] kasan_report+0xda/0x110
[ 26.188236] ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
[ 26.188236] do_one_initcall+0x9a/0x3a0
[ 26.188236] do_init_module+0x27c/0x790
[ 26.188236] load_module+0x4a9a/0x6350
[ 26.188236] init_module_from_file+0x15c/0x180
[ 26.188236] idempotent_init_module+0x21f/0x750
[ 26.188236] __x64_sys_finit_module+0xba/0x120
[ 26.188236] do_syscall_64+0xe2/0x570
[ 26.188236] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 26.188236] </TASK>
[ 26.188236] The buggy address belongs to the object at ffff888009e31000
[ 26.188236] which belongs to the cache kmalloc-4k of size 4096
[ 26.188236] The buggy address is located 0 bytes to the right of
[ 26.188236] allocated 4000-byte region [ffff888009e31000, ffff888009e31fa0)
[ 26.188236] ffff888009e31f80: 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc fc
[ 26.188236] ^
[ 26.188236] ffff888009e32000: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 26.188236] ==================================================================
[ 26.255513] ceph_oob4_poc: len=0xcccccccc (OOB garbage from KASAN redzone)
0xCCCCCCCC is KASAN redzone poison, confirming the read landed in
the slab redzone immediately past the 4000-byte allocation.
Attacker model: a malicious or compromised OSD in a multi-tenant Ceph
deployment can trigger this against any kernel client that issues the
lock.get_info class method (e.g. during RBD exclusive lock acquisition)
without any further privileges beyond OSD session establishment.
Fixes: d4ed4a530562 ("libceph: support for lock.lock_info")
Cc: stable@vger.kernel.org
Signed-off-by: Pavitra Jha <jhapavitra98@gmail.com>
---
v2: Move inline comments above ceph_decode_skip_n calls to stay within
the 80-column limit, and rename label bad -> out_bad, per
Viacheslav Dubeyko's review.
---
net/ceph/cls_lock_client.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/net/ceph/cls_lock_client.c b/net/ceph/cls_lock_client.c
index 78276273c..4f27b3d15 100644
--- a/net/ceph/cls_lock_client.c
+++ b/net/ceph/cls_lock_client.c
@@ -259,7 +259,7 @@ static int decode_locker(void **p, void *end, struct ceph_locker *locker)
if (ret)
return ret;
- ceph_decode_copy(p, &locker->id.name, sizeof(locker->id.name));
+ ceph_decode_copy_safe(p, end, &locker->id.name, sizeof(locker->id.name), out_bad);
s = ceph_extract_encoded_string(p, end, NULL, GFP_NOIO);
if (IS_ERR(s))
return PTR_ERR(s);
@@ -270,19 +270,23 @@ static int decode_locker(void **p, void *end, struct ceph_locker *locker)
if (ret)
return ret;
- *p += sizeof(struct ceph_timespec); /* skip expiration */
+ /* skip expiration */
+ ceph_decode_skip_n(p, end, sizeof(struct ceph_timespec), out_bad);
ret = ceph_decode_entity_addr(p, end, &locker->info.addr);
if (ret)
return ret;
- len = ceph_decode_32(p);
- *p += len; /* skip description */
+ ceph_decode_32_safe(p, end, len, out_bad);
+ /* skip description */
+ ceph_decode_skip_n(p, end, len, out_bad);
dout("%s %s%llu cookie %s addr %s\n", __func__,
ENTITY_NAME(locker->id.name), locker->id.cookie,
ceph_pr_addr(&locker->info.addr));
return 0;
+out_bad:
+ return -EINVAL;
}
static int decode_lockers(void **p, void *end, u8 *type, char **tag,
--
2.53.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2] ceph: fix multiple unsafe decodes in decode_locker()
2026-05-28 13:01 ` [PATCH v2] " Pavitra Jha
@ 2026-05-28 18:14 ` Viacheslav Dubeyko
2026-05-28 18:17 ` [PATCH v2] ceph: fix bare ceph_decode_8 OOB in decode_lockers() Pavitra Jha
2026-06-02 5:02 ` [PATCH v3] ceph: fix multiple unsafe decodes in decode_locker() Pavitra Jha
0 siblings, 2 replies; 9+ messages in thread
From: Viacheslav Dubeyko @ 2026-05-28 18:14 UTC (permalink / raw)
To: idryomov, jhapavitra98; +Cc: ceph-devel, stable, linux-kernel
On Thu, 2026-05-28 at 09:01 -0400, Pavitra Jha wrote:
> decode_locker() in cls_lock_client.c contains three unsafe decode
> operations that allow a malicious or compromised OSD to trigger
> slab-out-of-bounds reads:
>
> 1. ceph_decode_copy() at the locker_id_t name field has no preceding
> bounds check. With p == end after ceph_start_decoding() accepts
> struct_len=0, this reads sizeof(ceph_entity_name) = 9 bytes past
> the validated buffer boundary.
>
> 2. *p += sizeof(struct ceph_timespec) after the locker_info_t header
> is an unchecked pointer advance. A malicious OSD can position p
> past end, causing all subsequent _safe checks to pass against a
> bogus boundary.
>
> 3. len = ceph_decode_32(p) has no preceding bounds check, and the
> immediately following *p += len is uncapped. A malicious OSD can
> send len=0xffffffff, advancing p gigabytes past end and escaping
> the decode window entirely.
>
> Fix all three by replacing bare operations with their safe variants:
> ceph_decode_copy -> ceph_decode_copy_safe
> *p += sizeof(...) -> ceph_decode_skip_n
> ceph_decode_32(p) -> ceph_decode_32_safe
> *p += len -> ceph_decode_skip_n
>
> A new out_bad: label is added to return -EINVAL on any bounds
> violation. -EINVAL is appropriate here: the data received from the OSD
> is structurally malformed, which is an invalid argument to the decode
> contract regardless of whether the caller or the wire is at fault.
>
> KASAN report (kernel 7.0.0-rc7, QEMU/x86_64, KASLR disabled):
>
> [ 26.183969] ceph_oob4_poc: buf=ffff888009e31000 end=ffff888009e31fa0
> [ 26.186087] ceph_oob4_poc: struct_v=1 struct_len=0 p==end: 1
> [ 26.186738] ceph_oob4_poc: triggering bare ceph_decode_32 past slab boundary...
> [ 26.187679] ==================================================================
> [ 26.188236] BUG: KASAN: slab-out-of-bounds in ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
> [ 26.188236] Read of size 4 at addr ffff888009e31fa0 by task insmod/59
> [ 26.188236] CPU: 0 UID: 0 PID: 59 Comm: insmod Tainted: G O 7.0.0-rc7-g9c2abf69da83-dirty #15 PREEMPT(lazy)
> [ 26.188236] Call Trace:
> [ 26.188236] <TASK>
> [ 26.188236] dump_stack_lvl+0x4d/0x70
> [ 26.188236] print_report+0x170/0x4f3
> [ 26.188236] kasan_report+0xda/0x110
> [ 26.188236] ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
> [ 26.188236] do_one_initcall+0x9a/0x3a0
> [ 26.188236] do_init_module+0x27c/0x790
> [ 26.188236] load_module+0x4a9a/0x6350
> [ 26.188236] init_module_from_file+0x15c/0x180
> [ 26.188236] idempotent_init_module+0x21f/0x750
> [ 26.188236] __x64_sys_finit_module+0xba/0x120
> [ 26.188236] do_syscall_64+0xe2/0x570
> [ 26.188236] entry_SYSCALL_64_after_hwframe+0x77/0x7f
> [ 26.188236] </TASK>
> [ 26.188236] The buggy address belongs to the object at ffff888009e31000
> [ 26.188236] which belongs to the cache kmalloc-4k of size 4096
> [ 26.188236] The buggy address is located 0 bytes to the right of
> [ 26.188236] allocated 4000-byte region [ffff888009e31000, ffff888009e31fa0)
> [ 26.188236] ffff888009e31f80: 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc fc
> [ 26.188236] ^
> [ 26.188236] ffff888009e32000: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
> [ 26.188236] ==================================================================
> [ 26.255513] ceph_oob4_poc: len=0xcccccccc (OOB garbage from KASAN redzone)
>
> 0xCCCCCCCC is KASAN redzone poison, confirming the read landed in
> the slab redzone immediately past the 4000-byte allocation.
>
> Attacker model: a malicious or compromised OSD in a multi-tenant Ceph
> deployment can trigger this against any kernel client that issues the
> lock.get_info class method (e.g. during RBD exclusive lock acquisition)
> without any further privileges beyond OSD session establishment.
>
> Fixes: d4ed4a530562 ("libceph: support for lock.lock_info")
> Cc: stable@vger.kernel.org
> Signed-off-by: Pavitra Jha <jhapavitra98@gmail.com>
> ---
> v2: Move inline comments above ceph_decode_skip_n calls to stay within
> the 80-column limit, and rename label bad -> out_bad, per
> Viacheslav Dubeyko's review.
> ---
> net/ceph/cls_lock_client.c | 12 ++++++++----
> 1 file changed, 8 insertions(+), 4 deletions(-)
>
> diff --git a/net/ceph/cls_lock_client.c b/net/ceph/cls_lock_client.c
> index 78276273c..4f27b3d15 100644
> --- a/net/ceph/cls_lock_client.c
> +++ b/net/ceph/cls_lock_client.c
> @@ -259,7 +259,7 @@ static int decode_locker(void **p, void *end, struct ceph_locker *locker)
> if (ret)
> return ret;
>
> - ceph_decode_copy(p, &locker->id.name, sizeof(locker->id.name));
> + ceph_decode_copy_safe(p, end, &locker->id.name, sizeof(locker->id.name), out_bad);
This line contains 85 symbols. What's the point of such long line? Why not
something like this?
ceph_decode_copy_safe(p, end, &locker->id.name,
sizeof(locker->id.name), out_bad);
Have you run scripts/checkpatch.pl for the patch? I am sure that the check
should complain about this.
Thanks,
Slava.
> s = ceph_extract_encoded_string(p, end, NULL, GFP_NOIO);
> if (IS_ERR(s))
> return PTR_ERR(s);
> @@ -270,19 +270,23 @@ static int decode_locker(void **p, void *end, struct ceph_locker *locker)
> if (ret)
> return ret;
>
> - *p += sizeof(struct ceph_timespec); /* skip expiration */
> + /* skip expiration */
> + ceph_decode_skip_n(p, end, sizeof(struct ceph_timespec), out_bad);
>
> ret = ceph_decode_entity_addr(p, end, &locker->info.addr);
> if (ret)
> return ret;
>
> - len = ceph_decode_32(p);
> - *p += len; /* skip description */
> + ceph_decode_32_safe(p, end, len, out_bad);
> + /* skip description */
> + ceph_decode_skip_n(p, end, len, out_bad);
>
> dout("%s %s%llu cookie %s addr %s\n", __func__,
> ENTITY_NAME(locker->id.name), locker->id.cookie,
> ceph_pr_addr(&locker->info.addr));
> return 0;
> +out_bad:
> + return -EINVAL;
> }
>
> static int decode_lockers(void **p, void *end, u8 *type, char **tag,
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2] ceph: fix bare ceph_decode_8 OOB in decode_lockers()
2026-05-28 18:14 ` Viacheslav Dubeyko
@ 2026-05-28 18:17 ` Pavitra Jha
2026-06-02 5:02 ` [PATCH v3] ceph: fix multiple unsafe decodes in decode_locker() Pavitra Jha
1 sibling, 0 replies; 9+ messages in thread
From: Pavitra Jha @ 2026-05-28 18:17 UTC (permalink / raw)
To: Slava.Dubeyko; +Cc: idryomov, amarkuze, ceph-devel, linux-kernel, stable
Hi Slava,
Sorry for the confusion here.
The original patch fixing:
*num_lockers = ceph_decode_32(p);
and the later fix for:
*type = ceph_decode_8(p);
were intended as two separate incremental fixes,
not as replacement versions of the same patch.
I mistakenly labeled the second one as "[PATCH v2]" and also
sent it as a separate thread, which made the relationship unclear.
I'll resend this properly as a clean patch series.
Thanks,
Pavitra
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3] ceph: fix multiple unsafe decodes in decode_locker()
2026-05-28 18:14 ` Viacheslav Dubeyko
2026-05-28 18:17 ` [PATCH v2] ceph: fix bare ceph_decode_8 OOB in decode_lockers() Pavitra Jha
@ 2026-06-02 5:02 ` Pavitra Jha
1 sibling, 0 replies; 9+ messages in thread
From: Pavitra Jha @ 2026-06-02 5:02 UTC (permalink / raw)
To: idryomov, Slava.Dubeyko; +Cc: ceph-devel, linux-kernel, stable, Pavitra Jha
decode_locker() in cls_lock_client.c contains three unsafe decode
operations that allow a malicious or compromised OSD to trigger
slab-out-of-bounds reads:
1. ceph_decode_copy() at the locker_id_t name field has no preceding
bounds check. With p == end after ceph_start_decoding() accepts
struct_len=0, this reads sizeof(ceph_entity_name) = 9 bytes past
the validated buffer boundary.
2. *p += sizeof(struct ceph_timespec) after the locker_info_t header
is an unchecked pointer advance. A malicious OSD can position p
past end, causing all subsequent _safe checks to pass against a
bogus boundary.
3. len = ceph_decode_32(p) has no preceding bounds check, and the
immediately following *p += len is uncapped. A malicious OSD can
send len=0xffffffff, advancing p gigabytes past end and escaping
the decode window entirely.
Fix all three by replacing bare operations with their safe variants:
ceph_decode_copy -> ceph_decode_copy_safe
*p += sizeof(...) -> ceph_decode_skip_n
ceph_decode_32(p) -> ceph_decode_32_safe
*p += len -> ceph_decode_skip_n
A new out_bad: label is added to return -EINVAL on any bounds
violation. -EINVAL is appropriate here: the data received from the OSD
is structurally malformed, which is an invalid argument to the decode
contract regardless of whether the caller or the wire is at fault.
KASAN report (kernel 7.0.0-rc7, QEMU/x86_64, KASLR disabled):
[ 26.183969] ceph_oob4_poc: buf=ffff888009e31000 end=ffff888009e31fa0
[ 26.186087] ceph_oob4_poc: struct_v=1 struct_len=0 p==end: 1
[ 26.186738] ceph_oob4_poc: triggering bare ceph_decode_32 past slab boundary...
[ 26.187679] ==================================================================
[ 26.188236] BUG: KASAN: slab-out-of-bounds in ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
[ 26.188236] Read of size 4 at addr ffff888009e31fa0 by task insmod/59
[ 26.188236] CPU: 0 UID: 0 PID: 59 Comm: insmod Tainted: G O 7.0.0-rc7-g9c2abf69da83-dirty #15 PREEMPT(lazy)
[ 26.188236] Tainted: [O]=OOT_MODULE
[ 26.188236] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
[ 26.188236] Call Trace:
[ 26.188236] <TASK>
[ 26.188236] dump_stack_lvl+0x4d/0x70
[ 26.188236] print_report+0x170/0x4f3
[ 26.188236] kasan_report+0xda/0x110
[ 26.188236] ceph_oob4_init+0x22b/0xff0 [ceph_oob4_poc]
[ 26.188236] do_one_initcall+0x9a/0x3a0
[ 26.188236] do_init_module+0x27c/0x790
[ 26.188236] load_module+0x4a9a/0x6350
[ 26.188236] init_module_from_file+0x15c/0x180
[ 26.188236] idempotent_init_module+0x21f/0x750
[ 26.188236] __x64_sys_finit_module+0xba/0x120
[ 26.188236] do_syscall_64+0xe2/0x570
[ 26.188236] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 26.188236] </TASK>
[ 26.188236] The buggy address belongs to the object at ffff888009e31000
[ 26.188236] which belongs to the cache kmalloc-4k of size 4096
[ 26.188236] The buggy address is located 0 bytes to the right of
[ 26.188236] allocated 4000-byte region [ffff888009e31000, ffff888009e31fa0)
[ 26.188236] ffff888009e31f80: 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc fc
[ 26.188236] ^
[ 26.188236] ffff888009e32000: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 26.188236] ==================================================================
[ 26.255513] ceph_oob4_poc: len=0xcccccccc (OOB garbage from KASAN redzone)
0xCCCCCCCC is KASAN redzone poison, confirming the read landed in
the slab redzone immediately past the 4000-byte allocation.
Attacker model: a malicious or compromised OSD in a multi-tenant Ceph
deployment can trigger this against any kernel client that issues the
lock.get_info class method (e.g. during RBD exclusive lock acquisition)
without any further privileges beyond OSD session establishment.
Fixes: d4ed4a530562 ("libceph: support for lock.lock_info")
Cc: stable@vger.kernel.org
Signed-off-by: Pavitra Jha <jhapavitra98@gmail.com>
---
v3: Split ceph_decode_copy_safe call to fit 80-column limit,
per Viacheslav Dubeyko's review of v2.
v2: Move inline comments above ceph_decode_skip_n calls, rename
label bad -> out_bad, per Viacheslav Dubeyko's review.
---
net/ceph/cls_lock_client.c | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/net/ceph/cls_lock_client.c b/net/ceph/cls_lock_client.c
index 4e6a6d3e4..79a449897 100644
--- a/net/ceph/cls_lock_client.c
+++ b/net/ceph/cls_lock_client.c
@@ -259,7 +259,8 @@ static int decode_locker(void **p, void *end, struct ceph_locker *locker)
if (ret)
return ret;
- ceph_decode_copy(p, &locker->id.name, sizeof(locker->id.name));
+ ceph_decode_copy_safe(p, end, &locker->id.name,
+ sizeof(locker->id.name), out_bad);
s = ceph_extract_encoded_string(p, end, NULL, GFP_NOIO);
if (IS_ERR(s))
return PTR_ERR(s);
@@ -270,19 +271,23 @@ static int decode_locker(void **p, void *end, struct ceph_locker *locker)
if (ret)
return ret;
- *p += sizeof(struct ceph_timespec); /* skip expiration */
+ /* skip expiration */
+ ceph_decode_skip_n(p, end, sizeof(struct ceph_timespec), out_bad);
ret = ceph_decode_entity_addr(p, end, &locker->info.addr);
if (ret)
return ret;
- len = ceph_decode_32(p);
- *p += len; /* skip description */
+ ceph_decode_32_safe(p, end, len, out_bad);
+ /* skip description */
+ ceph_decode_skip_n(p, end, len, out_bad);
dout("%s %s%llu cookie %s addr %s\n", __func__,
ENTITY_NAME(locker->id.name), locker->id.cookie,
ceph_pr_addr(&locker->info.addr));
return 0;
+out_bad:
+ return -EINVAL;
}
static int decode_lockers(void **p, void *end, u8 *type, char **tag,
--
2.53.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2] ceph: fix bare ceph_decode_8 OOB in decode_lockers()
2026-05-28 13:25 ` [PATCH v2] ceph: fix bare ceph_decode_8 OOB in decode_lockers() Pavitra Jha
2026-05-28 18:06 ` Viacheslav Dubeyko
@ 2026-05-30 17:46 ` Dan Carpenter
1 sibling, 0 replies; 9+ messages in thread
From: Dan Carpenter @ 2026-05-30 17:46 UTC (permalink / raw)
To: oe-kbuild, Pavitra Jha, idryomov
Cc: lkp, oe-kbuild-all, Slava.Dubeyko, amarkuze, ceph-devel,
linux-kernel, stable, Pavitra Jha
Hi Pavitra,
kernel test robot noticed the following build warnings:
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Pavitra-Jha/ceph-fix-bare-ceph_decode_8-OOB-in-decode_lockers/20260528-212749
base: https://github.com/ceph/ceph-client.git testing
patch link: https://lore.kernel.org/r/20260528132521.843004-1-jhapavitra98%40gmail.com
patch subject: [PATCH v2] ceph: fix bare ceph_decode_8 OOB in decode_lockers()
config: um-randconfig-r073-20260530 (https://download.01.org/0day-ci/archive/20260531/202605310022.LGyGb8eD-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
smatch: v0.5.0-9185-gbcc58b9c
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Reported-by: Dan Carpenter <error27@gmail.com>
| Closes: https://lore.kernel.org/r/202605310022.LGyGb8eD-lkp@intel.com/
smatch warnings:
net/ceph/cls_lock_client.c:313 decode_lockers() warn: missing error code 'ret'
vim +/ret +313 net/ceph/cls_lock_client.c
d4ed4a53056288 Douglas Fuller 2015-06-29 288 static int decode_lockers(void **p, void *end, u8 *type, char **tag,
d4ed4a53056288 Douglas Fuller 2015-06-29 289 struct ceph_locker **lockers, u32 *num_lockers)
d4ed4a53056288 Douglas Fuller 2015-06-29 290 {
d4ed4a53056288 Douglas Fuller 2015-06-29 291 u8 struct_v;
d4ed4a53056288 Douglas Fuller 2015-06-29 292 u32 struct_len;
d4ed4a53056288 Douglas Fuller 2015-06-29 293 char *s;
d4ed4a53056288 Douglas Fuller 2015-06-29 294 int i;
d4ed4a53056288 Douglas Fuller 2015-06-29 295 int ret;
d4ed4a53056288 Douglas Fuller 2015-06-29 296
d4ed4a53056288 Douglas Fuller 2015-06-29 297 ret = ceph_start_decoding(p, end, 1, "cls_lock_get_info_reply",
d4ed4a53056288 Douglas Fuller 2015-06-29 298 &struct_v, &struct_len);
d4ed4a53056288 Douglas Fuller 2015-06-29 299 if (ret)
d4ed4a53056288 Douglas Fuller 2015-06-29 300 return ret;
d4ed4a53056288 Douglas Fuller 2015-06-29 301
d4ed4a53056288 Douglas Fuller 2015-06-29 302 *num_lockers = ceph_decode_32(p);
69050f8d6d075d Kees Cook 2026-02-20 303 *lockers = kzalloc_objs(**lockers, *num_lockers, GFP_NOIO);
d4ed4a53056288 Douglas Fuller 2015-06-29 304 if (!*lockers)
d4ed4a53056288 Douglas Fuller 2015-06-29 305 return -ENOMEM;
d4ed4a53056288 Douglas Fuller 2015-06-29 306
d4ed4a53056288 Douglas Fuller 2015-06-29 307 for (i = 0; i < *num_lockers; i++) {
d4ed4a53056288 Douglas Fuller 2015-06-29 308 ret = decode_locker(p, end, *lockers + i);
d4ed4a53056288 Douglas Fuller 2015-06-29 309 if (ret)
d4ed4a53056288 Douglas Fuller 2015-06-29 310 goto err_free_lockers;
d4ed4a53056288 Douglas Fuller 2015-06-29 311 }
d4ed4a53056288 Douglas Fuller 2015-06-29 312
cff58e4599d8e1 Pavitra Jha 2026-05-28 @313 ceph_decode_8_safe(p, end, *type, err_free_lockers);
This macro has a goto err_free_lockers but the error code isn't set.
d4ed4a53056288 Douglas Fuller 2015-06-29 314 s = ceph_extract_encoded_string(p, end, NULL, GFP_NOIO);
d4ed4a53056288 Douglas Fuller 2015-06-29 315 if (IS_ERR(s)) {
d4ed4a53056288 Douglas Fuller 2015-06-29 316 ret = PTR_ERR(s);
d4ed4a53056288 Douglas Fuller 2015-06-29 317 goto err_free_lockers;
d4ed4a53056288 Douglas Fuller 2015-06-29 318 }
d4ed4a53056288 Douglas Fuller 2015-06-29 319
d4ed4a53056288 Douglas Fuller 2015-06-29 320 *tag = s;
d4ed4a53056288 Douglas Fuller 2015-06-29 321 return 0;
d4ed4a53056288 Douglas Fuller 2015-06-29 322
d4ed4a53056288 Douglas Fuller 2015-06-29 323 err_free_lockers:
d4ed4a53056288 Douglas Fuller 2015-06-29 324 ceph_free_lockers(*lockers, *num_lockers);
d4ed4a53056288 Douglas Fuller 2015-06-29 325 return ret;
d4ed4a53056288 Douglas Fuller 2015-06-29 326 }
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2] ceph: fix bare ceph_decode_8 OOB in decode_lockers()
2026-05-28 13:25 ` [PATCH v2] ceph: fix bare ceph_decode_8 OOB in decode_lockers() Pavitra Jha
@ 2026-05-28 18:06 ` Viacheslav Dubeyko
2026-05-30 17:46 ` Dan Carpenter
1 sibling, 0 replies; 9+ messages in thread
From: Viacheslav Dubeyko @ 2026-05-28 18:06 UTC (permalink / raw)
To: idryomov, jhapavitra98; +Cc: Alex Markuze, ceph-devel, linux-kernel, stable
On Thu, 2026-05-28 at 09:25 -0400, Pavitra Jha wrote:
> decode_lockers() in cls_lock_client.c contains a bare ceph_decode_8(p)
> call after the decode_locker() loop that has no preceding bounds check.
>
> If a malicious or compromised OSD sends a cls_lock_get_info_reply where
> num_lockers is crafted such that the decode_locker() loop advances p
> exactly to end (or if num_lockers=0 and p is already at end after
> ceph_start_decoding() accepts struct_len=0), the subsequent bare
> ceph_decode_8(p) reads one byte past the validated buffer boundary.
>
> The result is passed directly into *type, which is subsequently used as
> a lock type discriminator by callers. An OSD-controlled one-byte OOB
> read at this position gives an attacker influence over the lock type
> field with no further preconditions.
>
> The safe variant ceph_decode_8_safe() already exists and is used
> consistently throughout the codebase. This site is the only remaining
> bare ceph_decode_8() in the decode_lockers() post-loop path.
>
> The goto target is err_free_lockers (not err_inval) because *lockers is
> already allocated at this point and must be freed on any decode failure.
>
> v1 of this series fixed the bare ceph_decode_32() before kzalloc_objs()
> and added the err_inval label. This v2 addresses the second bare decode
> identified by Viacheslav Dubeyko's review.
>
> Regarding the -EINVAL choice (raised in review): -EINVAL is correct for
> the err_inval path. The failure is structural malformation of OSD-supplied
> data, not a memory shortage. -ENOMEM would misrepresent the failure class
> to callers and to stable@ backporters triaging error paths.
>
> Attacker model: a malicious or compromised OSD in a multi-tenant Ceph
> deployment can trigger this against any kernel client that issues the
> lock.get_info class method (e.g. during RBD exclusive lock acquisition)
> without any further privileges beyond OSD session establishment.
>
> Fixes: d4ed4a530562 ("libceph: support for lock.lock_info")
> Cc: stable@vger.kernel.org
> Signed-off-by: Pavitra Jha <jhapavitra98@gmail.com>
> ---
> v2: Replace bare *type = ceph_decode_8(p) with ceph_decode_8_safe(),
> goto err_free_lockers to correctly free *lockers on failure.
> Address Viacheslav Dubeyko's review question about this site and
> clarify -EINVAL rationale.
> ---
> net/ceph/cls_lock_client.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/net/ceph/cls_lock_client.c b/net/ceph/cls_lock_client.c
> index 4f27b3d15..c9183a348 100644
> --- a/net/ceph/cls_lock_client.c
> +++ b/net/ceph/cls_lock_client.c
> @@ -314,7 +314,7 @@ static int decode_lockers(void **p, void *end, u8 *type, char **tag,
> goto err_free_lockers;
> }
>
> - *type = ceph_decode_8(p);
> + ceph_decode_8_safe(p, end, *type, err_free_lockers);
> s = ceph_extract_encoded_string(p, end, NULL, GFP_NOIO);
> if (IS_ERR(s)) {
> ret = PTR_ERR(s);
Is it correct patch? Because, initial patch contained this:
> - *num_lockers = ceph_decode_32(p);
> + ceph_decode_32_safe(p, end, *num_lockers, err_inval);
> *lockers = kzalloc_objs(**lockers, *num_lockers, GFP_NOIO);
And I expected to see both modifications. I am slightly confused, frankly
speaking.
Thanks,
Slava.
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2] ceph: fix bare ceph_decode_8 OOB in decode_lockers()
2026-05-26 19:04 [PATCH] ceph: fix OOB read in decode_lockers() via missing bounds check Viacheslav Dubeyko
@ 2026-05-28 13:25 ` Pavitra Jha
2026-05-28 18:06 ` Viacheslav Dubeyko
2026-05-30 17:46 ` Dan Carpenter
0 siblings, 2 replies; 9+ messages in thread
From: Pavitra Jha @ 2026-05-28 13:25 UTC (permalink / raw)
To: idryomov
Cc: Slava.Dubeyko, amarkuze, ceph-devel, linux-kernel, stable, Pavitra Jha
decode_lockers() in cls_lock_client.c contains a bare ceph_decode_8(p)
call after the decode_locker() loop that has no preceding bounds check.
If a malicious or compromised OSD sends a cls_lock_get_info_reply where
num_lockers is crafted such that the decode_locker() loop advances p
exactly to end (or if num_lockers=0 and p is already at end after
ceph_start_decoding() accepts struct_len=0), the subsequent bare
ceph_decode_8(p) reads one byte past the validated buffer boundary.
The result is passed directly into *type, which is subsequently used as
a lock type discriminator by callers. An OSD-controlled one-byte OOB
read at this position gives an attacker influence over the lock type
field with no further preconditions.
The safe variant ceph_decode_8_safe() already exists and is used
consistently throughout the codebase. This site is the only remaining
bare ceph_decode_8() in the decode_lockers() post-loop path.
The goto target is err_free_lockers (not err_inval) because *lockers is
already allocated at this point and must be freed on any decode failure.
v1 of this series fixed the bare ceph_decode_32() before kzalloc_objs()
and added the err_inval label. This v2 addresses the second bare decode
identified by Viacheslav Dubeyko's review.
Regarding the -EINVAL choice (raised in review): -EINVAL is correct for
the err_inval path. The failure is structural malformation of OSD-supplied
data, not a memory shortage. -ENOMEM would misrepresent the failure class
to callers and to stable@ backporters triaging error paths.
Attacker model: a malicious or compromised OSD in a multi-tenant Ceph
deployment can trigger this against any kernel client that issues the
lock.get_info class method (e.g. during RBD exclusive lock acquisition)
without any further privileges beyond OSD session establishment.
Fixes: d4ed4a530562 ("libceph: support for lock.lock_info")
Cc: stable@vger.kernel.org
Signed-off-by: Pavitra Jha <jhapavitra98@gmail.com>
---
v2: Replace bare *type = ceph_decode_8(p) with ceph_decode_8_safe(),
goto err_free_lockers to correctly free *lockers on failure.
Address Viacheslav Dubeyko's review question about this site and
clarify -EINVAL rationale.
---
net/ceph/cls_lock_client.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/ceph/cls_lock_client.c b/net/ceph/cls_lock_client.c
index 4f27b3d15..c9183a348 100644
--- a/net/ceph/cls_lock_client.c
+++ b/net/ceph/cls_lock_client.c
@@ -314,7 +314,7 @@ static int decode_lockers(void **p, void *end, u8 *type, char **tag,
goto err_free_lockers;
}
- *type = ceph_decode_8(p);
+ ceph_decode_8_safe(p, end, *type, err_free_lockers);
s = ceph_extract_encoded_string(p, end, NULL, GFP_NOIO);
if (IS_ERR(s)) {
ret = PTR_ERR(s);
--
2.53.0
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-06-02 5:02 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-23 8:59 [PATCH] ceph: fix multiple unsafe decodes in decode_locker() Pavitra Jha
2026-05-26 19:18 ` Viacheslav Dubeyko
2026-05-28 13:01 ` [PATCH v2] " Pavitra Jha
2026-05-28 18:14 ` Viacheslav Dubeyko
2026-05-28 18:17 ` [PATCH v2] ceph: fix bare ceph_decode_8 OOB in decode_lockers() Pavitra Jha
2026-06-02 5:02 ` [PATCH v3] ceph: fix multiple unsafe decodes in decode_locker() Pavitra Jha
2026-05-26 19:04 [PATCH] ceph: fix OOB read in decode_lockers() via missing bounds check Viacheslav Dubeyko
2026-05-28 13:25 ` [PATCH v2] ceph: fix bare ceph_decode_8 OOB in decode_lockers() Pavitra Jha
2026-05-28 18:06 ` Viacheslav Dubeyko
2026-05-30 17:46 ` Dan Carpenter
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Powered by JetHome