mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [Linux Kernel Bug] KASAN: use-after-free Write in ocfs2_local_release_dquot
@ 2026-09-13 14:27 Jiaming Zhang
  2026-09-14  3:49 ` [PATCH] ocfs2: fix chunk number of the first chunk in a local quota file Jiaming Zhang
  0 siblings, 1 reply; 5+ messages in thread
From: Jiaming Zhang @ 2026-09-13 14:27 UTC (permalink / raw)
  To: jlbec, joseph.qi, mark, ocfs2-devel; +Cc: linux-kernel, syzkaller, r772577952

Dear Linux kernel developers and maintainers,

We are writing to report an issue discovered in the OCFS2 subsystem.  The
issue is reproducible on the latest version of linux (v7.3-rc2, commit
df2908090cda368b01ff43709f51890076c56157).  Below is the kernel report:

==================================================================
BUG: KASAN: use-after-free in instrument_write
include/linux/instrumented.h:41 [inline]
BUG: KASAN: use-after-free in ___clear_bit
include/asm-generic/bitops/instrumented-non-atomic.h:44 [inline]
BUG: KASAN: use-after-free in __clear_bit_le
include/asm-generic/bitops/le.h:41 [inline]
BUG: KASAN: use-after-free in _ocfs2_clear_bit fs/ocfs2/ocfs2.h:936 [inline]
BUG: KASAN: use-after-free in ocfs2_clear_bit_unaligned
fs/ocfs2/ocfs2.h:967 [inline]
BUG: KASAN: use-after-free in ocfs2_local_release_dquot+0x44c/0x6a0
fs/ocfs2/quota_local.c:1298
Write of size 8 at addr ffff8880569ef400 by task kworker/u10:1/37

CPU: 1 UID: 0 PID: 37 Comm: kworker/u10:1 Not tainted 7.3.0-rc2 #47
PREEMPT(full)
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix,
1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: quota_events_unbound quota_release_workfn
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x153/0x7e0 mm/kasan/report.c:482
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 check_region_inline mm/kasan/generic.c:-1 [inline]
 kasan_check_range+0x2b0/0x2c0 mm/kasan/generic.c:200
 instrument_write include/linux/instrumented.h:41 [inline]
 ___clear_bit include/asm-generic/bitops/instrumented-non-atomic.h:44 [inline]
 __clear_bit_le include/asm-generic/bitops/le.h:41 [inline]
 _ocfs2_clear_bit fs/ocfs2/ocfs2.h:936 [inline]
 ocfs2_clear_bit_unaligned fs/ocfs2/ocfs2.h:967 [inline]
 ocfs2_local_release_dquot+0x44c/0x6a0 fs/ocfs2/quota_local.c:1298
 ocfs2_release_dquot+0x626/0xc70 fs/ocfs2/quota_global.c:793
 quota_release_workfn+0x35f/0x610 fs/quota/dquot.c:867
 process_one_work kernel/workqueue.c:3396 [inline]
 process_scheduled_works+0xc85/0x18f0 kernel/workqueue.c:3479
 worker_thread+0x8a3/0xda0 kernel/workqueue.c:3560
 kthread+0x38c/0x480 kernel/kthread.c:436
 ret_from_fork+0x509/0xb70 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
 </TASK>

The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x569ef
flags: 0x4fff00000000000(node=1|zone=1|lastcpupid=0x7ff)
raw: 04fff00000000000 ffffea00015a7bc8 ffffea00015a7bc8 0000000000000000
raw: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000
page dumped because: kasan: bad access detected
page_owner info is not present (never set?)

Memory state around the buggy address:
 ffff8880569ef300: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
 ffff8880569ef380: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
>ffff8880569ef400: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
                   ^
 ffff8880569ef480: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
 ffff8880569ef500: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
==================================================================

Following is the root cause analysis for this issue, note that the
analysis is performed with the assistance of LLM, but we try our best to
ensure the accuracy.

When ocfs2 releases a quota structure, it clears the structure's bit in the
bitmap of the local quota file chunk that holds it, and the position of
that bit is computed from the chunk's number.  A bogus chunk number can
push that position far outside the bitmap, so the write lands on unrelated
memory.

The chunk number is wrong because ocfs2_local_quota_add_chunk() appends the
new chunk to the list first, and then numbers it one past the entry before
it:

list_add_tail(&chunk->qc_chunk, &oinfo->dqi_chunk);
chunk->qc_num = list_entry(chunk->qc_chunk.prev,
  struct ocfs2_quota_chunk,
  qc_chunk)->qc_num + 1;

If the list was empty, the entry before it is the list head itself.  The
head is a member of struct ocfs2_mem_dqinfo, not a chunk, so the "previous
number" it reads is part of the dqi_gqinode pointer stored next to the
head.  A crafted image gets a first chunk numbered with part of a kernel
pointer instead of 0.

ol_dqblk_off() turns the chunk number into a file offset by shifting a
32-bit block number left by the block size bits, and a block number built
from such a large chunk number loses its top bits there.  When the entry is
released, the offset is converted back into a bit position using the full
chunk number, so the lost bits push that position far outside the chunk,
and clearing it corrupts unrelated memory.  Depending on where the write
lands, testing also triggered the following reports:

- BUG: unable to handle kernel paging request in ocfs2_local_release_dquot
- KASAN: slab-out-of-bounds Write in ocfs2_local_release_dquot
- KASAN: slab-use-after-free Write in ocfs2_local_release_dquot
- KFENCE: use-after-free write in ocfs2_local_release_dquot

To fix this issue, the chunk number should be computed before the chunk is
added to the list, and set to 0 when the list is empty:

diff --git a/fs/ocfs2/quota_local.c b/fs/ocfs2/quota_local.c
index f55810c59b1b..d351cda9211f 100644
--- a/fs/ocfs2/quota_local.c
+++ b/fs/ocfs2/quota_local.c
@@ -1071,10 +1071,13 @@ static struct ocfs2_quota_chunk
*ocfs2_local_quota_add_chunk(
  goto out;
  }

+ if (list_empty(&oinfo->dqi_chunk))
+ chunk->qc_num = 0;
+ else
+ chunk->qc_num = list_entry(oinfo->dqi_chunk.prev,
+   struct ocfs2_quota_chunk,
+   qc_chunk)->qc_num + 1;
  list_add_tail(&chunk->qc_chunk, &oinfo->dqi_chunk);
- chunk->qc_num = list_entry(chunk->qc_chunk.prev,
-   struct ocfs2_quota_chunk,
-   qc_chunk)->qc_num + 1;
  chunk->qc_headerbh = bh;
  *offset = 0;
  return chunk;

After applying the patch, the reproducer no longer triggers the issue on
our machine.  If this solution is acceptable, we are happy to submit a
formal patch.

The kernel console output, kernel config, syzkaller reproducer, and C
reproducer are available at google drive:
https://drive.google.com/drive/folders/1-LzPTgOALEc3eOjmgM6oOfRKkYXJ-bnO?usp=drive_link

Please let us know if any further information is required.

Best Regards,
Jiaming Zhang

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH] ocfs2: fix chunk number of the first chunk in a local quota file
  2026-09-13 14:27 [Linux Kernel Bug] KASAN: use-after-free Write in ocfs2_local_release_dquot Jiaming Zhang
@ 2026-09-14  3:49 ` Jiaming Zhang
  2026-09-15  9:26   ` Joseph Qi
  2026-09-16  1:07   ` Andrew Morton
  0 siblings, 2 replies; 5+ messages in thread
From: Jiaming Zhang @ 2026-09-14  3:49 UTC (permalink / raw)
  To: jlbec, joseph.qi, mark, ocfs2-devel
  Cc: linux-kernel, syzkaller, r772577952, stable

The local quota file in OCFS2 is divided into chunks, and each chunk
begins with a header block holding a bitmap of the quota entries that
chunk has handed out.  Chunks are numbered from zero, and that number is
used to convert the file offset of an entry back into a bit position in
the bitmap. ocfs2_local_quota_add_chunk() appends a new chunk to the
in-memory list and numbers it one past the chunk that was last:

	list_add_tail(&chunk->qc_chunk, &oinfo->dqi_chunk);
	chunk->qc_num = list_entry(chunk->qc_chunk.prev,
				   struct ocfs2_quota_chunk,
				   qc_chunk)->qc_num + 1;

The predecessor is looked up after the new chunk is added to the list,
so if the list was empty, the prev pointer is the list head itself.  The
head is the dqi_chunk member of struct ocfs2_mem_dqinfo and is not a
chunk, so reading qc_num through it lands 16 bytes past the start of the
head, on the dqi_gqinode pointer that follows it.  The first chunk of
the file is then numbered with the lower half of a kernel pointer
instead of 0.

The list is empty when the local quota file header claims the file has
no chunks.  ocfs2_local_read_info() takes dqi_chunks from that header
without validating it, so an image with dqi_chunks == 0 takes this path
when the first quota entry is allocated.

ocfs2_create_local_dquot() turns the bad number into a file offset with
ol_dqblk_off(), which shifts a 32-bit block number left by the block
size bits, so the top bits of such a large block number are lost.
ocfs2_local_release_dquot() turns the offset back into a bit index with
ol_dqblk_chunk_off(), using the full chunk number, so the lost bits push
that index far outside the bitmap, and clearing it corrupts unrelated
memory.

Compute the chunk number before putting the chunk on the list, and use 0
when the list is empty.

Fixes: 9e33d69f553a ("ocfs2: Implementation of local and global quota file handling")
Closes: https://lore.kernel.org/lkml/CANypQFZ05tpth0Xc33gmP6jgPnkY4VuezyHcsajV-SqCsmN_gg@mail.gmail.com/
Cc: stable@vger.kernel.org
Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Jiaming Zhang <r772577952@gmail.com>
---
 fs/ocfs2/quota_local.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/fs/ocfs2/quota_local.c b/fs/ocfs2/quota_local.c
index f55810c59b1b..d351cda9211f 100644
--- a/fs/ocfs2/quota_local.c
+++ b/fs/ocfs2/quota_local.c
@@ -1071,10 +1071,13 @@ static struct ocfs2_quota_chunk *ocfs2_local_quota_add_chunk(
 		goto out;
 	}
 
+	if (list_empty(&oinfo->dqi_chunk))
+		chunk->qc_num = 0;
+	else
+		chunk->qc_num = list_entry(oinfo->dqi_chunk.prev,
+					   struct ocfs2_quota_chunk,
+					   qc_chunk)->qc_num + 1;
 	list_add_tail(&chunk->qc_chunk, &oinfo->dqi_chunk);
-	chunk->qc_num = list_entry(chunk->qc_chunk.prev,
-				   struct ocfs2_quota_chunk,
-				   qc_chunk)->qc_num + 1;
 	chunk->qc_headerbh = bh;
 	*offset = 0;
 	return chunk;
-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] ocfs2: fix chunk number of the first chunk in a local quota file
  2026-09-14  3:49 ` [PATCH] ocfs2: fix chunk number of the first chunk in a local quota file Jiaming Zhang
@ 2026-09-15  9:26   ` Joseph Qi
  2026-09-16  1:07   ` Andrew Morton
  1 sibling, 0 replies; 5+ messages in thread
From: Joseph Qi @ 2026-09-15  9:26 UTC (permalink / raw)
  To: Jiaming Zhang, Andrew Morton
  Cc: jlbec, mark, ocfs2-devel, linux-kernel, syzkaller, stable



On 9/14/26 11:49 AM, Jiaming Zhang wrote:
> The local quota file in OCFS2 is divided into chunks, and each chunk
> begins with a header block holding a bitmap of the quota entries that
> chunk has handed out.  Chunks are numbered from zero, and that number is
> used to convert the file offset of an entry back into a bit position in
> the bitmap. ocfs2_local_quota_add_chunk() appends a new chunk to the
> in-memory list and numbers it one past the chunk that was last:
> 
> 	list_add_tail(&chunk->qc_chunk, &oinfo->dqi_chunk);
> 	chunk->qc_num = list_entry(chunk->qc_chunk.prev,
> 				   struct ocfs2_quota_chunk,
> 				   qc_chunk)->qc_num + 1;
> 
> The predecessor is looked up after the new chunk is added to the list,
> so if the list was empty, the prev pointer is the list head itself.  The
> head is the dqi_chunk member of struct ocfs2_mem_dqinfo and is not a
> chunk, so reading qc_num through it lands 16 bytes past the start of the
> head, on the dqi_gqinode pointer that follows it.  The first chunk of
> the file is then numbered with the lower half of a kernel pointer
> instead of 0.
> 
> The list is empty when the local quota file header claims the file has
> no chunks.  ocfs2_local_read_info() takes dqi_chunks from that header
> without validating it, so an image with dqi_chunks == 0 takes this path
> when the first quota entry is allocated.
> 
> ocfs2_create_local_dquot() turns the bad number into a file offset with
> ol_dqblk_off(), which shifts a 32-bit block number left by the block
> size bits, so the top bits of such a large block number are lost.
> ocfs2_local_release_dquot() turns the offset back into a bit index with
> ol_dqblk_chunk_off(), using the full chunk number, so the lost bits push
> that index far outside the bitmap, and clearing it corrupts unrelated
> memory.
> 
> Compute the chunk number before putting the chunk on the list, and use 0
> when the list is empty.
> 
> Fixes: 9e33d69f553a ("ocfs2: Implementation of local and global quota file handling")
> Closes: https://lore.kernel.org/lkml/CANypQFZ05tpth0Xc33gmP6jgPnkY4VuezyHcsajV-SqCsmN_gg@mail.gmail.com/
> Cc: stable@vger.kernel.org
> Assisted-by: Claude Code:claude-opus-5
> Signed-off-by: Jiaming Zhang <r772577952@gmail.com>

Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
> ---
>  fs/ocfs2/quota_local.c | 9 ++++++---
>  1 file changed, 6 insertions(+), 3 deletions(-)
> 
> diff --git a/fs/ocfs2/quota_local.c b/fs/ocfs2/quota_local.c
> index f55810c59b1b..d351cda9211f 100644
> --- a/fs/ocfs2/quota_local.c
> +++ b/fs/ocfs2/quota_local.c
> @@ -1071,10 +1071,13 @@ static struct ocfs2_quota_chunk *ocfs2_local_quota_add_chunk(
>  		goto out;
>  	}
>  
> +	if (list_empty(&oinfo->dqi_chunk))
> +		chunk->qc_num = 0;
> +	else
> +		chunk->qc_num = list_entry(oinfo->dqi_chunk.prev,
> +					   struct ocfs2_quota_chunk,
> +					   qc_chunk)->qc_num + 1;
>  	list_add_tail(&chunk->qc_chunk, &oinfo->dqi_chunk);
> -	chunk->qc_num = list_entry(chunk->qc_chunk.prev,
> -				   struct ocfs2_quota_chunk,
> -				   qc_chunk)->qc_num + 1;
>  	chunk->qc_headerbh = bh;
>  	*offset = 0;
>  	return chunk;


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] ocfs2: fix chunk number of the first chunk in a local quota file
  2026-09-14  3:49 ` [PATCH] ocfs2: fix chunk number of the first chunk in a local quota file Jiaming Zhang
  2026-09-15  9:26   ` Joseph Qi
@ 2026-09-16  1:07   ` Andrew Morton
  2026-09-16  4:58     ` Jiaming Zhang
  1 sibling, 1 reply; 5+ messages in thread
From: Andrew Morton @ 2026-09-16  1:07 UTC (permalink / raw)
  To: Jiaming Zhang
  Cc: jlbec, joseph.qi, mark, ocfs2-devel, linux-kernel, syzkaller, stable

On Mon, 14 Sep 2026 11:49:40 +0800 Jiaming Zhang <r772577952@gmail.com> wrote:

> The local quota file in OCFS2 is divided into chunks, and each chunk
> begins with a header block holding a bitmap of the quota entries that
> chunk has handed out.  Chunks are numbered from zero, and that number is
> used to convert the file offset of an entry back into a bit position in
> the bitmap. ocfs2_local_quota_add_chunk() appends a new chunk to the
> in-memory list and numbers it one past the chunk that was last:
> 
> 	list_add_tail(&chunk->qc_chunk, &oinfo->dqi_chunk);
> 	chunk->qc_num = list_entry(chunk->qc_chunk.prev,
> 				   struct ocfs2_quota_chunk,
> 				   qc_chunk)->qc_num + 1;
> 
> The predecessor is looked up after the new chunk is added to the list,
> so if the list was empty, the prev pointer is the list head itself.  The
> head is the dqi_chunk member of struct ocfs2_mem_dqinfo and is not a
> chunk, so reading qc_num through it lands 16 bytes past the start of the
> head, on the dqi_gqinode pointer that follows it.  The first chunk of
> the file is then numbered with the lower half of a kernel pointer
> instead of 0.
> 
> The list is empty when the local quota file header claims the file has
> no chunks.  ocfs2_local_read_info() takes dqi_chunks from that header
> without validating it, so an image with dqi_chunks == 0 takes this path
> when the first quota entry is allocated.
> 
> ocfs2_create_local_dquot() turns the bad number into a file offset with
> ol_dqblk_off(), which shifts a 32-bit block number left by the block
> size bits, so the top bits of such a large block number are lost.
> ocfs2_local_release_dquot() turns the offset back into a bit index with
> ol_dqblk_chunk_off(), using the full chunk number, so the lost bits push
> that index far outside the bitmap, and clearing it corrupts unrelated
> memory.
> 
> Compute the chunk number before putting the chunk on the list, and use 0
> when the list is empty.

Thanks.

What's missing here is any description of how the fix affects our
users.  And that's really really important.

> Fixes: 9e33d69f553a ("ocfs2: Implementation of local and global quota file handling")
> Closes: https://lore.kernel.org/lkml/CANypQFZ05tpth0Xc33gmP6jgPnkY4VuezyHcsajV-SqCsmN_gg@mail.gmail.com/
> Cc: stable@vger.kernel.org

Especially when proposing a -stable backport.

I see from your KASAN report
(https://lore.kernel.org/CANypQFZ05tpth0Xc33gmP6jgPnkY4VuezyHcsajV-SqCsmN_gg@mail.gmail.com)
that there's a use-after-free.  But even that wasn't revealed in your
proposed changelog!

So.

When fixing a bug please always describe the userspace-visible effects
of that bug.

Describe whether there's a know reproducer.  Tell us whether it's some
LLM-found-this theoretical thing or whether it's affecting you in the
real world.

I and all the people downstream from me (Linus, -stable, LTS, distros,
android, hyperscalars, everyone else) all will want to know these
things and I'd like you to provide them.

> Assisted-by: Claude Code:claude-opus-5

Great.  Please add prompts to Claude to ensure that all the info I'm
asking for is included in your changelogs going forward.  Then share
these prompts across your organization.

> --- a/fs/ocfs2/quota_local.c
> +++ b/fs/ocfs2/quota_local.c
> @@ -1071,10 +1071,13 @@ static struct ocfs2_quota_chunk *ocfs2_local_quota_add_chunk(
>  		goto out;
>  	}
>  
> +	if (list_empty(&oinfo->dqi_chunk))
> +		chunk->qc_num = 0;
> +	else
> +		chunk->qc_num = list_entry(oinfo->dqi_chunk.prev,
> +					   struct ocfs2_quota_chunk,
> +					   qc_chunk)->qc_num + 1;
>  	list_add_tail(&chunk->qc_chunk, &oinfo->dqi_chunk);
> -	chunk->qc_num = list_entry(chunk->qc_chunk.prev,
> -				   struct ocfs2_quota_chunk,
> -				   qc_chunk)->qc_num + 1;
>  	chunk->qc_headerbh = bh;
>  	*offset = 0;
>  	return chunk;

whew.  Applied.

I'll retain the cc:stable for now, but I don't know why.


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] ocfs2: fix chunk number of the first chunk in a local quota file
  2026-09-16  1:07   ` Andrew Morton
@ 2026-09-16  4:58     ` Jiaming Zhang
  0 siblings, 0 replies; 5+ messages in thread
From: Jiaming Zhang @ 2026-09-16  4:58 UTC (permalink / raw)
  To: Andrew Morton
  Cc: jlbec, joseph.qi, mark, ocfs2-devel, linux-kernel, syzkaller, stable

Andrew Morton <akpm@linux-foundation.org> 于2026年9月16日周三 09:07写道:
>
> On Mon, 14 Sep 2026 11:49:40 +0800 Jiaming Zhang <r772577952@gmail.com> wrote:
>
> > The local quota file in OCFS2 is divided into chunks, and each chunk
> > begins with a header block holding a bitmap of the quota entries that
> > chunk has handed out.  Chunks are numbered from zero, and that number is
> > used to convert the file offset of an entry back into a bit position in
> > the bitmap. ocfs2_local_quota_add_chunk() appends a new chunk to the
> > in-memory list and numbers it one past the chunk that was last:
> >
> >       list_add_tail(&chunk->qc_chunk, &oinfo->dqi_chunk);
> >       chunk->qc_num = list_entry(chunk->qc_chunk.prev,
> >                                  struct ocfs2_quota_chunk,
> >                                  qc_chunk)->qc_num + 1;
> >
> > The predecessor is looked up after the new chunk is added to the list,
> > so if the list was empty, the prev pointer is the list head itself.  The
> > head is the dqi_chunk member of struct ocfs2_mem_dqinfo and is not a
> > chunk, so reading qc_num through it lands 16 bytes past the start of the
> > head, on the dqi_gqinode pointer that follows it.  The first chunk of
> > the file is then numbered with the lower half of a kernel pointer
> > instead of 0.
> >
> > The list is empty when the local quota file header claims the file has
> > no chunks.  ocfs2_local_read_info() takes dqi_chunks from that header
> > without validating it, so an image with dqi_chunks == 0 takes this path
> > when the first quota entry is allocated.
> >
> > ocfs2_create_local_dquot() turns the bad number into a file offset with
> > ol_dqblk_off(), which shifts a 32-bit block number left by the block
> > size bits, so the top bits of such a large block number are lost.
> > ocfs2_local_release_dquot() turns the offset back into a bit index with
> > ol_dqblk_chunk_off(), using the full chunk number, so the lost bits push
> > that index far outside the bitmap, and clearing it corrupts unrelated
> > memory.
> >
> > Compute the chunk number before putting the chunk on the list, and use 0
> > when the list is empty.
>
> Thanks.
>
> What's missing here is any description of how the fix affects our
> users.  And that's really really important.

Sorry for the missing information.  I have added it below, and I hope
it is useful.

>
> > Fixes: 9e33d69f553a ("ocfs2: Implementation of local and global quota file handling")
> > Closes: https://lore.kernel.org/lkml/CANypQFZ05tpth0Xc33gmP6jgPnkY4VuezyHcsajV-SqCsmN_gg@mail.gmail.com/
> > Cc: stable@vger.kernel.org
>
> Especially when proposing a -stable backport.
>
> I see from your KASAN report
> (https://lore.kernel.org/CANypQFZ05tpth0Xc33gmP6jgPnkY4VuezyHcsajV-SqCsmN_gg@mail.gmail.com)
> that there's a use-after-free.  But even that wasn't revealed in your
> proposed changelog!
>
> So.
>
> When fixing a bug please always describe the userspace-visible effects
> of that bug.

Mounting a crafted OCFS2 image can corrupt kernel memory.  When the
local quota file header of such an image claims zero chunks, the first
quota entry allocated during the mount gets a chunk number taken from
a kernel pointer, and releasing that entry clears a bit far outside
the chunk bitmap.

The write will corrupt the data stored at that address, and the
corruption may lead to a system crash or damage unrelated data.
Mounting requires CAP_SYS_ADMIN, so it takes an untrusted image, such
as removable media or a loop mount of a file from elsewhere.

Since the chunk number comes from a kernel pointer, the address of the
bit that gets cleared differs from boot to boot, and the issue has
been reported under several titles:

- BUG: unable to handle kernel paging request in ocfs2_local_release_dquot
- KASAN: use-after-free Write in ocfs2_local_release_dquot
- KASAN: slab-out-of-bounds Write in ocfs2_local_release_dquot
- KASAN: slab-use-after-free Write in ocfs2_local_release_dquot
- KFENCE: use-after-free write in ocfs2_local_release_dquot

Note that none of these is a use-after-free in the quota code: the
chunk and its buffer head are alive.  The bit that gets cleared lies
far outside the bitmap.  KASAN and KFENCE name each report based on
the object occupying that address, which explains why the same issue
is reported under so many different titles.  In the report I sent, the
address fell in a free page, which KASAN labels use-after-free.

>
> Describe whether there's a know reproducer.  Tell us whether it's some
> LLM-found-this theoretical thing or whether it's affecting you in the
> real world.

Yes, there are known reproducers.  Both a syzkaller and a C reproducer
are available from the Google Drive link [1] in my report thread.  The
issue was found by our modified syzkaller, not a theoretical thing
found by an LLM.  I have not seen it outside fuzzing, so it is not
affecting me in the real world.

>
> I and all the people downstream from me (Linus, -stable, LTS, distros,
> android, hyperscalars, everyone else) all will want to know these
> things and I'd like you to provide them.
>
> > Assisted-by: Claude Code:claude-opus-5
>
> Great.  Please add prompts to Claude to ensure that all the info I'm
> asking for is included in your changelogs going forward.  Then share
> these prompts across your organization.

Understood.  Thank you for the suggestion. :)

>
> > --- a/fs/ocfs2/quota_local.c
> > +++ b/fs/ocfs2/quota_local.c
> > @@ -1071,10 +1071,13 @@ static struct ocfs2_quota_chunk *ocfs2_local_quota_add_chunk(
> >               goto out;
> >       }
> >
> > +     if (list_empty(&oinfo->dqi_chunk))
> > +             chunk->qc_num = 0;
> > +     else
> > +             chunk->qc_num = list_entry(oinfo->dqi_chunk.prev,
> > +                                        struct ocfs2_quota_chunk,
> > +                                        qc_chunk)->qc_num + 1;
> >       list_add_tail(&chunk->qc_chunk, &oinfo->dqi_chunk);
> > -     chunk->qc_num = list_entry(chunk->qc_chunk.prev,
> > -                                struct ocfs2_quota_chunk,
> > -                                qc_chunk)->qc_num + 1;
> >       chunk->qc_headerbh = bh;
> >       *offset = 0;
> >       return chunk;
>
> whew.  Applied.
>
> I'll retain the cc:stable for now, but I don't know why.
>

My reasoning was that this issue has been there since 2008, so every
stable tree has it, and the fix is small and changes nothing for a
healthy filesystem.  However, it has only ever been hit by a fuzzer.
You can drop the Cc: stable if you do not think it meets the relevant
criteria.

Please feel free to tell me if any further information is needed.

[1] https://drive.google.com/drive/folders/1-LzPTgOALEc3eOjmgM6oOfRKkYXJ-bnO?usp=drive_link

Best Regards,
Jiaming Zhang

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-09-16  4:59 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-13 14:27 [Linux Kernel Bug] KASAN: use-after-free Write in ocfs2_local_release_dquot Jiaming Zhang
2026-09-14  3:49 ` [PATCH] ocfs2: fix chunk number of the first chunk in a local quota file Jiaming Zhang
2026-09-15  9:26   ` Joseph Qi
2026-09-16  1:07   ` Andrew Morton
2026-09-16  4:58     ` Jiaming Zhang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®