Re: [PATCH] ocfs2: fix chunk number of the first chunk in a local quota file
From: Jiaming Zhang
Date: Wed Sep 16 2026 - 00:59:12 EST
Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx> 于2026年9月16日周三 09:07写道:
>
> On Mon, 14 Sep 2026 11:49:40 +0800 Jiaming Zhang <r772577952@xxxxxxxxx> 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@xxxxxxxxxxxxxx/
> > Cc: stable@xxxxxxxxxxxxxxx
>
> Especially when proposing a -stable backport.
>
> I see from your KASAN report
> (https://lore.kernel.org/CANypQFZ05tpth0Xc33gmP6jgPnkY4VuezyHcsajV-SqCsmN_gg@xxxxxxxxxxxxxx)
> 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