| 0/0 |
2026/05/24 03:37 |
flow |
assessment-security |
42m
Results: map[DenialOfService:true Explanation:Based on the analysis of the bug report and the kernel source code, here is the
assessment of the bug and its security impact:
### Bug Explanation
The bug is located in the error handling path of `f2fs_fill_super()` during the
mount process of an f2fs filesystem.
When the kernel attempts to mount the filesystem, it calls
`f2fs_get_valid_checkpoint()` to read and validate the filesystem's checkpoint
blocks. This function uses `f2fs_get_meta_folio()` to read the blocks, which
populates the page cache of the special `sbi->meta_inode`.
If the checkpoint blocks are corrupted (e.g., they have an invalid CRC, as seen
in the bug report logs), `f2fs_get_valid_checkpoint()` returns an error. The
error handling path in `f2fs_fill_super()` then jumps to the `free_meta_inode`
label and calls `iput(sbi->meta_inode)` to free the inode.
However, the error path fails to call
`truncate_inode_pages_final(META_MAPPING(sbi))` before dropping the inode
reference. Consequently, the `iput()` call triggers the inode eviction process
(`evict()`), which eventually calls `clear_inode()`. Because the invalid
checkpoint blocks were left behind in the inode's page cache, `clear_inode()`
hits the `BUG_ON(inode->i_data.nrpages)` assertion, causing a kernel panic.
### Security Impact
* **Exploitable:** **False**. The bug triggers a deterministic `BUG_ON`
assertion which cleanly halts the kernel (or oopses the current process,
depending on the configuration). It does not cause memory corruption,
use-after-free, or alter control flow in a way that could be leveraged for
privilege escalation or arbitrary code execution.
* **Denial Of Service:** **True**. Triggering the bug results in a kernel panic,
leading to a complete system crash.
* **Accessible From Unprivileged Processes / User Namespaces:** **False**. The
`f2fs` filesystem does not have the `FS_USERNS_MOUNT` flag. Therefore, it cannot
be mounted by unprivileged users, even within a new user namespace. Mounting it
requires `CAP_SYS_ADMIN` in the initial user namespace.
* **Malicious Filesystem / Peripheral Trigger:** **True**. The bug is triggered
by parsing a maliciously crafted f2fs image. While an unprivileged user cannot
mount it directly, the bug poses a significant risk in Desktop or Mobile
environments that are configured to auto-mount external media (like USB drives).
An attacker could plug in a malicious USB drive to crash the system.
* **Network / Remote / VM Triggers:** **False**. The bug is strictly tied to the
local filesystem mount path and cannot be triggered over the network or by a VM
guest (unless the host is explicitly configured to mount an untrusted f2fs image
provided by the guest). Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/24 03:37 |
action |
syz-repro-to-c-repro |
0m
Results: map[SimplifiedCRepro:// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <setjmp.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/loop.h>
#ifndef __NR_memfd_create
#define __NR_memfd_create 319
#endif
static unsigned long long procid;
//% This code is derived from puff.{c,h}, found in the zlib development. The
//% original files come with the following copyright notice:
//% Copyright (C) 2002-2013 Mark Adler, all rights reserved
//% version 2.3, 21 Jan 2013
//% This software is provided 'as-is', without any express or implied
//% warranty. In no event will the author be held liable for any damages
//% arising from the use of this software.
//% Permission is granted to anyone to use this software for any purpose,
//% including commercial applications, and to alter it and redistribute it
//% freely, subject to the following restrictions:
//% 1. The origin of this software must not be misrepresented; you must not
//% claim that you wrote the original software. If you use this software
//% in a product, an acknowledgment in the product documentation would be
//% appreciated but is not required.
//% 2. Altered source versions must be plainly marked as such, and must not be
//% misrepresented as being the original software.
//% 3. This notice may not be removed or altered from any source distribution.
//% Mark Adler madler@alumni.caltech.edu
//% BEGIN CODE DERIVED FROM puff.{c,h}
#define MAXBITS 15
#define MAXLCODES 286
#define MAXDCODES 30
#define MAXCODES (MAXLCODES + MAXDCODES)
#define FIXLCODES 288
struct puff_state {
unsigned char* out;
unsigned long outlen;
unsigned long outcnt;
const unsigned char* in;
unsigned long inlen;
unsigned long incnt;
int bitbuf;
int bitcnt;
jmp_buf env;
};
static int puff_bits(struct puff_state* s, int need)
{
long val = s->bitbuf;
while (s->bitcnt < need) {
if (s->incnt == s->inlen)
longjmp(s->env, 1);
val |= (long)(s->in[s->incnt++]) << s->bitcnt;
s->bitcnt += 8;
}
s->bitbuf = (int)(val >> need);
s->bitcnt -= need;
return (int)(val & ((1L << need) - 1));
}
static int puff_stored(struct puff_state* s)
{
s->bitbuf = 0;
s->bitcnt = 0;
if (s->incnt + 4 > s->inlen)
return 2;
unsigned len = s->in[s->incnt++];
len |= s->in[s->incnt++] << 8;
if (s->in[s->incnt++] != (~len & 0xff) ||
s->in[s->incnt++] != ((~len >> 8) & 0xff))
return -2;
if (s->incnt + len > s->inlen)
return 2;
if (s->outcnt + len > s->outlen)
return 1;
for (; len--; s->outcnt++, s->incnt++) {
if (s->in[s->incnt])
s->out[s->outcnt] = s->in[s->incnt];
}
return 0;
}
struct puff_huffman {
short* count;
short* symbol;
};
static int puff_decode(struct puff_state* s, const struct puff_huffman* h)
{
int first = 0;
int index = 0;
int bitbuf = s->bitbuf;
int left = s->bitcnt;
int code = first = index = 0;
int len = 1;
short* next = h->count + 1;
while (1) {
while (left--) {
code |= bitbuf & 1;
bitbuf >>= 1;
int count = *next++;
if (code - count < first) {
s->bitbuf = bitbuf;
s->bitcnt = (s->bitcnt - len) & 7;
return h->symbol[index + (code - first)];
}
index += count;
first += count;
first <<= 1;
code <<= 1;
len++;
}
left = (MAXBITS + 1) - len;
if (left == 0)
break;
if (s->incnt == s->inlen)
longjmp(s->env, 1);
bitbuf = s->in[s->incnt++];
if (left > 8)
left = 8;
}
return -10;
}
static int puff_construct(struct puff_huffman* h, const short* length, int n)
{
int len;
for (len = 0; len <= MAXBITS; len++)
h->count[len] = 0;
int symbol;
for (symbol = 0; symbol < n; symbol++)
(h->count[length[symbol]])++;
if (h->count[0] == n)
return 0;
int left = 1;
for (len = 1; len <= MAXBITS; len++) {
left <<= 1;
left -= h->count[len];
if (left < 0)
return left;
}
short offs[MAXBITS + 1];
offs[1] = 0;
for (len = 1; len < MAXBITS; len++)
offs[len + 1] = offs[len] + h->count[len];
for (symbol = 0; symbol < n; symbol++)
if (length[symbol] != 0)
h->symbol[offs[length[symbol]]++] = symbol;
return left;
}
static int puff_codes(struct puff_state* s,
const struct puff_huffman* lencode,
const struct puff_huffman* distcode)
{
static const short lens[29] = {
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
static const short lext[29] = {
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
static const short dists[30] = {
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
8193, 12289, 16385, 24577};
static const short dext[30] = {
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
12, 12, 13, 13};
int symbol;
do {
symbol = puff_decode(s, lencode);
if (symbol < 0)
return symbol;
if (symbol < 256) {
if (s->outcnt == s->outlen)
return 1;
if (symbol)
s->out[s->outcnt] = symbol;
s->outcnt++;
} else if (symbol > 256) {
symbol -= 257;
if (symbol >= 29)
return -10;
int len = lens[symbol] + puff_bits(s, lext[symbol]);
symbol = puff_decode(s, distcode);
if (symbol < 0)
return symbol;
unsigned dist = dists[symbol] + puff_bits(s, dext[symbol]);
if (dist > s->outcnt)
return -11;
if (s->outcnt + len > s->outlen)
return 1;
while (len--) {
if (dist <= s->outcnt && s->out[s->outcnt - dist])
s->out[s->outcnt] = s->out[s->outcnt - dist];
s->outcnt++;
}
}
} while (symbol != 256);
return 0;
}
static int puff_fixed(struct puff_state* s)
{
static int virgin = 1;
static short lencnt[MAXBITS + 1], lensym[FIXLCODES];
static short distcnt[MAXBITS + 1], distsym[MAXDCODES];
static struct puff_huffman lencode, distcode;
if (virgin) {
lencode.count = lencnt;
lencode.symbol = lensym;
distcode.count = distcnt;
distcode.symbol = distsym;
short lengths[FIXLCODES];
int symbol;
for (symbol = 0; symbol < 144; symbol++)
lengths[symbol] = 8;
for (; symbol < 256; symbol++)
lengths[symbol] = 9;
for (; symbol < 280; symbol++)
lengths[symbol] = 7;
for (; symbol < FIXLCODES; symbol++)
lengths[symbol] = 8;
puff_construct(&lencode, lengths, FIXLCODES);
for (symbol = 0; symbol < MAXDCODES; symbol++)
lengths[symbol] = 5;
puff_construct(&distcode, lengths, MAXDCODES);
virgin = 0;
}
return puff_codes(s, &lencode, &distcode);
}
static int puff_dynamic(struct puff_state* s)
{
static const short order[19] =
{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
int nlen = puff_bits(s, 5) + 257;
int ndist = puff_bits(s, 5) + 1;
int ncode = puff_bits(s, 4) + 4;
if (nlen > MAXLCODES || ndist > MAXDCODES)
return -3;
short lengths[MAXCODES];
int index;
for (index = 0; index < ncode; index++)
lengths[order[index]] = puff_bits(s, 3);
for (; index < 19; index++)
lengths[order[index]] = 0;
short lencnt[MAXBITS + 1], lensym[MAXLCODES];
struct puff_huffman lencode = {lencnt, lensym};
int err = puff_construct(&lencode, lengths, 19);
if (err != 0)
return -4;
index = 0;
while (index < nlen + ndist) {
int symbol;
int len;
symbol = puff_decode(s, &lencode);
if (symbol < 0)
return symbol;
if (symbol < 16)
lengths[index++] = symbol;
else {
len = 0;
if (symbol == 16) {
if (index == 0)
return -5;
len = lengths[index - 1];
symbol = 3 + puff_bits(s, 2);
} else if (symbol == 17)
symbol = 3 + puff_bits(s, 3);
else
symbol = 11 + puff_bits(s, 7);
if (index + symbol > nlen + ndist)
return -6;
while (symbol--)
lengths[index++] = len;
}
}
if (lengths[256] == 0)
return -9;
err = puff_construct(&lencode, lengths, nlen);
if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1]))
return -7;
short distcnt[MAXBITS + 1], distsym[MAXDCODES];
struct puff_huffman distcode = {distcnt, distsym};
err = puff_construct(&distcode, lengths + nlen, ndist);
if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1]))
return -8;
return puff_codes(s, &lencode, &distcode);
}
static int puff(
unsigned char* dest,
unsigned long* destlen,
const unsigned char* source,
unsigned long sourcelen)
{
struct puff_state s = {
.out = dest,
.outlen = *destlen,
.outcnt = 0,
.in = source,
.inlen = sourcelen,
.incnt = 0,
.bitbuf = 0,
.bitcnt = 0,
};
int err;
if (setjmp(s.env) != 0)
err = 2;
else {
int last;
do {
last = puff_bits(&s, 1);
int type = puff_bits(&s, 2);
err = type == 0 ? puff_stored(&s) : (type == 1 ? puff_fixed(&s) : (type == 2 ? puff_dynamic(&s) : -1));
if (err != 0)
break;
} while (!last);
}
*destlen = s.outcnt;
return err;
}
//% END CODE DERIVED FROM puff.{c,h}
#define ZLIB_HEADER_WIDTH 2
static int puff_zlib_to_file(const unsigned char* source, unsigned long sourcelen, int dest_fd)
{
if (sourcelen < ZLIB_HEADER_WIDTH)
return 0;
source += ZLIB_HEADER_WIDTH;
sourcelen -= ZLIB_HEADER_WIDTH;
const unsigned long max_destlen = 132 << 20;
void* ret = mmap(0, max_destlen, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANON, -1, 0);
if (ret == MAP_FAILED)
return -1;
unsigned char* dest = (unsigned char*)ret;
unsigned long destlen = max_destlen;
int err = puff(dest, &destlen, source, sourcelen);
if (err) {
munmap(dest, max_destlen);
errno = -err;
return -1;
}
if (write(dest_fd, dest, destlen) != (ssize_t)destlen) {
munmap(dest, max_destlen);
return -1;
}
return munmap(dest, max_destlen);
}
static int setup_loop_device(unsigned char* data, unsigned long size, const char* loopname, int* loopfd_p)
{
int err = 0, loopfd = -1;
int memfd = syscall(__NR_memfd_create, "syzkaller", 0);
if (memfd == -1) {
err = errno;
goto error;
}
if (puff_zlib_to_file(data, size, memfd)) {
err = errno;
goto error_close_memfd;
}
loopfd = open(loopname, O_RDWR);
if (loopfd == -1) {
err = errno;
goto error_close_memfd;
}
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
if (errno != EBUSY) {
err = errno;
goto error_close_loop;
}
ioctl(loopfd, LOOP_CLR_FD, 0);
usleep(1000);
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
err = errno;
goto error_close_loop;
}
}
close(memfd);
*loopfd_p = loopfd;
return 0;
error_close_loop:
close(loopfd);
error_close_memfd:
close(memfd);
error:
errno = err;
return -1;
}
static void reset_loop_device(const char* loopname)
{
int loopfd = open(loopname, O_RDWR);
if (loopfd == -1) {
return;
}
if (ioctl(loopfd, LOOP_CLR_FD, 0)) {
}
close(loopfd);
}
static long syz_mount_image(
volatile long fsarg,
volatile long dir,
volatile long flags,
volatile long optsarg,
volatile long change_dir,
volatile unsigned long size,
volatile long image)
{
unsigned char* data = (unsigned char*)image;
int res = -1, err = 0, need_loop_device = !!size;
char* mount_opts = (char*)optsarg;
char* target = (char*)dir;
char* fs = (char*)fsarg;
char* source = NULL;
char loopname[64];
if (need_loop_device) {
int loopfd;
memset(loopname, 0, sizeof(loopname));
snprintf(loopname, sizeof(loopname), "/dev/loop%llu", procid);
if (setup_loop_device(data, size, loopname, &loopfd) == -1)
return -1;
close(loopfd);
source = loopname;
}
mkdir(target, 0777);
char opts[256];
memset(opts, 0, sizeof(opts));
if (strlen(mount_opts) > (sizeof(opts) - 32)) {
}
strncpy(opts, mount_opts, sizeof(opts) - 32);
if (strcmp(fs, "iso9660") == 0) {
flags |= MS_RDONLY;
} else if (strncmp(fs, "ext", 3) == 0) {
bool has_remount_ro = false;
char* remount_ro_start = strstr(opts, "errors=remount-ro");
if (remount_ro_start != NULL) {
char after = *(remount_ro_start + strlen("errors=remount-ro"));
char before = remount_ro_start == opts ? '\0' : *(remount_ro_start - 1);
has_remount_ro = ((before == '\0' || before == ',') && (after == '\0' || after == ','));
}
if (strstr(opts, "errors=panic") || !has_remount_ro)
strcat(opts, ",errors=continue");
} else if (strcmp(fs, "xfs") == 0) {
strcat(opts, ",nouuid");
} else if (strncmp(fs, "gfs2", 4) == 0 && (strstr(opts, "errors=panic") || strstr(opts, "debug"))) {
strcat(opts, ",errors=withdraw");
}
res = mount(source, target, fs, flags, opts);
if (res == -1) {
err = errno;
goto error_clear_loop;
}
res = open(target, O_RDONLY | O_DIRECTORY);
if (res == -1) {
err = errno;
goto error_clear_loop;
}
if (change_dir) {
res = chdir(target);
if (res == -1) {
err = errno;
}
}
error_clear_loop:
if (need_loop_device)
reset_loop_device(loopname);
errno = err;
return res;
}
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// syz_mount_image$f2fs arguments: [
// fs: ptr[in, buffer] {
// buffer: {66 32 66 73 00} (length 0x5)
// }
// dir: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 30 00} (length 0x8)
// }
// flags: mount_flags = 0x8 (8 bytes)
// opts: ptr[in, fs_options[f2fs_options]] {
// fs_options[f2fs_options] {
// elems: array[fs_opt_elem[f2fs_options]] {
// fs_opt_elem[f2fs_options] {
// elem: union f2fs_options {
// block_mode: buffer: {6d 6f 64 65 3d 66 72 61 67 6d 65 6e 74 3a 62 6c 6f 63 6b} (length 0x13)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[f2fs_options] {
// elem: union f2fs_options {
// compress_chksum: buffer: {63 6f 6d 70 72 65 73 73 5f 63 68 6b 73 75 6d} (length 0xf)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[f2fs_options] {
// elem: union f2fs_options {
// noflush_merge: buffer: {6e 6f 66 6c 75 73 68 5f 6d 65 72 67 65} (length 0xd)
// }
// comma: const = 0x2c (1 bytes)
// }
// }
// common: array[fs_opt_elem[fs_options_common]] {
// }
// null: const = 0x0 (1 bytes)
// }
// }
// chdir: int8 = 0x1 (1 bytes)
// size: len = 0x5548 (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x5548)
// }
// ]
// returns fd_dir
memcpy((void*)0x200000000140, "f2fs\000", 5);
memcpy((void*)0x2000000001c0, "./file0\000", 8);
memcpy((void*)0x200000000200, "mode=fragment:block", 19);
*(uint8_t*)0x200000000213 = 0x2c;
memcpy((void*)0x200000000214, "compress_chksum", 15);
*(uint8_t*)0x200000000223 = 0x2c;
memcpy((void*)0x200000000224, "noflush_merge", 13);
*(uint8_t*)0x200000000231 = 0x2c;
*(uint8_t*)0x200000000232 = 0;
memcpy((void*)0x200000000bc0, "... [truncated large byte array] ...", 21832);
syz_mount_image(/*fs=*/0x200000000140, /*dir=*/0x2000000001c0, /*flags=MS_NOEXEC*/8, /*opts=*/0x200000000200, /*chdir=*/1, /*size=*/0x5548, /*img=*/0x200000000bc0);
return 0;
}
]
|
| 2/1 |
2026/05/24 03:37 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/33401b0ae2b26e4c419588f8e79c3dc5358cb294]
|
| 3/1 |
2026/05/24 03:38 |
action |
kernel-builder |
24m
Results: map[KernelObj:/app/workdir/cache/build/213c33be2cceef7492390f06e8980f9c0be63409]
|
| 4/1 |
2026/05/24 04:03 |
action |
codesearch-prepare |
11m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/24 04:14 |
agent |
expert |
5m
Model:
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Instruction:
You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by greeping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption or elevated privileges.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completly wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control tranfers, that's also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitabability depends on how exactly the uninit value
is used in the code, and what it affects.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidetial Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
F2FS-fs (loop0): Wrong SSA boundary, start(3584) end(4096) blocks(3072)
F2FS-fs (loop0): Can't find valid F2FS filesystem in 1th superblock
F2FS-fs (loop0): Image doesn't support compression
F2FS-fs (loop0): invalid crc value
------------[ cut here ]------------
kernel BUG at fs/inode.c:782!
Oops: invalid opcode: 0000 [#1] SMP KASAN PTI
CPU: 1 UID: 0 PID: 6061 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT_{RT,(full)}
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/24/2026
RIP: 0010:clear_inode+0x19a/0x1c0 fs/inode.c:782
Code: 4c 89 f7 e8 08 2f eb ff e9 5a ff ff ff 44 89 f1 80 e1 07 80 c1 03 38 c1 7c a2 4c 89 f7 e8 de 2f eb ff eb 98 e8 47 c7 86 ff 90 <0f> 0b e8 3f c7 86 ff 90 0f 0b e8 37 c7 86 ff 90 0f 0b e8 2f c7 86
RSP: 0018:ffffc90003e17850 EFLAGS: 00010293
RAX: ffffffff823c6d29 RBX: ffff888057a99310 RCX: ffff88802b1edac0
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
RBP: ffffc90003e179c0 R08: 0000000000000000 R09: 0000000000000000
R10: dffffc0000000000 R11: fffffbfff1e8fbef R12: dffffc0000000000
R13: 1ffff920007c2f1c R14: ffff888057a99568 R15: 0000000000000001
FS: 000055557a53e500(0000) GS:ffff8881266c9000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fff6ffc5bbc CR3: 0000000034a72000 CR4: 00000000003526f0
Call Trace:
<TASK>
evict+0x61e/0xb10 fs/inode.c:837
f2fs_fill_super+0x54e4/0x6e30 fs/f2fs/super.c:5147
get_tree_bdev_flags+0x431/0x4f0 fs/super.c:1691
vfs_get_tree+0x92/0x2a0 fs/super.c:1751
fc_mount fs/namespace.c:1199 [inline]
do_new_mount_fc fs/namespace.c:3636 [inline]
do_new_mount+0x329/0xa50 fs/namespace.c:3712
do_mount fs/namespace.c:4035 [inline]
__do_sys_mount fs/namespace.c:4224 [inline]
__se_sys_mount+0x31d/0x420 fs/namespace.c:4201
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xe2/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7ff95c0cc14a
Code: 48 c7 c2 e8 ff ff ff f7 d8 64 89 02 b8 ff ff ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 49 89 ca b8 a5 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffd4f028d88 EFLAGS: 00000246 ORIG_RAX: 00000000000000a5
RAX: ffffffffffffffda RBX: 00007ffd4f028e10 RCX: 00007ff95c0cc14a
RDX: 0000200000000140 RSI: 00002000000001c0 RDI: 00007ffd4f028dd0
RBP: 0000200000000140 R08: 00007ffd4f028e10 R09: 0000000000000008
R10: 0000000000000008 R11: 0000000000000246 R12: 00002000000001c0
R13: 00007ffd4f028dd0 R14: 0000000000005548 R15: 0000200000000200
</TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:clear_inode+0x19a/0x1c0 fs/inode.c:782
Code: 4c 89 f7 e8 08 2f eb ff e9 5a ff ff ff 44 89 f1 80 e1 07 80 c1 03 38 c1 7c a2 4c 89 f7 e8 de 2f eb ff eb 98 e8 47 c7 86 ff 90 <0f> 0b e8 3f c7 86 ff 90 0f 0b e8 37 c7 86 ff 90 0f 0b e8 2f c7 86
RSP: 0018:ffffc90003e17850 EFLAGS: 00010293
RAX: ffffffff823c6d29 RBX: ffff888057a99310 RCX: ffff88802b1edac0
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
RBP: ffffc90003e179c0 R08: 0000000000000000 R09: 0000000000000000
R10: dffffc0000000000 R11: fffffbfff1e8fbef R12: dffffc0000000000
R13: 1ffff920007c2f1c R14: ffff888057a99568 R15: 0000000000000001
FS: 000055557a53e500(0000) GS:ffff8881266c9000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fff6ffc5bbc CR3: 0000000034a72000 CR4: 00000000003526f0
It is reproducible with the followint program.
Keep in mind that it may lack the precise threading, sandboxing, and some arguments of a working reproducer.
But it should give an idea of the involved syscalls.
// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <setjmp.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/loop.h>
#ifndef __NR_memfd_create
#define __NR_memfd_create 319
#endif
static unsigned long long procid;
//% This code is derived from puff.{c,h}, found in the zlib development. The
//% original files come with the following copyright notice:
//% Copyright (C) 2002-2013 Mark Adler, all rights reserved
//% version 2.3, 21 Jan 2013
//% This software is provided 'as-is', without any express or implied
//% warranty. In no event will the author be held liable for any damages
//% arising from the use of this software.
//% Permission is granted to anyone to use this software for any purpose,
//% including commercial applications, and to alter it and redistribute it
//% freely, subject to the following restrictions:
//% 1. The origin of this software must not be misrepresented; you must not
//% claim that you wrote the original software. If you use this software
//% in a product, an acknowledgment in the product documentation would be
//% appreciated but is not required.
//% 2. Altered source versions must be plainly marked as such, and must not be
//% misrepresented as being the original software.
//% 3. This notice may not be removed or altered from any source distribution.
//% Mark Adler madler@alumni.caltech.edu
//% BEGIN CODE DERIVED FROM puff.{c,h}
#define MAXBITS 15
#define MAXLCODES 286
#define MAXDCODES 30
#define MAXCODES (MAXLCODES + MAXDCODES)
#define FIXLCODES 288
struct puff_state {
unsigned char* out;
unsigned long outlen;
unsigned long outcnt;
const unsigned char* in;
unsigned long inlen;
unsigned long incnt;
int bitbuf;
int bitcnt;
jmp_buf env;
};
static int puff_bits(struct puff_state* s, int need)
{
long val = s->bitbuf;
while (s->bitcnt < need) {
if (s->incnt == s->inlen)
longjmp(s->env, 1);
val |= (long)(s->in[s->incnt++]) << s->bitcnt;
s->bitcnt += 8;
}
s->bitbuf = (int)(val >> need);
s->bitcnt -= need;
return (int)(val & ((1L << need) - 1));
}
static int puff_stored(struct puff_state* s)
{
s->bitbuf = 0;
s->bitcnt = 0;
if (s->incnt + 4 > s->inlen)
return 2;
unsigned len = s->in[s->incnt++];
len |= s->in[s->incnt++] << 8;
if (s->in[s->incnt++] != (~len & 0xff) ||
s->in[s->incnt++] != ((~len >> 8) & 0xff))
return -2;
if (s->incnt + len > s->inlen)
return 2;
if (s->outcnt + len > s->outlen)
return 1;
for (; len--; s->outcnt++, s->incnt++) {
if (s->in[s->incnt])
s->out[s->outcnt] = s->in[s->incnt];
}
return 0;
}
struct puff_huffman {
short* count;
short* symbol;
};
static int puff_decode(struct puff_state* s, const struct puff_huffman* h)
{
int first = 0;
int index = 0;
int bitbuf = s->bitbuf;
int left = s->bitcnt;
int code = first = index = 0;
int len = 1;
short* next = h->count + 1;
while (1) {
while (left--) {
code |= bitbuf & 1;
bitbuf >>= 1;
int count = *next++;
if (code - count < first) {
s->bitbuf = bitbuf;
s->bitcnt = (s->bitcnt - len) & 7;
return h->symbol[index + (code - first)];
}
index += count;
first += count;
first <<= 1;
code <<= 1;
len++;
}
left = (MAXBITS + 1) - len;
if (left == 0)
break;
if (s->incnt == s->inlen)
longjmp(s->env, 1);
bitbuf = s->in[s->incnt++];
if (left > 8)
left = 8;
}
return -10;
}
static int puff_construct(struct puff_huffman* h, const short* length, int n)
{
int len;
for (len = 0; len <= MAXBITS; len++)
h->count[len] = 0;
int symbol;
for (symbol = 0; symbol < n; symbol++)
(h->count[length[symbol]])++;
if (h->count[0] == n)
return 0;
int left = 1;
for (len = 1; len <= MAXBITS; len++) {
left <<= 1;
left -= h->count[len];
if (left < 0)
return left;
}
short offs[MAXBITS + 1];
offs[1] = 0;
for (len = 1; len < MAXBITS; len++)
offs[len + 1] = offs[len] + h->count[len];
for (symbol = 0; symbol < n; symbol++)
if (length[symbol] != 0)
h->symbol[offs[length[symbol]]++] = symbol;
return left;
}
static int puff_codes(struct puff_state* s,
const struct puff_huffman* lencode,
const struct puff_huffman* distcode)
{
static const short lens[29] = {
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
static const short lext[29] = {
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
static const short dists[30] = {
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
8193, 12289, 16385, 24577};
static const short dext[30] = {
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
12, 12, 13, 13};
int symbol;
do {
symbol = puff_decode(s, lencode);
if (symbol < 0)
return symbol;
if (symbol < 256) {
if (s->outcnt == s->outlen)
return 1;
if (symbol)
s->out[s->outcnt] = symbol;
s->outcnt++;
} else if (symbol > 256) {
symbol -= 257;
if (symbol >= 29)
return -10;
int len = lens[symbol] + puff_bits(s, lext[symbol]);
symbol = puff_decode(s, distcode);
if (symbol < 0)
return symbol;
unsigned dist = dists[symbol] + puff_bits(s, dext[symbol]);
if (dist > s->outcnt)
return -11;
if (s->outcnt + len > s->outlen)
return 1;
while (len--) {
if (dist <= s->outcnt && s->out[s->outcnt - dist])
s->out[s->outcnt] = s->out[s->outcnt - dist];
s->outcnt++;
}
}
} while (symbol != 256);
return 0;
}
static int puff_fixed(struct puff_state* s)
{
static int virgin = 1;
static short lencnt[MAXBITS + 1], lensym[FIXLCODES];
static short distcnt[MAXBITS + 1], distsym[MAXDCODES];
static struct puff_huffman lencode, distcode;
if (virgin) {
lencode.count = lencnt;
lencode.symbol = lensym;
distcode.count = distcnt;
distcode.symbol = distsym;
short lengths[FIXLCODES];
int symbol;
for (symbol = 0; symbol < 144; symbol++)
lengths[symbol] = 8;
for (; symbol < 256; symbol++)
lengths[symbol] = 9;
for (; symbol < 280; symbol++)
lengths[symbol] = 7;
for (; symbol < FIXLCODES; symbol++)
lengths[symbol] = 8;
puff_construct(&lencode, lengths, FIXLCODES);
for (symbol = 0; symbol < MAXDCODES; symbol++)
lengths[symbol] = 5;
puff_construct(&distcode, lengths, MAXDCODES);
virgin = 0;
}
return puff_codes(s, &lencode, &distcode);
}
static int puff_dynamic(struct puff_state* s)
{
static const short order[19] =
{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
int nlen = puff_bits(s, 5) + 257;
int ndist = puff_bits(s, 5) + 1;
int ncode = puff_bits(s, 4) + 4;
if (nlen > MAXLCODES || ndist > MAXDCODES)
return -3;
short lengths[MAXCODES];
int index;
for (index = 0; index < ncode; index++)
lengths[order[index]] = puff_bits(s, 3);
for (; index < 19; index++)
lengths[order[index]] = 0;
short lencnt[MAXBITS + 1], lensym[MAXLCODES];
struct puff_huffman lencode = {lencnt, lensym};
int err = puff_construct(&lencode, lengths, 19);
if (err != 0)
return -4;
index = 0;
while (index < nlen + ndist) {
int symbol;
int len;
symbol = puff_decode(s, &lencode);
if (symbol < 0)
return symbol;
if (symbol < 16)
lengths[index++] = symbol;
else {
len = 0;
if (symbol == 16) {
if (index == 0)
return -5;
len = lengths[index - 1];
symbol = 3 + puff_bits(s, 2);
} else if (symbol == 17)
symbol = 3 + puff_bits(s, 3);
else
symbol = 11 + puff_bits(s, 7);
if (index + symbol > nlen + ndist)
return -6;
while (symbol--)
lengths[index++] = len;
}
}
if (lengths[256] == 0)
return -9;
err = puff_construct(&lencode, lengths, nlen);
if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1]))
return -7;
short distcnt[MAXBITS + 1], distsym[MAXDCODES];
struct puff_huffman distcode = {distcnt, distsym};
err = puff_construct(&distcode, lengths + nlen, ndist);
if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1]))
return -8;
return puff_codes(s, &lencode, &distcode);
}
static int puff(
unsigned char* dest,
unsigned long* destlen,
const unsigned char* source,
unsigned long sourcelen)
{
struct puff_state s = {
.out = dest,
.outlen = *destlen,
.outcnt = 0,
.in = source,
.inlen = sourcelen,
.incnt = 0,
.bitbuf = 0,
.bitcnt = 0,
};
int err;
if (setjmp(s.env) != 0)
err = 2;
else {
int last;
do {
last = puff_bits(&s, 1);
int type = puff_bits(&s, 2);
err = type == 0 ? puff_stored(&s) : (type == 1 ? puff_fixed(&s) : (type == 2 ? puff_dynamic(&s) : -1));
if (err != 0)
break;
} while (!last);
}
*destlen = s.outcnt;
return err;
}
//% END CODE DERIVED FROM puff.{c,h}
#define ZLIB_HEADER_WIDTH 2
static int puff_zlib_to_file(const unsigned char* source, unsigned long sourcelen, int dest_fd)
{
if (sourcelen < ZLIB_HEADER_WIDTH)
return 0;
source += ZLIB_HEADER_WIDTH;
sourcelen -= ZLIB_HEADER_WIDTH;
const unsigned long max_destlen = 132 << 20;
void* ret = mmap(0, max_destlen, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANON, -1, 0);
if (ret == MAP_FAILED)
return -1;
unsigned char* dest = (unsigned char*)ret;
unsigned long destlen = max_destlen;
int err = puff(dest, &destlen, source, sourcelen);
if (err) {
munmap(dest, max_destlen);
errno = -err;
return -1;
}
if (write(dest_fd, dest, destlen) != (ssize_t)destlen) {
munmap(dest, max_destlen);
return -1;
}
return munmap(dest, max_destlen);
}
static int setup_loop_device(unsigned char* data, unsigned long size, const char* loopname, int* loopfd_p)
{
int err = 0, loopfd = -1;
int memfd = syscall(__NR_memfd_create, "syzkaller", 0);
if (memfd == -1) {
err = errno;
goto error;
}
if (puff_zlib_to_file(data, size, memfd)) {
err = errno;
goto error_close_memfd;
}
loopfd = open(loopname, O_RDWR);
if (loopfd == -1) {
err = errno;
goto error_close_memfd;
}
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
if (errno != EBUSY) {
err = errno;
goto error_close_loop;
}
ioctl(loopfd, LOOP_CLR_FD, 0);
usleep(1000);
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
err = errno;
goto error_close_loop;
}
}
close(memfd);
*loopfd_p = loopfd;
return 0;
error_close_loop:
close(loopfd);
error_close_memfd:
close(memfd);
error:
errno = err;
return -1;
}
static void reset_loop_device(const char* loopname)
{
int loopfd = open(loopname, O_RDWR);
if (loopfd == -1) {
return;
}
if (ioctl(loopfd, LOOP_CLR_FD, 0)) {
}
close(loopfd);
}
static long syz_mount_image(
volatile long fsarg,
volatile long dir,
volatile long flags,
volatile long optsarg,
volatile long change_dir,
volatile unsigned long size,
volatile long image)
{
unsigned char* data = (unsigned char*)image;
int res = -1, err = 0, need_loop_device = !!size;
char* mount_opts = (char*)optsarg;
char* target = (char*)dir;
char* fs = (char*)fsarg;
char* source = NULL;
char loopname[64];
if (need_loop_device) {
int loopfd;
memset(loopname, 0, sizeof(loopname));
snprintf(loopname, sizeof(loopname), "/dev/loop%llu", procid);
if (setup_loop_device(data, size, loopname, &loopfd) == -1)
return -1;
close(loopfd);
source = loopname;
}
mkdir(target, 0777);
char opts[256];
memset(opts, 0, sizeof(opts));
if (strlen(mount_opts) > (sizeof(opts) - 32)) {
}
strncpy(opts, mount_opts, sizeof(opts) - 32);
if (strcmp(fs, "iso9660") == 0) {
flags |= MS_RDONLY;
} else if (strncmp(fs, "ext", 3) == 0) {
bool has_remount_ro = false;
char* remount_ro_start = strstr(opts, "errors=remount-ro");
if (remount_ro_start != NULL) {
char after = *(remount_ro_start + strlen("errors=remount-ro"));
char before = remount_ro_start == opts ? '\0' : *(remount_ro_start - 1);
has_remount_ro = ((before == '\0' || before == ',') && (after == '\0' || after == ','));
}
if (strstr(opts, "errors=panic") || !has_remount_ro)
strcat(opts, ",errors=continue");
} else if (strcmp(fs, "xfs") == 0) {
strcat(opts, ",nouuid");
} else if (strncmp(fs, "gfs2", 4) == 0 && (strstr(opts, "errors=panic") || strstr(opts, "debug"))) {
strcat(opts, ",errors=withdraw");
}
res = mount(source, target, fs, flags, opts);
if (res == -1) {
err = errno;
goto error_clear_loop;
}
res = open(target, O_RDONLY | O_DIRECTORY);
if (res == -1) {
err = errno;
goto error_clear_loop;
}
if (change_dir) {
res = chdir(target);
if (res == -1) {
err = errno;
}
}
error_clear_loop:
if (need_loop_device)
reset_loop_device(loopname);
errno = err;
return res;
}
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// syz_mount_image$f2fs arguments: [
// fs: ptr[in, buffer] {
// buffer: {66 32 66 73 00} (length 0x5)
// }
// dir: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 30 00} (length 0x8)
// }
// flags: mount_flags = 0x8 (8 bytes)
// opts: ptr[in, fs_options[f2fs_options]] {
// fs_options[f2fs_options] {
// elems: array[fs_opt_elem[f2fs_options]] {
// fs_opt_elem[f2fs_options] {
// elem: union f2fs_options {
// block_mode: buffer: {6d 6f 64 65 3d 66 72 61 67 6d 65 6e 74 3a 62 6c 6f 63 6b} (length 0x13)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[f2fs_options] {
// elem: union f2fs_options {
// compress_chksum: buffer: {63 6f 6d 70 72 65 73 73 5f 63 68 6b 73 75 6d} (length 0xf)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[f2fs_options] {
// elem: union f2fs_options {
// noflush_merge: buffer: {6e 6f 66 6c 75 73 68 5f 6d 65 72 67 65} (length 0xd)
// }
// comma: const = 0x2c (1 bytes)
// }
// }
// common: array[fs_opt_elem[fs_options_common]] {
// }
// null: const = 0x0 (1 bytes)
// }
// }
// chdir: int8 = 0x1 (1 bytes)
// size: len = 0x5548 (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x5548)
// }
// ]
// returns fd_dir
memcpy((void*)0x200000000140, "f2fs\000", 5);
memcpy((void*)0x2000000001c0, "./file0\000", 8);
memcpy((void*)0x200000000200, "mode=fragment:block", 19);
*(uint8_t*)0x200000000213 = 0x2c;
memcpy((void*)0x200000000214, "compress_chksum", 15);
*(uint8_t*)0x200000000223 = 0x2c;
memcpy((void*)0x200000000224, "noflush_merge", 13);
*(uint8_t*)0x200000000231 = 0x2c;
*(uint8_t*)0x200000000232 = 0;
memcpy((void*)0x200000000bc0, "... [truncated large byte array] ...", 21832);
syz_mount_image(/*fs=*/0x200000000140, /*dir=*/0x2000000001c0, /*flags=MS_NOEXEC*/8, /*opts=*/0x200000000200, /*chdir=*/1, /*size=*/0x5548, /*img=*/0x200000000bc0);
return 0;
}
Reply: Based on the analysis of the bug report and the kernel source code, here is the assessment of the bug and its security impact:
### Bug Explanation
The bug is located in the error handling path of `f2fs_fill_super()` during the mount process of an f2fs filesystem.
When the kernel attempts to mount the filesystem, it calls `f2fs_get_valid_checkpoint()` to read and validate the filesystem's checkpoint blocks. This function uses `f2fs_get_meta_folio()` to read the blocks, which populates the page cache of the special `sbi->meta_inode`.
If the checkpoint blocks are corrupted (e.g., they have an invalid CRC, as seen in the bug report logs), `f2fs_get_valid_checkpoint()` returns an error. The error handling path in `f2fs_fill_super()` then jumps to the `free_meta_inode` label and calls `iput(sbi->meta_inode)` to free the inode.
However, the error path fails to call `truncate_inode_pages_final(META_MAPPING(sbi))` before dropping the inode reference. Consequently, the `iput()` call triggers the inode eviction process (`evict()`), which eventually calls `clear_inode()`. Because the invalid checkpoint blocks were left behind in the inode's page cache, `clear_inode()` hits the `BUG_ON(inode->i_data.nrpages)` assertion, causing a kernel panic.
### Security Impact
* **Exploitable:** **False**. The bug triggers a deterministic `BUG_ON` assertion which cleanly halts the kernel (or oopses the current process, depending on the configuration). It does not cause memory corruption, use-after-free, or alter control flow in a way that could be leveraged for privilege escalation or arbitrary code execution.
* **Denial Of Service:** **True**. Triggering the bug results in a kernel panic, leading to a complete system crash.
* **Accessible From Unprivileged Processes / User Namespaces:** **False**. The `f2fs` filesystem does not have the `FS_USERNS_MOUNT` flag. Therefore, it cannot be mounted by unprivileged users, even within a new user namespace. Mounting it requires `CAP_SYS_ADMIN` in the initial user namespace.
* **Malicious Filesystem / Peripheral Trigger:** **True**. The bug is triggered by parsing a maliciously crafted f2fs image. While an unprivileged user cannot mount it directly, the bug poses a significant risk in Desktop or Mobile environments that are configured to auto-mount external media (like USB drives). An attacker could plug in a malicious USB drive to crash the system.
* **Network / Remote / VM Triggers:** **False**. The bug is strictly tied to the local filesystem mount path and cannot be triggered over the network or by a VM guest (unless the host is explicitly configured to mount an untrusted f2fs image provided by the guest).
|
| 6/2 |
2026/05/24 04:14 |
llm |
expert |
1m
Model:
Tokens:
input: 14726
output: 37
thoughts: 66
Thoughts: Here's my analysis of the situation:
**Investigating a Kernel Bug Report**
Alright, a kernel panic, specifically a `kernel BUG at fs/inode.c:782!` - that's not ideal. The stack trace points to `clear_inode+0x19a/0x1c0 fs/inode.c:782`. My first step, naturally, is to dive straight into the code at that line. Time to pull up `fs/inode.c` and zero in on line 782. Let's see what `clear_inode` is doing and what could be causing a problem there. This function is critical for inode management, so this could be an interesting one.
|
| 7/2 |
2026/05/24 04:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/inode.c IncludeLines:true Name:clear_inode]
Results: map[SourceCode: 774: void clear_inode(struct inode *inode)
775: {
776: /*
777: * We have to cycle the i_pages lock here because reclaim can be in the
778: * process of removing the last page (in __filemap_remove_folio())
779: * and we must not free the mapping under it.
780: */
781: xa_lock_irq(&inode->i_data.i_pages);
782: BUG_ON(inode->i_data.nrpages);
783: /*
784: * Almost always, mapping_empty(&inode->i_data) here; but there are
785: * two known and long-standing ways in which nodes may get left behind
786: * (when deep radix-tree node allocation failed partway; or when THP
787: * collapse_file() failed). Until those two known cases are cleaned up,
788: * or a cleanup function is called here, do not BUG_ON(!mapping_empty),
789: * nor even WARN_ON(!mapping_empty).
790: */
791: xa_unlock_irq(&inode->i_data.i_pages);
792: BUG_ON(!list_empty(&inode->i_data.i_private_list));
793: BUG_ON(!(inode_state_read_once(inode) & I_FREEING));
794: BUG_ON(inode_state_read_once(inode) & I_CLEAR);
795: BUG_ON(!list_empty(&inode->i_wb_list));
796: /* don't need i_lock here, no concurrent mods to i_state */
797: inode_state_assign_raw(inode, I_FREEING | I_CLEAR);
798: }
SourceFile:fs/inode.c]
|
| 8/2 |
2026/05/24 04:15 |
llm |
expert |
0m
Model:
Tokens:
input: 15505
output: 45
thoughts: 320
Thoughts: **Debugging a F2FS Mount Issue**
Okay, so I've got a `BUG_ON` in `clear_inode()` and it looks like the culprit is this `inode->i_data.nrpages` check. Not good. I need to figure out how this is happening. The trace is pointing me toward `evict` in `fs/inode.c`, then through `f2fs_fill_super` at `fs/f2fs/super.c:5147` during the mount process, specifically `get_tree_bdev_flags`, `vfs_get_tree`, and all the mount calls. My hunch is that there's a problem during the F2FS superblock filling. I need to take a close look at `f2fs_fill_super` to see what's going on with the inode initialization or potentially, some kind of corruption during the mount sequence leading to this issue. Let's start there.
|
| 9/2 |
2026/05/24 04:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/f2fs/super.c IncludeLines:true Name:f2fs_fill_super]
Results: map[SourceCode:4873: static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc)
4874: {
4875: struct f2fs_fs_context *ctx = fc->fs_private;
4876: struct f2fs_sb_info *sbi;
4877: struct f2fs_super_block *raw_super;
4878: struct inode *root;
4879: int err;
4880: bool skip_recovery = false, need_fsck = false;
4881: int recovery, i, valid_super_block;
4882: struct curseg_info *seg_i;
4883: int retry_cnt = 1;
4884: #ifdef CONFIG_QUOTA
4885: bool quota_enabled = false;
4886: #endif
4887:
4888: try_onemore:
4889: err = -EINVAL;
4890: raw_super = NULL;
4891: valid_super_block = -1;
4892: recovery = 0;
4893:
4894: /* allocate memory for f2fs-specific super block info */
4895: sbi = kzalloc(sizeof(struct f2fs_sb_info), GFP_KERNEL);
4896: if (!sbi)
4897: return -ENOMEM;
4898:
4899: sbi->sb = sb;
4900:
4901: /* initialize locks within allocated memory */
4902: init_f2fs_rwsem(&sbi->gc_lock);
4903: mutex_init(&sbi->writepages);
4904: init_f2fs_rwsem(&sbi->cp_global_sem);
4905: init_f2fs_rwsem(&sbi->node_write);
4906: init_f2fs_rwsem(&sbi->node_change);
4907: spin_lock_init(&sbi->stat_lock);
4908: init_f2fs_rwsem(&sbi->cp_rwsem);
4909: init_f2fs_rwsem(&sbi->cp_enable_rwsem);
4910: init_f2fs_rwsem(&sbi->quota_sem);
4911: init_waitqueue_head(&sbi->cp_wait);
4912: spin_lock_init(&sbi->error_lock);
4913:
4914: for (i = 0; i < NR_INODE_TYPE; i++) {
4915: INIT_LIST_HEAD(&sbi->inode_list[i]);
4916: spin_lock_init(&sbi->inode_lock[i]);
4917: }
4918: mutex_init(&sbi->flush_lock);
4919:
4920: /* set a block size */
4921: if (unlikely(!sb_set_blocksize(sb, F2FS_BLKSIZE))) {
4922: f2fs_err(sbi, "unable to set blocksize");
4923: goto free_sbi;
4924: }
4925:
4926: err = read_raw_super_block(sbi, &raw_super, &valid_super_block,
4927: &recovery);
4928: if (err)
4929: goto free_sbi;
4930:
4931: sb->s_fs_info = sbi;
4932: sbi->raw_super = raw_super;
4933:
4934: INIT_WORK(&sbi->s_error_work, f2fs_record_error_work);
4935: memcpy(sbi->errors, raw_super->s_errors, MAX_F2FS_ERRORS);
4936: memcpy(sbi->stop_reason, raw_super->s_stop_reason, MAX_STOP_REASON);
4937:
4938: /* precompute checksum seed for metadata */
4939: if (f2fs_sb_has_inode_chksum(sbi))
4940: sbi->s_chksum_seed = f2fs_chksum(~0, raw_super->uuid,
4941: sizeof(raw_super->uuid));
4942:
4943: default_options(sbi, false);
4944:
4945: err = f2fs_check_opt_consistency(fc, sb);
4946: if (err)
4947: goto free_sb_buf;
4948:
4949: f2fs_apply_options(fc, sb);
4950:
4951: err = f2fs_sanity_check_options(sbi, false);
4952: if (err)
4953: goto free_options;
4954:
4955: sb->s_maxbytes = max_file_blocks(NULL) <<
4956: le32_to_cpu(raw_super->log_blocksize);
4957: sb->s_max_links = F2FS_LINK_MAX;
4958:
4959: err = f2fs_setup_casefold(sbi);
4960: if (err)
4961: goto free_options;
4962:
4963: #ifdef CONFIG_QUOTA
4964: sb->dq_op = &f2fs_quota_operations;
4965: sb->s_qcop = &f2fs_quotactl_ops;
4966: sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP | QTYPE_MASK_PRJ;
4967:
4968: if (f2fs_sb_has_quota_ino(sbi)) {
4969: for (i = 0; i < MAXQUOTAS; i++) {
4970: if (f2fs_qf_ino(sbi->sb, i))
4971: sbi->nquota_files++;
4972: }
4973: }
4974: #endif
4975:
4976: sb->s_op = &f2fs_sops;
4977: #ifdef CONFIG_FS_ENCRYPTION
4978: sb->s_cop = &f2fs_cryptops;
4979: #endif
4980: #ifdef CONFIG_FS_VERITY
4981: sb->s_vop = &f2fs_verityops;
4982: #endif
4983: sb->s_xattr = f2fs_xattr_handlers;
4984: sb->s_export_op = &f2fs_export_ops;
4985: sb->s_magic = F2FS_SUPER_MAGIC;
4986: sb->s_time_gran = 1;
4987: sb->s_flags = (sb->s_flags & ~SB_POSIXACL) |
4988: (test_opt(sbi, POSIX_ACL) ? SB_POSIXACL : 0);
4989: if (test_opt(sbi, INLINECRYPT))
4990: sb->s_flags |= SB_INLINECRYPT;
4991:
4992: if (test_opt(sbi, LAZYTIME))
4993: sb->s_flags |= SB_LAZYTIME;
4994: else
4995: sb->s_flags &= ~SB_LAZYTIME;
4996:
4997: super_set_uuid(sb, (void *) raw_super->uuid, sizeof(raw_super->uuid));
4998: super_set_sysfs_name_bdev(sb);
4999: sb->s_iflags |= SB_I_CGROUPWB;
5000:
5001: /* init f2fs-specific super block info */
5002: sbi->valid_super_block = valid_super_block;
5003:
5004: /* disallow all the data/node/meta page writes */
5005: set_sbi_flag(sbi, SBI_POR_DOING);
5006:
5007: err = f2fs_init_write_merge_io(sbi);
5008: if (err)
5009: goto free_bio_info;
5010:
5011: init_sb_info(sbi);
5012:
5013: err = f2fs_init_iostat(sbi);
5014: if (err)
5015: goto free_bio_info;
5016:
5017: err = init_percpu_info(sbi);
5018: if (err)
5019: goto free_iostat;
5020:
5021: err = f2fs_init_page_array_cache(sbi);
5022: if (err)
5023: goto free_percpu;
5024:
5025: /* get an inode for meta space */
5026: sbi->meta_inode = f2fs_iget(sb, F2FS_META_INO(sbi));
5027: if (IS_ERR(sbi->meta_inode)) {
5028: f2fs_err(sbi, "Failed to read F2FS meta data inode");
5029: err = PTR_ERR(sbi->meta_inode);
5030: goto free_page_array_cache;
5031: }
5032:
5033: err = f2fs_get_valid_checkpoint(sbi);
5034: if (err) {
5035: f2fs_err(sbi, "Failed to get valid F2FS checkpoint");
5036: goto free_meta_inode;
5037: }
5038:
5039: if (__is_set_ckpt_flags(F2FS_CKPT(sbi), CP_QUOTA_NEED_FSCK_FLAG))
5040: set_sbi_flag(sbi, SBI_QUOTA_NEED_REPAIR);
5041: if (__is_set_ckpt_flags(F2FS_CKPT(sbi), CP_DISABLED_QUICK_FLAG)) {
5042: set_sbi_flag(sbi, SBI_CP_DISABLED_QUICK);
5043: sbi->interval_time[DISABLE_TIME] = DEF_DISABLE_QUICK_INTERVAL;
5044: }
5045:
5046: if (__is_set_ckpt_flags(F2FS_CKPT(sbi), CP_FSCK_FLAG))
5047: set_sbi_flag(sbi, SBI_NEED_FSCK);
5048:
5049: /* Initialize device list */
5050: err = f2fs_scan_devices(sbi);
5051: if (err) {
5052: f2fs_err(sbi, "Failed to find devices");
5053: goto free_devices;
5054: }
5055:
5056: err = f2fs_init_post_read_wq(sbi);
5057: if (err) {
5058: f2fs_err(sbi, "Failed to initialize post read workqueue");
5059: goto free_devices;
5060: }
5061:
5062: sbi->total_valid_node_count =
5063: le32_to_cpu(sbi->ckpt->valid_node_count);
5064: percpu_counter_set(&sbi->total_valid_inode_count,
5065: le32_to_cpu(sbi->ckpt->valid_inode_count));
5066: sbi->user_block_count = le64_to_cpu(sbi->ckpt->user_block_count);
5067: sbi->total_valid_block_count =
5068: le64_to_cpu(sbi->ckpt->valid_block_count);
5069: sbi->last_valid_block_count = sbi->total_valid_block_count;
5070: sbi->reserved_blocks = 0;
5071: sbi->current_reserved_blocks = 0;
5072: limit_reserve_root(sbi);
5073: adjust_unusable_cap_perc(sbi);
5074:
5075: f2fs_init_extent_cache_info(sbi);
5076:
5077: f2fs_init_ino_entry_info(sbi);
5078:
5079: f2fs_init_fsync_node_info(sbi);
5080:
5081: /* setup checkpoint request control and start checkpoint issue thread */
5082: f2fs_init_ckpt_req_control(sbi);
5083: if (!f2fs_readonly(sb) && !test_opt(sbi, DISABLE_CHECKPOINT) &&
5084: test_opt(sbi, MERGE_CHECKPOINT)) {
5085: err = f2fs_start_ckpt_thread(sbi);
5086: if (err) {
5087: f2fs_err(sbi,
5088: "Failed to start F2FS issue_checkpoint_thread (%d)",
5089: err);
5090: goto stop_ckpt_thread;
5091: }
5092: }
5093:
5094: /* setup f2fs internal modules */
5095: err = f2fs_build_segment_manager(sbi);
5096: if (err) {
5097: f2fs_err(sbi, "Failed to initialize F2FS segment manager (%d)",
5098: err);
5099: goto free_sm;
5100: }
5101: err = f2fs_build_node_manager(sbi);
5102: if (err) {
5103: f2fs_err(sbi, "Failed to initialize F2FS node manager (%d)",
5104: err);
5105: goto free_nm;
5106: }
5107:
5108: /* For write statistics */
5109: sbi->sectors_written_start = f2fs_get_sectors_written(sbi);
5110:
5111: /* get segno of first zoned block device */
5112: sbi->first_seq_zone_segno = get_first_seq_zone_segno(sbi);
5113:
5114: sbi->reserved_pin_section = f2fs_sb_has_blkzoned(sbi) ?
5115: ZONED_PIN_SEC_REQUIRED_COUNT :
5116: GET_SEC_FROM_SEG(sbi, overprovision_segments(sbi));
5117:
5118: /* Read accumulated write IO statistics if exists */
5119: seg_i = CURSEG_I(sbi, CURSEG_HOT_NODE);
5120: if (__exist_node_summaries(sbi))
5121: sbi->kbytes_written =
5122: le64_to_cpu(seg_i->journal->info.kbytes_written);
5123:
5124: f2fs_build_gc_manager(sbi);
5125:
5126: err = f2fs_build_stats(sbi);
5127: if (err)
5128: goto free_nm;
5129:
5130: /* get an inode for node space */
5131: sbi->node_inode = f2fs_iget(sb, F2FS_NODE_INO(sbi));
5132: if (IS_ERR(sbi->node_inode)) {
5133: f2fs_err(sbi, "Failed to read node inode");
5134: err = PTR_ERR(sbi->node_inode);
5135: goto free_stats;
5136: }
5137:
5138: /* read root inode and dentry */
5139: root = f2fs_iget(sb, F2FS_ROOT_INO(sbi));
5140: if (IS_ERR(root)) {
5141: f2fs_err(sbi, "Failed to read root inode");
5142: err = PTR_ERR(root);
5143: goto free_node_inode;
5144: }
5145: if (!S_ISDIR(root->i_mode) || !root->i_blocks ||
5146: !root->i_size || !root->i_nlink) {
5147: iput(root);
5148: err = -EINVAL;
5149: goto free_node_inode;
5150: }
5151:
5152: generic_set_sb_d_ops(sb);
5153: sb->s_root = d_make_root(root); /* allocate root dentry */
5154: if (!sb->s_root) {
5155: err = -ENOMEM;
5156: goto free_node_inode;
5157: }
5158:
5159: err = f2fs_init_compress_inode(sbi);
5160: if (err)
5161: goto free_root_inode;
5162:
5163: err = f2fs_register_sysfs(sbi);
5164: if (err)
5165: goto free_compress_inode;
5166:
5167: sbi->umount_lock_holder = current;
5168: #ifdef CONFIG_QUOTA
5169: /* Enable quota usage during mount */
5170: if (f2fs_sb_has_quota_ino(sbi) && !f2fs_readonly(sb)) {
5171: err = f2fs_enable_quotas(sb);
5172: if (err)
5173: f2fs_err(sbi, "Cannot turn on quotas: error %d", err);
5174: }
5175:
5176: quota_enabled = f2fs_recover_quota_begin(sbi);
5177: #endif
5178: /* if there are any orphan inodes, free them */
5179: err = f2fs_recover_orphan_inodes(sbi);
5180: if (err)
5181: goto free_meta;
5182:
5183: if (unlikely(is_set_ckpt_flags(sbi, CP_DISABLED_FLAG))) {
5184: skip_recovery = true;
5185: goto reset_checkpoint;
5186: }
5187:
5188: /* recover fsynced data */
5189: if (!test_opt(sbi, DISABLE_ROLL_FORWARD) &&
5190: !test_opt(sbi, NORECOVERY)) {
5191: /*
5192: * mount should be failed, when device has readonly mode, and
5193: * previous checkpoint was not done by clean system shutdown.
5194: */
5195: if (f2fs_hw_is_readonly(sbi)) {
5196: if (!is_set_ckpt_flags(sbi, CP_UMOUNT_FLAG)) {
5197: err = f2fs_recover_fsync_data(sbi, true);
5198: if (err > 0) {
5199: err = -EROFS;
5200: f2fs_err(sbi, "Need to recover fsync data, but "
5201: "write access unavailable, please try "
5202: "mount w/ disable_roll_forward or norecovery");
5203: }
5204: if (err < 0)
5205: goto free_meta;
5206: }
5207: f2fs_info(sbi, "write access unavailable, skipping recovery");
5208: goto reset_checkpoint;
5209: }
5210:
5211: if (need_fsck)
5212: set_sbi_flag(sbi, SBI_NEED_FSCK);
5213:
5214: if (skip_recovery)
5215: goto reset_checkpoint;
5216:
5217: err = f2fs_recover_fsync_data(sbi, false);
5218: if (err < 0) {
5219: if (err != -ENOMEM)
5220: skip_recovery = true;
5221: need_fsck = true;
5222: f2fs_err(sbi, "Cannot recover all fsync data errno=%d",
5223: err);
5224: goto free_meta;
5225: }
5226: } else {
5227: err = f2fs_recover_fsync_data(sbi, true);
5228: if (err > 0) {
5229: if (!f2fs_readonly(sb)) {
5230: f2fs_err(sbi, "Need to recover fsync data");
5231: err = -EINVAL;
5232: goto free_meta;
5233: } else {
5234: f2fs_info(sbi, "drop all fsynced data");
5235: err = 0;
5236: }
5237: }
5238: }
5239:
5240: reset_checkpoint:
5241: #ifdef CONFIG_QUOTA
5242: f2fs_recover_quota_end(sbi, quota_enabled);
5243: #endif
5244: /*
5245: * If the f2fs is not readonly and fsync data recovery succeeds,
5246: * write pointer consistency of cursegs and other zones are already
5247: * checked and fixed during recovery. However, if recovery fails,
5248: * write pointers are left untouched, and retry-mount should check
5249: * them here.
5250: */
5251: if (skip_recovery)
5252: err = f2fs_check_and_fix_write_pointer(sbi);
5253: if (err)
5254: goto free_meta;
5255:
5256: /* f2fs_recover_fsync_data() cleared this already */
5257: clear_sbi_flag(sbi, SBI_POR_DOING);
5258:
5259: err = f2fs_init_inmem_curseg(sbi);
5260: if (err)
5261: goto sync_free_meta;
5262:
5263: if (test_opt(sbi, DISABLE_CHECKPOINT))
5264: err = f2fs_disable_checkpoint(sbi);
5265: else if (is_set_ckpt_flags(sbi, CP_DISABLED_FLAG))
5266: err = f2fs_enable_checkpoint(sbi);
5267: if (err)
5268: goto sync_free_meta;
5269:
5270: /*
5271: * If filesystem is not mounted as read-only then
5272: * do start the gc_thread.
5273: */
5274: if ((F2FS_OPTION(sbi).bggc_mode != BGGC_MODE_OFF ||
5275: test_opt(sbi, GC_MERGE)) && !f2fs_readonly(sb)) {
5276: /* After POR, we can run background GC thread.*/
5277: err = f2fs_start_gc_thread(sbi);
5278: if (err)
5279: goto sync_free_meta;
5280: }
5281:
5282: /* recover broken superblock */
5283: if (recovery) {
5284: err = f2fs_commit_super(sbi, true);
5285: f2fs_info(sbi, "Try to recover %dth superblock, ret: %d",
5286: sbi->valid_super_block ? 1 : 2, err);
5287: }
5288:
5289: f2fs_join_shrinker(sbi);
5290:
5291: f2fs_tuning_parameters(sbi);
5292:
5293: f2fs_notice(sbi, "Mounted with checkpoint version = %llx",
5294: cur_cp_version(F2FS_CKPT(sbi)));
5295: f2fs_update_time(sbi, CP_TIME);
5296: f2fs_update_time(sbi, REQ_TIME);
5297: clear_sbi_flag(sbi, SBI_CP_DISABLED_QUICK);
5298:
5299: sbi->umount_lock_holder = NULL;
5300: return 0;
5301:
5302: sync_free_meta:
5303: /* safe to flush all the data */
5304: sync_filesystem(sbi->sb);
5305: retry_cnt = 0;
5306:
5307: free_meta:
5308: #ifdef CONFIG_QUOTA
5309: f2fs_truncate_quota_inode_pages(sb);
5310: if (f2fs_sb_has_quota_ino(sbi) && !f2fs_readonly(sb))
5311: f2fs_quota_off_umount(sbi->sb);
5312: #endif
5313: /*
5314: * Some dirty meta pages can be produced by f2fs_recover_orphan_inodes()
5315: * failed by EIO. Then, iput(node_inode) can trigger balance_fs_bg()
5316: * followed by f2fs_write_checkpoint() through f2fs_write_node_pages(), which
5317: * falls into an infinite loop in f2fs_sync_meta_pages().
5318: */
5319: truncate_inode_pages_final(META_MAPPING(sbi));
5320: /* evict some inodes being cached by GC */
5321: evict_inodes(sb);
5322: f2fs_unregister_sysfs(sbi);
5323: free_compress_inode:
5324: f2fs_destroy_compress_inode(sbi);
5325: free_root_inode:
5326: dput(sb->s_root);
5327: sb->s_root = NULL;
5328: free_node_inode:
5329: f2fs_release_ino_entry(sbi, true);
5330: truncate_inode_pages_final(NODE_MAPPING(sbi));
5331: iput(sbi->node_inode);
5332: sbi->node_inode = NULL;
5333: free_stats:
5334: f2fs_destroy_stats(sbi);
5335: free_nm:
5336: /* stop discard thread before destroying node manager */
5337: f2fs_stop_discard_thread(sbi);
5338: f2fs_destroy_node_manager(sbi);
5339: free_sm:
5340: f2fs_destroy_segment_manager(sbi);
5341: stop_ckpt_thread:
5342: f2fs_stop_ckpt_thread(sbi);
5343: /* flush s_error_work before sbi destroy */
5344: flush_work(&sbi->s_error_work);
5345: f2fs_destroy_post_read_wq(sbi);
5346: free_devices:
5347: destroy_device_list(sbi);
5348: kvfree(sbi->ckpt);
5349: free_meta_inode:
5350: make_bad_inode(sbi->meta_inode);
5351: iput(sbi->meta_inode);
5352: sbi->meta_inode = NULL;
5353: free_page_array_cache:
5354: f2fs_destroy_page_array_cache(sbi);
5355: free_percpu:
5356: destroy_percpu_info(sbi);
5357: free_iostat:
5358: f2fs_destroy_iostat(sbi);
5359: free_bio_info:
5360: for (i = 0; i < NR_PAGE_TYPE; i++)
5361: kfree(sbi->write_io[i]);
5362:
5363: #if IS_ENABLED(CONFIG_UNICODE)
5364: utf8_unload(sb->s_encoding);
5365: sb->s_encoding = NULL;
5366: #endif
5367: free_options:
5368: #ifdef CONFIG_QUOTA
5369: for (i = 0; i < MAXQUOTAS; i++)
5370: kfree(F2FS_OPTION(sbi).s_qf_names[i]);
5371: #endif
5372: /* no need to free dummy_enc_policy, we just keep it in ctx when failed */
5373: swap(F2FS_CTX_INFO(ctx).dummy_enc_policy, F2FS_OPTION(sbi).dummy_enc_policy);
5374: free_sb_buf:
5375: kfree(raw_super);
5376: free_sbi:
5377: kfree(sbi);
5378: sb->s_fs_info = NULL;
5379:
5380: /* give only one another chance */
5381: if (retry_cnt > 0 && skip_recovery) {
5382: retry_cnt--;
5383: shrink_dcache_sb(sb);
5384: goto try_onemore;
5385: }
5386: return err;
5387: }
SourceFile:fs/f2fs/super.c]
|
| 10/2 |
2026/05/24 04:15 |
llm |
expert |
0m
Model:
Tokens:
input: 24511
output: 20
thoughts: 280
Thoughts: **Debugging a F2FS Filesystem Bug**
Okay, so I've got a `BUG_ON(inode->i_data.nrpages);` in `clear_inode()` that's been triggered. The call trace points to `evict` being called from `f2fs_fill_super`. I need to figure out how this is happening. Let's look at the error path in `f2fs_fill_super`. I see code that calls `make_bad_inode()`, then potentially calls `iput()` on `sbi->meta_inode`, `sbi->node_inode`, or `root`. This looks like where the inode might be getting messed up.
Now, let's examine the bug report output. I see a few potentially relevant lines: "Wrong SSA boundary," "Can't find valid F2FS filesystem," "Image doesn't support compression," and "invalid crc value." That last line is definitely concerning.
Wait a minute... "Image doesn't support compression" stands out. That's interesting. I need to find the source code location where that message is printed. Perhaps that will shed some light on the issue.
|
| 11/2 |
2026/05/24 04:15 |
tool |
grepper |
0m
Args: map[Expression:Image doesn't support compression]
Results: map[Output:fs/f2fs/super.c=1361=static int f2fs_check_compression(struct fs_context *fc,
--
fs/f2fs/super.c-1371- ctx_test_opt(ctx, F2FS_MOUNT_COMPRESS_CACHE))
fs/f2fs/super.c:1372: f2fs_info(sbi, "Image doesn't support compression");
fs/f2fs/super.c-1373- clear_compression_spec(ctx);
]
|
| 12/2 |
2026/05/24 04:16 |
llm |
expert |
0m
Model:
Tokens:
input: 25205
output: 45
thoughts: 13
Thoughts: Okay, let's dive into this. Here's my take on `f2fs_check_compression` in a first-person expert perspective:
**Analyzing f2fs_check_compression**
Alright, let's take a look at `f2fs_check_compression`. Right off the bat, I know this function is critical. Compression within F2FS is a game-changer for storage density and I/O performance, but it also introduces a whole new level of complexity that needs rigorous checking. I expect this function to be the bedrock for verifying the integrity of compressed data within the filesystem. It should have a robust set of checks designed to catch any corruption that might arise from hardware errors, software bugs, or even unexpected corner cases in the compression algorithms themselves.
Iβll be looking for how it handles:
* **Metadata Consistency:** Does it verify the integrity of the compression metadata, like the compression algorithm used, the original and compressed block sizes, and any internal offsets? This is vital, because bad metadata is a guaranteed path to data loss.
* **Data Integrity of Compressed Blocks:** I want to see how the actual decompressed data is validated. Does it do things like checksum verification after decompression? Redundant verification? This should be robust.
* **Error Handling:** It should gracefully handle different types of corruption. For example, it needs to know how to deal with compressed blocks that are truncated, corrupted, or even if decompression fails altogether for some reason.
* **Performance Considerations:** Ideally, this function is designed to run efficiently. I'm hoping it is designed not to cripple performance, especially during online checks when the filesystem is mounted and running. This might be a trade-off.
* **Integration with the F2FS Architecture:** The function has to seamlessly work with the rest of the F2FS code, especially the block allocation and deallocation systems. Any inconsistencies here and the whole filesystem is at risk.
I need to see how well it addresses these fundamental issues. The depth and robustness of this function will dictate a lot about the overall reliability of F2FS when compression is enabled. A thorough examination is absolutely necessary. This is where the rubber meets the road.
|
| 13/2 |
2026/05/24 04:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/f2fs/super.c IncludeLines:true Name:f2fs_check_compression]
Results: map[SourceCode:1361: static int f2fs_check_compression(struct fs_context *fc,
1362: struct super_block *sb)
1363: {
1364: #ifdef CONFIG_F2FS_FS_COMPRESSION
1365: struct f2fs_fs_context *ctx = fc->fs_private;
1366: struct f2fs_sb_info *sbi = F2FS_SB(sb);
1367: int i, cnt;
1368:
1369: if (!f2fs_sb_has_compression(sbi)) {
1370: if (test_compression_spec(ctx->spec_mask) ||
1371: ctx_test_opt(ctx, F2FS_MOUNT_COMPRESS_CACHE))
1372: f2fs_info(sbi, "Image doesn't support compression");
1373: clear_compression_spec(ctx);
1374: ctx->opt_mask &= ~BIT(F2FS_MOUNT_COMPRESS_CACHE);
1375: return 0;
1376: }
1377: if (ctx->spec_mask & F2FS_SPEC_compress_extension) {
1378: cnt = F2FS_CTX_INFO(ctx).compress_ext_cnt;
1379: for (i = 0; i < F2FS_CTX_INFO(ctx).compress_ext_cnt; i++) {
1380: if (is_compress_extension_exist(&F2FS_OPTION(sbi),
1381: F2FS_CTX_INFO(ctx).extensions[i], true)) {
1382: F2FS_CTX_INFO(ctx).extensions[i][0] = '\0';
1383: cnt--;
1384: }
1385: }
1386: if (F2FS_OPTION(sbi).compress_ext_cnt + cnt > COMPRESS_EXT_NUM) {
1387: f2fs_err(sbi, "invalid extension length/number");
1388: return -EINVAL;
1389: }
1390: }
1391: if (ctx->spec_mask & F2FS_SPEC_nocompress_extension) {
1392: cnt = F2FS_CTX_INFO(ctx).nocompress_ext_cnt;
1393: for (i = 0; i < F2FS_CTX_INFO(ctx).nocompress_ext_cnt; i++) {
1394: if (is_compress_extension_exist(&F2FS_OPTION(sbi),
1395: F2FS_CTX_INFO(ctx).noextensions[i], false)) {
1396: F2FS_CTX_INFO(ctx).noextensions[i][0] = '\0';
1397: cnt--;
1398: }
1399: }
1400: if (F2FS_OPTION(sbi).nocompress_ext_cnt + cnt > COMPRESS_EXT_NUM) {
1401: f2fs_err(sbi, "invalid noextension length/number");
1402: return -EINVAL;
1403: }
1404: }
1405:
1406: if (f2fs_test_compress_extension(F2FS_CTX_INFO(ctx).noextensions,
1407: F2FS_CTX_INFO(ctx).nocompress_ext_cnt,
1408: F2FS_CTX_INFO(ctx).extensions,
1409: F2FS_CTX_INFO(ctx).compress_ext_cnt)) {
1410: f2fs_err(sbi, "new noextensions conflicts with new extensions");
1411: return -EINVAL;
1412: }
1413: if (f2fs_test_compress_extension(F2FS_CTX_INFO(ctx).noextensions,
1414: F2FS_CTX_INFO(ctx).nocompress_ext_cnt,
1415: F2FS_OPTION(sbi).extensions,
1416: F2FS_OPTION(sbi).compress_ext_cnt)) {
1417: f2fs_err(sbi, "new noextensions conflicts with old extensions");
1418: return -EINVAL;
1419: }
1420: if (f2fs_test_compress_extension(F2FS_OPTION(sbi).noextensions,
1421: F2FS_OPTION(sbi).nocompress_ext_cnt,
1422: F2FS_CTX_INFO(ctx).extensions,
1423: F2FS_CTX_INFO(ctx).compress_ext_cnt)) {
1424: f2fs_err(sbi, "new extensions conflicts with old noextensions");
1425: return -EINVAL;
1426: }
1427: #endif
1428: return 0;
1429: }
SourceFile:fs/f2fs/super.c]
|
| 14/2 |
2026/05/24 04:16 |
llm |
expert |
0m
Model:
Tokens:
input: 27073
output: 17
thoughts: 114
Thoughts: **Analyzing the F2FS Error Log**
Okay, so I'm looking at this bug report, and the F2FS output immediately grabs my attention. Specifically, that "invalid crc value" message. That's a critical error signal in F2FS, indicating serious corruption, usually at the superblock level or within metadata. It's often a precursor to complete filesystem failure. I see a few other issues, too: "Wrong SSA boundary", "Can't find valid F2FS filesystem" on the second superblock, and "Image doesn't support compression". While these might be related or independent, the CRC error is the priority. I need to figure out the root cause, so my immediate focus is on tracing that CRC error through the F2FS code. Where is it being generated and how? Let's dive in.
|
| 15/2 |
2026/05/24 04:16 |
tool |
grepper |
0m
Args: map[Expression:invalid crc value]
Results: map[Output:fs/f2fs/checkpoint.c=845=static int get_checkpoint_version(struct f2fs_sb_info *sbi, block_t cp_addr,
--
fs/f2fs/checkpoint.c-868- f2fs_folio_put(*cp_folio, true);
fs/f2fs/checkpoint.c:869: f2fs_warn(sbi, "invalid crc value");
fs/f2fs/checkpoint.c-870- return -EINVAL;
]
|
| 16/2 |
2026/05/24 04:16 |
llm |
expert |
0m
Model:
Tokens:
input: 27515
output: 43
thoughts: 11
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deep in my domain of expertise:
**Checking the Checkpoint Version**
Alright, so the first thing on my mind is `get_checkpoint_version`. I see that. It's immediately clear that I need to establish the version of the checkpoint I'm dealing with. Why? Well, depending on the framework, the checkpoint format, the model architecture, and the specific version of the code that created it, the interpretation and loading of the checkpoint can vary *wildly*. Different versions could introduce different serialization methods, new metadata structures, incompatible parameter names, or even completely change how the model's internal states are stored. Without knowing the version, I'm essentially flying blind. I could end up with a model that's subtly, or catastrophically, misloaded. This is foundational; I need this information before doing anything else that relies on the checkpoint. It's the starting point for compatibility assessment and subsequent loading strategies. I'm essentially treating this as the first command. Knowing this is critical to ensuring proper execution further down the line. It's the first bit of information I need.
|
| 17/2 |
2026/05/24 04:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/f2fs/checkpoint.c IncludeLines:true Name:get_checkpoint_version]
Results: map[SourceCode: 845: static int get_checkpoint_version(struct f2fs_sb_info *sbi, block_t cp_addr,
846: struct f2fs_checkpoint **cp_block, struct folio **cp_folio,
847: unsigned long long *version)
848: {
849: size_t crc_offset = 0;
850: __u32 crc;
851:
852: *cp_folio = f2fs_get_meta_folio(sbi, cp_addr);
853: if (IS_ERR(*cp_folio))
854: return PTR_ERR(*cp_folio);
855:
856: *cp_block = folio_address(*cp_folio);
857:
858: crc_offset = le32_to_cpu((*cp_block)->checksum_offset);
859: if (crc_offset < CP_MIN_CHKSUM_OFFSET ||
860: crc_offset > CP_CHKSUM_OFFSET) {
861: f2fs_folio_put(*cp_folio, true);
862: f2fs_warn(sbi, "invalid crc_offset: %zu", crc_offset);
863: return -EINVAL;
864: }
865:
866: crc = f2fs_checkpoint_chksum(*cp_block);
867: if (crc != cur_cp_crc(*cp_block)) {
868: f2fs_folio_put(*cp_folio, true);
869: f2fs_warn(sbi, "invalid crc value");
870: return -EINVAL;
871: }
872:
873: *version = cur_cp_version(*cp_block);
874: return 0;
875: }
SourceFile:fs/f2fs/checkpoint.c]
|
| 18/2 |
2026/05/24 04:16 |
llm |
expert |
0m
Model:
Tokens:
input: 28358
output: 47
thoughts: 29
Thoughts: Here's my analysis of this situation:
**Investigating the Checkpoint Version Error**
Okay, so `get_checkpoint_version` is returning `-EINVAL`. That's an immediate red flag. A negative `EINVAL` indicates an invalid argument, so something's gone wrong in the input or processing within that function. I need to trace this back to the source.
The interesting part is *where* `get_checkpoint_version` is being called from. It's inside `f2fs_get_valid_checkpoint`. That context is crucial. `f2fs_get_valid_checkpoint` is responsible for, well, getting a valid checkpoint. It sets the stage for everything that follows. I have to figure out what parameter being passed to `get_checkpoint_version` is invalid, as the error clearly points there. Is there a case that `f2fs_get_valid_checkpoint` isn't handling properly, leading to the bad argument? I'll probably need to step through the code execution in a debugger, starting from the point where `f2fs_get_valid_checkpoint` is called, and track exactly how the argument in question is populated, and its various transformations. Hopefully, that will give me some insight.
|
| 19/2 |
2026/05/24 04:16 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/f2fs/checkpoint.c IncludeLines:true Name:f2fs_get_valid_checkpoint]
Results: map[SourceCode: 918: int f2fs_get_valid_checkpoint(struct f2fs_sb_info *sbi)
919: {
920: struct f2fs_checkpoint *cp_block;
921: struct f2fs_super_block *fsb = sbi->raw_super;
922: struct folio *cp1, *cp2, *cur_folio;
923: unsigned long blk_size = sbi->blocksize;
924: unsigned long long cp1_version = 0, cp2_version = 0;
925: unsigned long long cp_start_blk_no;
926: unsigned int cp_blks = 1 + __cp_payload(sbi);
927: block_t cp_blk_no;
928: int i;
929: int err;
930:
931: sbi->ckpt = f2fs_kvzalloc(sbi, array_size(blk_size, cp_blks),
932: GFP_KERNEL);
933: if (!sbi->ckpt)
934: return -ENOMEM;
935: /*
936: * Finding out valid cp block involves read both
937: * sets( cp pack 1 and cp pack 2)
938: */
939: cp_start_blk_no = le32_to_cpu(fsb->cp_blkaddr);
940: cp1 = validate_checkpoint(sbi, cp_start_blk_no, &cp1_version);
941:
942: /* The second checkpoint pack should start at the next segment */
943: cp_start_blk_no += ((unsigned long long)1) <<
944: le32_to_cpu(fsb->log_blocks_per_seg);
945: cp2 = validate_checkpoint(sbi, cp_start_blk_no, &cp2_version);
946:
947: if (cp1 && cp2) {
948: if (ver_after(cp2_version, cp1_version))
949: cur_folio = cp2;
950: else
951: cur_folio = cp1;
952: } else if (cp1) {
953: cur_folio = cp1;
954: } else if (cp2) {
955: cur_folio = cp2;
956: } else {
957: err = -EFSCORRUPTED;
958: goto fail_no_cp;
959: }
960:
961: cp_block = folio_address(cur_folio);
962: memcpy(sbi->ckpt, cp_block, blk_size);
963:
964: if (cur_folio == cp1)
965: sbi->cur_cp_pack = 1;
966: else
967: sbi->cur_cp_pack = 2;
968:
969: /* Sanity checking of checkpoint */
970: if (f2fs_sanity_check_ckpt(sbi)) {
971: err = -EFSCORRUPTED;
972: goto free_fail_no_cp;
973: }
974:
975: if (cp_blks <= 1)
976: goto done;
977:
978: cp_blk_no = le32_to_cpu(fsb->cp_blkaddr);
979: if (cur_folio == cp2)
980: cp_blk_no += BIT(le32_to_cpu(fsb->log_blocks_per_seg));
981:
982: for (i = 1; i < cp_blks; i++) {
983: void *sit_bitmap_ptr;
984: unsigned char *ckpt = (unsigned char *)sbi->ckpt;
985:
986: cur_folio = f2fs_get_meta_folio(sbi, cp_blk_no + i);
987: if (IS_ERR(cur_folio)) {
988: err = PTR_ERR(cur_folio);
989: goto free_fail_no_cp;
990: }
991: sit_bitmap_ptr = folio_address(cur_folio);
992: memcpy(ckpt + i * blk_size, sit_bitmap_ptr, blk_size);
993: f2fs_folio_put(cur_folio, true);
994: }
995: done:
996: f2fs_folio_put(cp1, true);
997: f2fs_folio_put(cp2, true);
998: return 0;
999:
1000: free_fail_no_cp:
1001: f2fs_folio_put(cp1, true);
1002: f2fs_folio_put(cp2, true);
1003: fail_no_cp:
1004: kvfree(sbi->ckpt);
1005: return err;
1006: }
SourceFile:fs/f2fs/checkpoint.c]
|
| 20/2 |
2026/05/24 04:16 |
llm |
expert |
1m
Model:
Tokens:
input: 30186
output: 43
thoughts: 404
Thoughts: **F2FS Meta Inode Handling - Potential Issue**
Okay, let's break this down. It looks like `f2fs_get_valid_checkpoint` is called within `f2fs_fill_super`. If that call fails, it jumps directly to `free_meta_inode`. Now, `free_meta_inode` does a few things I need to pay attention to:
```c
make_bad_inode(sbi->meta_inode);
iput(sbi->meta_inode);
sbi->meta_inode = NULL;
```
Interesting. `iput` is the key here. We know that `iput` calls `evict`, and `evict` calls `clear_inode`. Inside `clear_inode`, there's a `BUG_ON(inode->i_data.nrpages);`. That's the red flag. It's telling me that this `sbi->meta_inode` is somehow *supposed* to be a clean inode without any pages. But what I see here is that this is not being correctly done. That means, `sbi->meta_inode` is, potentially, not being properly cleaned up when the checkpoint fails, and it *still* has pages in its page cache!
Now, let's see where `sbi->meta_inode` is created and populated. I need to understand its lifecycle.
```c
/* get an inode for meta space */
sbi->meta_inode = f2fs_iget(sb, F2FS_META_INO(sbi));
if (IS_ERR(sbi->meta_inode)) {
f2fs_err(sbi, "Failed to read F2FS meta data inode");
err = PTR_ERR(sbi->meta_inode);
goto free_page_array_cache;
}
```
Wait a second. It's using `f2fs_iget` to get `F2FS_META_INO(sbi)`. But `f2fs_iget` is designed for normal inodes, right? Now, I remember that `F2FS_META_INO` is a special inode number - that must be what's causing `f2fs_iget` to act differently. But is the different handling correct to ensure that no pages are associated with it on a failed checkpoint? This needs investigation. The inconsistency in using `f2fs_iget` is something I need to keep in mind. I need to trace through `f2fs_iget` and figure out how it handles these special inodes and confirm that no page caching occurs in the meta inode creation.
|
| 21/2 |
2026/05/24 04:17 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/f2fs/inode.c IncludeLines:true Name:f2fs_iget]
Results: map[SourceCode: 568: struct inode *f2fs_iget(struct super_block *sb, unsigned long ino)
569: {
570: struct f2fs_sb_info *sbi = F2FS_SB(sb);
571: struct inode *inode;
572: int ret = 0;
573:
574: inode = iget_locked(sb, ino);
575: if (!inode)
576: return ERR_PTR(-ENOMEM);
577:
578: if (!(inode_state_read_once(inode) & I_NEW)) {
579: if (is_meta_ino(sbi, ino)) {
580: f2fs_err(sbi, "inaccessible inode: %lu, run fsck to repair", ino);
581: set_sbi_flag(sbi, SBI_NEED_FSCK);
582: ret = -EFSCORRUPTED;
583: trace_f2fs_iget_exit(inode, ret);
584: iput(inode);
585: f2fs_handle_error(sbi, ERROR_CORRUPTED_INODE);
586: return ERR_PTR(ret);
587: }
588:
589: trace_f2fs_iget(inode);
590: return inode;
591: }
592:
593: if (is_meta_ino(sbi, ino))
594: goto make_now;
595:
596: ret = do_read_inode(inode);
597: if (ret)
598: goto bad_inode;
599: make_now:
600: if (ino == F2FS_NODE_INO(sbi)) {
601: inode->i_mapping->a_ops = &f2fs_node_aops;
602: mapping_set_gfp_mask(inode->i_mapping, GFP_NOFS);
603: } else if (ino == F2FS_META_INO(sbi)) {
604: inode->i_mapping->a_ops = &f2fs_meta_aops;
605: mapping_set_gfp_mask(inode->i_mapping, GFP_NOFS);
606: } else if (ino == F2FS_COMPRESS_INO(sbi)) {
607: #ifdef CONFIG_F2FS_FS_COMPRESSION
608: inode->i_mapping->a_ops = &f2fs_compress_aops;
609: /*
610: * generic_error_remove_folio only truncates pages of regular
611: * inode
612: */
613: inode->i_mode |= S_IFREG;
614: #endif
615: mapping_set_gfp_mask(inode->i_mapping,
616: GFP_NOFS | __GFP_HIGHMEM | __GFP_MOVABLE);
617: } else if (S_ISREG(inode->i_mode)) {
618: inode->i_op = &f2fs_file_inode_operations;
619: inode->i_fop = &f2fs_file_operations;
620: inode->i_mapping->a_ops = &f2fs_dblock_aops;
621: } else if (S_ISDIR(inode->i_mode)) {
622: inode->i_op = &f2fs_dir_inode_operations;
623: inode->i_fop = &f2fs_dir_operations;
624: inode->i_mapping->a_ops = &f2fs_dblock_aops;
625: mapping_set_gfp_mask(inode->i_mapping, GFP_NOFS);
626: } else if (S_ISLNK(inode->i_mode)) {
627: if (file_is_encrypt(inode))
628: inode->i_op = &f2fs_encrypted_symlink_inode_operations;
629: else
630: inode->i_op = &f2fs_symlink_inode_operations;
631: inode_nohighmem(inode);
632: inode->i_mapping->a_ops = &f2fs_dblock_aops;
633: } else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
634: S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
635: inode->i_op = &f2fs_special_inode_operations;
636: init_special_inode(inode, inode->i_mode, inode->i_rdev);
637: } else {
638: ret = -EIO;
639: goto bad_inode;
640: }
641: f2fs_set_inode_flags(inode);
642:
643: unlock_new_inode(inode);
644: trace_f2fs_iget(inode);
645: return inode;
646:
647: bad_inode:
648: f2fs_inode_synced(inode);
649: iget_failed(inode);
650: trace_f2fs_iget_exit(inode, ret);
651: return ERR_PTR(ret);
652: }
SourceFile:fs/f2fs/inode.c]
|
| 22/2 |
2026/05/24 04:17 |
llm |
expert |
0m
Model:
Tokens:
input: 32763
output: 41
thoughts: 80
Thoughts: Okay, here's my understanding, assuming I'm an expert in file systems and F2FS in particular:
**Analyzing the F2FS Inode Acquisition and Checkpoint Validation**
Alright, let's break this down. From what I'm seeing, `f2fs_iget` is called and, specifically, it's returning a fresh inode for `F2FS_META_INO(sbi)`. This signals the retrieval of a metadata inode β probably the critical one that relates to the superblock information. I'd need to look at the implementation to be certain, but that's the likely scenario.
Then, immediately following this inode retrieval, we're invoking `f2fs_get_valid_checkpoint`. This is the important part because it indicates the intention to read and validate the on-disk checkpoint data. From what I am seeing the code will be executing something like:
```c
cp1 = validate_checkpoint(sbi, cp_start_blk_no, &cp1_version);
```
So, the focus here is to use `validate_checkpoint`, to read the checkpoint blocks and validate them. We're providing the superblock information (`sbi`), the starting block number (`cp_start_blk_no`) for the checkpoint data, and a variable to store the checkpoint version. I can guess, that `validate_checkpoint` is probably going to be a key function that does checksumming, version checks, and potentially some sanity checks on the checkpoint data structures to ensure the file system's integrity. Essentially, the code is confirming that the file system metadata is consistent with what we expect to find, from a previous checkpoint.
|
| 23/2 |
2026/05/24 04:17 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/f2fs/checkpoint.c IncludeLines:true Name:validate_checkpoint]
Results: map[SourceCode: 877: static struct folio *validate_checkpoint(struct f2fs_sb_info *sbi,
878: block_t cp_addr, unsigned long long *version)
879: {
880: struct folio *cp_folio_1 = NULL, *cp_folio_2 = NULL;
881: struct f2fs_checkpoint *cp_block = NULL;
882: unsigned long long cur_version = 0, pre_version = 0;
883: unsigned int cp_blocks;
884: int err;
885:
886: err = get_checkpoint_version(sbi, cp_addr, &cp_block,
887: &cp_folio_1, version);
888: if (err)
889: return NULL;
890:
891: cp_blocks = le32_to_cpu(cp_block->cp_pack_total_block_count);
892:
893: if (cp_blocks > BLKS_PER_SEG(sbi) || cp_blocks <= F2FS_CP_PACKS) {
894: f2fs_warn(sbi, "invalid cp_pack_total_block_count:%u",
895: le32_to_cpu(cp_block->cp_pack_total_block_count));
896: goto invalid_cp;
897: }
898: pre_version = *version;
899:
900: cp_addr += cp_blocks - 1;
901: err = get_checkpoint_version(sbi, cp_addr, &cp_block,
902: &cp_folio_2, version);
903: if (err)
904: goto invalid_cp;
905: cur_version = *version;
906:
907: if (cur_version == pre_version) {
908: *version = cur_version;
909: f2fs_folio_put(cp_folio_2, true);
910: return cp_folio_1;
911: }
912: f2fs_folio_put(cp_folio_2, true);
913: invalid_cp:
914: f2fs_folio_put(cp_folio_1, true);
915: return NULL;
916: }
SourceFile:fs/f2fs/checkpoint.c]
|
| 24/2 |
2026/05/24 04:17 |
llm |
expert |
0m
Model:
Tokens:
input: 33931
output: 47
thoughts: 34
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in file system checkpointing and metadata handling:
**Analyzing the Checkpoint Validation Flow**
Alright, so the validation of a checkpoint, I'm thinking, starts with the `validate_checkpoint` function call. That makes perfect sense. It's the entry point for ensuring the checkpoint's integrity. And as expected, `validate_checkpoint` immediately delegates to `get_checkpoint_version`. That's how we'll determine which format/version of the checkpoint we are dealing with.
Now, *here's* where things get interesting. `get_checkpoint_version` then dives into retrieving the actual checkpoint data by calling `f2fs_get_meta_folio(sbi, cp_addr)`. This function call is what actually fetches the checkpoint metadata itself, likely from the storage medium. The `sbi` probably refers to the superblock information, essential for context, and `cp_addr` is undoubtedly the logical block address where this checkpoint metadata resides on the device.
So, `validate_checkpoint` kicks off the process, `get_checkpoint_version` figures out the format, and `f2fs_get_meta_folio` does the low-level data retrieval. It's a clean, modular design. Now, I have to consider how `f2fs_get_meta_folio` handles the potential errors that come with reading storage.
|
| 25/2 |
2026/05/24 04:17 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/f2fs/checkpoint.c IncludeLines:true Name:f2fs_get_meta_folio]
Results: map[SourceCode: 110: struct folio *f2fs_get_meta_folio(struct f2fs_sb_info *sbi, pgoff_t index)
111: {
112: return __get_meta_folio(sbi, index, true);
113: }
SourceFile:fs/f2fs/checkpoint.c]
|
| 26/2 |
2026/05/24 04:17 |
llm |
expert |
0m
Model:
Tokens:
input: 34429
output: 44
thoughts: 12
Thoughts: Okay, let's dive into this code.
**Analyzing `__get_meta_folio`**
Alright, the first thing I'm seeing is this function, `__get_meta_folio`. Given the double underscores, I'm assuming this is a protected or private method, probably part of a larger class or module. The "meta" part of the name immediately makes me think this function is concerned with retrieving some kind of descriptive, contextual, or structural information related to a "folio." The use of "folio" suggests this is probably a data structure that involves page or sheet like concepts, maybe documents or financial statements? The prefix "meta" is a pretty good indicator that it's not the primary data itself but additional data about it. I need to look deeper into the code to understand what is in it. I wonder if it retrieves information from a database, or a configuration file, or perhaps it calculates it on the fly based on the data that it has stored. Time to see what it *does*.
|
| 27/2 |
2026/05/24 04:17 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/f2fs/checkpoint.c IncludeLines:true Name:__get_meta_folio]
Results: map[SourceCode: 57: static struct folio *__get_meta_folio(struct f2fs_sb_info *sbi, pgoff_t index,
58: bool is_meta)
59: {
60: struct address_space *mapping = META_MAPPING(sbi);
61: struct folio *folio;
62: struct f2fs_io_info fio = {
63: .sbi = sbi,
64: .type = META,
65: .op = REQ_OP_READ,
66: .op_flags = REQ_META | REQ_PRIO,
67: .old_blkaddr = index,
68: .new_blkaddr = index,
69: .encrypted_page = NULL,
70: .is_por = !is_meta ? 1 : 0,
71: };
72: int err;
73:
74: if (unlikely(!is_meta))
75: fio.op_flags &= ~REQ_META;
76: repeat:
77: folio = f2fs_grab_cache_folio(mapping, index, false);
78: if (IS_ERR(folio)) {
79: cond_resched();
80: goto repeat;
81: }
82: if (folio_test_uptodate(folio))
83: goto out;
84:
85: fio.folio = folio;
86:
87: err = f2fs_submit_page_bio(&fio);
88: if (err) {
89: f2fs_folio_put(folio, true);
90: return ERR_PTR(err);
91: }
92:
93: f2fs_update_iostat(sbi, NULL, FS_META_READ_IO, F2FS_BLKSIZE);
94:
95: folio_lock(folio);
96: if (unlikely(!is_meta_folio(folio))) {
97: f2fs_folio_put(folio, true);
98: goto repeat;
99: }
100:
101: if (unlikely(!folio_test_uptodate(folio))) {
102: f2fs_handle_page_eio(sbi, folio, META);
103: f2fs_folio_put(folio, true);
104: return ERR_PTR(-EIO);
105: }
106: out:
107: return folio;
108: }
SourceFile:fs/f2fs/checkpoint.c]
|
| 28/2 |
2026/05/24 04:17 |
llm |
expert |
0m
Model:
Tokens:
input: 35445
output: 45
thoughts: 283
Thoughts: **My Concerns Regarding Meta-Inode Management in F2FS**
Okay, so `__get_meta_folio` is responsible for reading a folio, and specifically it's targeting `META_MAPPING(sbi)`, which is just a convenient alias for the `i_mapping` of the `sbi->meta_inode`. So, `f2fs_get_valid_checkpoint` is essentially populating the page cache of that meta-inode. Seems straightforward enough. The tricky part is, what happens if `f2fs_get_valid_checkpoint` fails? Say a CRC error comes up. Well, according to the code, it returns an error, and then `f2fs_fill_super` calls `free_meta_inode`.
Now, here's where my alarm bells start ringing. Let's look at `free_meta_inode`: It calls `make_bad_inode`, `iput`, and then nulls out `sbi->meta_inode`. The problem is, `iput` will trigger the usual inode cleanup path, which includes `evict`, and *that* calls `clear_inode`, which contains the critical check: `BUG_ON(inode->i_data.nrpages);`.
Hang on! Shouldn't the page cache of `sbi->meta_inode` still be populated with the folios read during `validate_checkpoint`, even if `f2fs_get_valid_checkpoint` failed? Those folios are sitting there, aren't they? If so, *that* `BUG_ON` will trigger. This doesn't seem right. Shouldn't we be calling something like `truncate_inode_pages` or even the final `truncate_inode_pages_final` *before* we call `iput` to clear out those pages? Let me re-examine `free_meta_inode` again. I need to be sure I'm not missing something fundamental here. There seems to be a missing critical step to ensure data integrity during a checkpoint failure.
|
| 29/2 |
2026/05/24 04:18 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/f2fs/super.c IncludeLines:true Name:f2fs_fill_super]
Results: map[SourceCode:4873: static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc)
4874: {
4875: struct f2fs_fs_context *ctx = fc->fs_private;
4876: struct f2fs_sb_info *sbi;
4877: struct f2fs_super_block *raw_super;
4878: struct inode *root;
4879: int err;
4880: bool skip_recovery = false, need_fsck = false;
4881: int recovery, i, valid_super_block;
4882: struct curseg_info *seg_i;
4883: int retry_cnt = 1;
4884: #ifdef CONFIG_QUOTA
4885: bool quota_enabled = false;
4886: #endif
4887:
4888: try_onemore:
4889: err = -EINVAL;
4890: raw_super = NULL;
4891: valid_super_block = -1;
4892: recovery = 0;
4893:
4894: /* allocate memory for f2fs-specific super block info */
4895: sbi = kzalloc(sizeof(struct f2fs_sb_info), GFP_KERNEL);
4896: if (!sbi)
4897: return -ENOMEM;
4898:
4899: sbi->sb = sb;
4900:
4901: /* initialize locks within allocated memory */
4902: init_f2fs_rwsem(&sbi->gc_lock);
4903: mutex_init(&sbi->writepages);
4904: init_f2fs_rwsem(&sbi->cp_global_sem);
4905: init_f2fs_rwsem(&sbi->node_write);
4906: init_f2fs_rwsem(&sbi->node_change);
4907: spin_lock_init(&sbi->stat_lock);
4908: init_f2fs_rwsem(&sbi->cp_rwsem);
4909: init_f2fs_rwsem(&sbi->cp_enable_rwsem);
4910: init_f2fs_rwsem(&sbi->quota_sem);
4911: init_waitqueue_head(&sbi->cp_wait);
4912: spin_lock_init(&sbi->error_lock);
4913:
4914: for (i = 0; i < NR_INODE_TYPE; i++) {
4915: INIT_LIST_HEAD(&sbi->inode_list[i]);
4916: spin_lock_init(&sbi->inode_lock[i]);
4917: }
4918: mutex_init(&sbi->flush_lock);
4919:
4920: /* set a block size */
4921: if (unlikely(!sb_set_blocksize(sb, F2FS_BLKSIZE))) {
4922: f2fs_err(sbi, "unable to set blocksize");
4923: goto free_sbi;
4924: }
4925:
4926: err = read_raw_super_block(sbi, &raw_super, &valid_super_block,
4927: &recovery);
4928: if (err)
4929: goto free_sbi;
4930:
4931: sb->s_fs_info = sbi;
4932: sbi->raw_super = raw_super;
4933:
4934: INIT_WORK(&sbi->s_error_work, f2fs_record_error_work);
4935: memcpy(sbi->errors, raw_super->s_errors, MAX_F2FS_ERRORS);
4936: memcpy(sbi->stop_reason, raw_super->s_stop_reason, MAX_STOP_REASON);
4937:
4938: /* precompute checksum seed for metadata */
4939: if (f2fs_sb_has_inode_chksum(sbi))
4940: sbi->s_chksum_seed = f2fs_chksum(~0, raw_super->uuid,
4941: sizeof(raw_super->uuid));
4942:
4943: default_options(sbi, false);
4944:
4945: err = f2fs_check_opt_consistency(fc, sb);
4946: if (err)
4947: goto free_sb_buf;
4948:
4949: f2fs_apply_options(fc, sb);
4950:
4951: err = f2fs_sanity_check_options(sbi, false);
4952: if (err)
4953: goto free_options;
4954:
4955: sb->s_maxbytes = max_file_blocks(NULL) <<
4956: le32_to_cpu(raw_super->log_blocksize);
4957: sb->s_max_links = F2FS_LINK_MAX;
4958:
4959: err = f2fs_setup_casefold(sbi);
4960: if (err)
4961: goto free_options;
4962:
4963: #ifdef CONFIG_QUOTA
4964: sb->dq_op = &f2fs_quota_operations;
4965: sb->s_qcop = &f2fs_quotactl_ops;
4966: sb->s_quota_types = QTYPE_MASK_USR | QTYPE_MASK_GRP | QTYPE_MASK_PRJ;
4967:
4968: if (f2fs_sb_has_quota_ino(sbi)) {
4969: for (i = 0; i < MAXQUOTAS; i++) {
4970: if (f2fs_qf_ino(sbi->sb, i))
4971: sbi->nquota_files++;
4972: }
4973: }
4974: #endif
4975:
4976: sb->s_op = &f2fs_sops;
4977: #ifdef CONFIG_FS_ENCRYPTION
4978: sb->s_cop = &f2fs_cryptops;
4979: #endif
4980: #ifdef CONFIG_FS_VERITY
4981: sb->s_vop = &f2fs_verityops;
4982: #endif
4983: sb->s_xattr = f2fs_xattr_handlers;
4984: sb->s_export_op = &f2fs_export_ops;
4985: sb->s_magic = F2FS_SUPER_MAGIC;
4986: sb->s_time_gran = 1;
4987: sb->s_flags = (sb->s_flags & ~SB_POSIXACL) |
4988: (test_opt(sbi, POSIX_ACL) ? SB_POSIXACL : 0);
4989: if (test_opt(sbi, INLINECRYPT))
4990: sb->s_flags |= SB_INLINECRYPT;
4991:
4992: if (test_opt(sbi, LAZYTIME))
4993: sb->s_flags |= SB_LAZYTIME;
4994: else
4995: sb->s_flags &= ~SB_LAZYTIME;
4996:
4997: super_set_uuid(sb, (void *) raw_super->uuid, sizeof(raw_super->uuid));
4998: super_set_sysfs_name_bdev(sb);
4999: sb->s_iflags |= SB_I_CGROUPWB;
5000:
5001: /* init f2fs-specific super block info */
5002: sbi->valid_super_block = valid_super_block;
5003:
5004: /* disallow all the data/node/meta page writes */
5005: set_sbi_flag(sbi, SBI_POR_DOING);
5006:
5007: err = f2fs_init_write_merge_io(sbi);
5008: if (err)
5009: goto free_bio_info;
5010:
5011: init_sb_info(sbi);
5012:
5013: err = f2fs_init_iostat(sbi);
5014: if (err)
5015: goto free_bio_info;
5016:
5017: err = init_percpu_info(sbi);
5018: if (err)
5019: goto free_iostat;
5020:
5021: err = f2fs_init_page_array_cache(sbi);
5022: if (err)
5023: goto free_percpu;
5024:
5025: /* get an inode for meta space */
5026: sbi->meta_inode = f2fs_iget(sb, F2FS_META_INO(sbi));
5027: if (IS_ERR(sbi->meta_inode)) {
5028: f2fs_err(sbi, "Failed to read F2FS meta data inode");
5029: err = PTR_ERR(sbi->meta_inode);
5030: goto free_page_array_cache;
5031: }
5032:
5033: err = f2fs_get_valid_checkpoint(sbi);
5034: if (err) {
5035: f2fs_err(sbi, "Failed to get valid F2FS checkpoint");
5036: goto free_meta_inode;
5037: }
5038:
5039: if (__is_set_ckpt_flags(F2FS_CKPT(sbi), CP_QUOTA_NEED_FSCK_FLAG))
5040: set_sbi_flag(sbi, SBI_QUOTA_NEED_REPAIR);
5041: if (__is_set_ckpt_flags(F2FS_CKPT(sbi), CP_DISABLED_QUICK_FLAG)) {
5042: set_sbi_flag(sbi, SBI_CP_DISABLED_QUICK);
5043: sbi->interval_time[DISABLE_TIME] = DEF_DISABLE_QUICK_INTERVAL;
5044: }
5045:
5046: if (__is_set_ckpt_flags(F2FS_CKPT(sbi), CP_FSCK_FLAG))
5047: set_sbi_flag(sbi, SBI_NEED_FSCK);
5048:
5049: /* Initialize device list */
5050: err = f2fs_scan_devices(sbi);
5051: if (err) {
5052: f2fs_err(sbi, "Failed to find devices");
5053: goto free_devices;
5054: }
5055:
5056: err = f2fs_init_post_read_wq(sbi);
5057: if (err) {
5058: f2fs_err(sbi, "Failed to initialize post read workqueue");
5059: goto free_devices;
5060: }
5061:
5062: sbi->total_valid_node_count =
5063: le32_to_cpu(sbi->ckpt->valid_node_count);
5064: percpu_counter_set(&sbi->total_valid_inode_count,
5065: le32_to_cpu(sbi->ckpt->valid_inode_count));
5066: sbi->user_block_count = le64_to_cpu(sbi->ckpt->user_block_count);
5067: sbi->total_valid_block_count =
5068: le64_to_cpu(sbi->ckpt->valid_block_count);
5069: sbi->last_valid_block_count = sbi->total_valid_block_count;
5070: sbi->reserved_blocks = 0;
5071: sbi->current_reserved_blocks = 0;
5072: limit_reserve_root(sbi);
5073: adjust_unusable_cap_perc(sbi);
5074:
5075: f2fs_init_extent_cache_info(sbi);
5076:
5077: f2fs_init_ino_entry_info(sbi);
5078:
5079: f2fs_init_fsync_node_info(sbi);
5080:
5081: /* setup checkpoint request control and start checkpoint issue thread */
5082: f2fs_init_ckpt_req_control(sbi);
5083: if (!f2fs_readonly(sb) && !test_opt(sbi, DISABLE_CHECKPOINT) &&
5084: test_opt(sbi, MERGE_CHECKPOINT)) {
5085: err = f2fs_start_ckpt_thread(sbi);
5086: if (err) {
5087: f2fs_err(sbi,
5088: "Failed to start F2FS issue_checkpoint_thread (%d)",
5089: err);
5090: goto stop_ckpt_thread;
5091: }
5092: }
5093:
5094: /* setup f2fs internal modules */
5095: err = f2fs_build_segment_manager(sbi);
5096: if (err) {
5097: f2fs_err(sbi, "Failed to initialize F2FS segment manager (%d)",
5098: err);
5099: goto free_sm;
5100: }
5101: err = f2fs_build_node_manager(sbi);
5102: if (err) {
5103: f2fs_err(sbi, "Failed to initialize F2FS node manager (%d)",
5104: err);
5105: goto free_nm;
5106: }
5107:
5108: /* For write statistics */
5109: sbi->sectors_written_start = f2fs_get_sectors_written(sbi);
5110:
5111: /* get segno of first zoned block device */
5112: sbi->first_seq_zone_segno = get_first_seq_zone_segno(sbi);
5113:
5114: sbi->reserved_pin_section = f2fs_sb_has_blkzoned(sbi) ?
5115: ZONED_PIN_SEC_REQUIRED_COUNT :
5116: GET_SEC_FROM_SEG(sbi, overprovision_segments(sbi));
5117:
5118: /* Read accumulated write IO statistics if exists */
5119: seg_i = CURSEG_I(sbi, CURSEG_HOT_NODE);
5120: if (__exist_node_summaries(sbi))
5121: sbi->kbytes_written =
5122: le64_to_cpu(seg_i->journal->info.kbytes_written);
5123:
5124: f2fs_build_gc_manager(sbi);
5125:
5126: err = f2fs_build_stats(sbi);
5127: if (err)
5128: goto free_nm;
5129:
5130: /* get an inode for node space */
5131: sbi->node_inode = f2fs_iget(sb, F2FS_NODE_INO(sbi));
5132: if (IS_ERR(sbi->node_inode)) {
5133: f2fs_err(sbi, "Failed to read node inode");
5134: err = PTR_ERR(sbi->node_inode);
5135: goto free_stats;
5136: }
5137:
5138: /* read root inode and dentry */
5139: root = f2fs_iget(sb, F2FS_ROOT_INO(sbi));
5140: if (IS_ERR(root)) {
5141: f2fs_err(sbi, "Failed to read root inode");
5142: err = PTR_ERR(root);
5143: goto free_node_inode;
5144: }
5145: if (!S_ISDIR(root->i_mode) || !root->i_blocks ||
5146: !root->i_size || !root->i_nlink) {
5147: iput(root);
5148: err = -EINVAL;
5149: goto free_node_inode;
5150: }
5151:
5152: generic_set_sb_d_ops(sb);
5153: sb->s_root = d_make_root(root); /* allocate root dentry */
5154: if (!sb->s_root) {
5155: err = -ENOMEM;
5156: goto free_node_inode;
5157: }
5158:
5159: err = f2fs_init_compress_inode(sbi);
5160: if (err)
5161: goto free_root_inode;
5162:
5163: err = f2fs_register_sysfs(sbi);
5164: if (err)
5165: goto free_compress_inode;
5166:
5167: sbi->umount_lock_holder = current;
5168: #ifdef CONFIG_QUOTA
5169: /* Enable quota usage during mount */
5170: if (f2fs_sb_has_quota_ino(sbi) && !f2fs_readonly(sb)) {
5171: err = f2fs_enable_quotas(sb);
5172: if (err)
5173: f2fs_err(sbi, "Cannot turn on quotas: error %d", err);
5174: }
5175:
5176: quota_enabled = f2fs_recover_quota_begin(sbi);
5177: #endif
5178: /* if there are any orphan inodes, free them */
5179: err = f2fs_recover_orphan_inodes(sbi);
5180: if (err)
5181: goto free_meta;
5182:
5183: if (unlikely(is_set_ckpt_flags(sbi, CP_DISABLED_FLAG))) {
5184: skip_recovery = true;
5185: goto reset_checkpoint;
5186: }
5187:
5188: /* recover fsynced data */
5189: if (!test_opt(sbi, DISABLE_ROLL_FORWARD) &&
5190: !test_opt(sbi, NORECOVERY)) {
5191: /*
5192: * mount should be failed, when device has readonly mode, and
5193: * previous checkpoint was not done by clean system shutdown.
5194: */
5195: if (f2fs_hw_is_readonly(sbi)) {
5196: if (!is_set_ckpt_flags(sbi, CP_UMOUNT_FLAG)) {
5197: err = f2fs_recover_fsync_data(sbi, true);
5198: if (err > 0) {
5199: err = -EROFS;
5200: f2fs_err(sbi, "Need to recover fsync data, but "
5201: "write access unavailable, please try "
5202: "mount w/ disable_roll_forward or norecovery");
5203: }
5204: if (err < 0)
5205: goto free_meta;
5206: }
5207: f2fs_info(sbi, "write access unavailable, skipping recovery");
5208: goto reset_checkpoint;
5209: }
5210:
5211: if (need_fsck)
5212: set_sbi_flag(sbi, SBI_NEED_FSCK);
5213:
5214: if (skip_recovery)
5215: goto reset_checkpoint;
5216:
5217: err = f2fs_recover_fsync_data(sbi, false);
5218: if (err < 0) {
5219: if (err != -ENOMEM)
5220: skip_recovery = true;
5221: need_fsck = true;
5222: f2fs_err(sbi, "Cannot recover all fsync data errno=%d",
5223: err);
5224: goto free_meta;
5225: }
5226: } else {
5227: err = f2fs_recover_fsync_data(sbi, true);
5228: if (err > 0) {
5229: if (!f2fs_readonly(sb)) {
5230: f2fs_err(sbi, "Need to recover fsync data");
5231: err = -EINVAL;
5232: goto free_meta;
5233: } else {
5234: f2fs_info(sbi, "drop all fsynced data");
5235: err = 0;
5236: }
5237: }
5238: }
5239:
5240: reset_checkpoint:
5241: #ifdef CONFIG_QUOTA
5242: f2fs_recover_quota_end(sbi, quota_enabled);
5243: #endif
5244: /*
5245: * If the f2fs is not readonly and fsync data recovery succeeds,
5246: * write pointer consistency of cursegs and other zones are already
5247: * checked and fixed during recovery. However, if recovery fails,
5248: * write pointers are left untouched, and retry-mount should check
5249: * them here.
5250: */
5251: if (skip_recovery)
5252: err = f2fs_check_and_fix_write_pointer(sbi);
5253: if (err)
5254: goto free_meta;
5255:
5256: /* f2fs_recover_fsync_data() cleared this already */
5257: clear_sbi_flag(sbi, SBI_POR_DOING);
5258:
5259: err = f2fs_init_inmem_curseg(sbi);
5260: if (err)
5261: goto sync_free_meta;
5262:
5263: if (test_opt(sbi, DISABLE_CHECKPOINT))
5264: err = f2fs_disable_checkpoint(sbi);
5265: else if (is_set_ckpt_flags(sbi, CP_DISABLED_FLAG))
5266: err = f2fs_enable_checkpoint(sbi);
5267: if (err)
5268: goto sync_free_meta;
5269:
5270: /*
5271: * If filesystem is not mounted as read-only then
5272: * do start the gc_thread.
5273: */
5274: if ((F2FS_OPTION(sbi).bggc_mode != BGGC_MODE_OFF ||
5275: test_opt(sbi, GC_MERGE)) && !f2fs_readonly(sb)) {
5276: /* After POR, we can run background GC thread.*/
5277: err = f2fs_start_gc_thread(sbi);
5278: if (err)
5279: goto sync_free_meta;
5280: }
5281:
5282: /* recover broken superblock */
5283: if (recovery) {
5284: err = f2fs_commit_super(sbi, true);
5285: f2fs_info(sbi, "Try to recover %dth superblock, ret: %d",
5286: sbi->valid_super_block ? 1 : 2, err);
5287: }
5288:
5289: f2fs_join_shrinker(sbi);
5290:
5291: f2fs_tuning_parameters(sbi);
5292:
5293: f2fs_notice(sbi, "Mounted with checkpoint version = %llx",
5294: cur_cp_version(F2FS_CKPT(sbi)));
5295: f2fs_update_time(sbi, CP_TIME);
5296: f2fs_update_time(sbi, REQ_TIME);
5297: clear_sbi_flag(sbi, SBI_CP_DISABLED_QUICK);
5298:
5299: sbi->umount_lock_holder = NULL;
5300: return 0;
5301:
5302: sync_free_meta:
5303: /* safe to flush all the data */
5304: sync_filesystem(sbi->sb);
5305: retry_cnt = 0;
5306:
5307: free_meta:
5308: #ifdef CONFIG_QUOTA
5309: f2fs_truncate_quota_inode_pages(sb);
5310: if (f2fs_sb_has_quota_ino(sbi) && !f2fs_readonly(sb))
5311: f2fs_quota_off_umount(sbi->sb);
5312: #endif
5313: /*
5314: * Some dirty meta pages can be produced by f2fs_recover_orphan_inodes()
5315: * failed by EIO. Then, iput(node_inode) can trigger balance_fs_bg()
5316: * followed by f2fs_write_checkpoint() through f2fs_write_node_pages(), which
5317: * falls into an infinite loop in f2fs_sync_meta_pages().
5318: */
5319: truncate_inode_pages_final(META_MAPPING(sbi));
5320: /* evict some inodes being cached by GC */
5321: evict_inodes(sb);
5322: f2fs_unregister_sysfs(sbi);
5323: free_compress_inode:
5324: f2fs_destroy_compress_inode(sbi);
5325: free_root_inode:
5326: dput(sb->s_root);
5327: sb->s_root = NULL;
5328: free_node_inode:
5329: f2fs_release_ino_entry(sbi, true);
5330: truncate_inode_pages_final(NODE_MAPPING(sbi));
5331: iput(sbi->node_inode);
5332: sbi->node_inode = NULL;
5333: free_stats:
5334: f2fs_destroy_stats(sbi);
5335: free_nm:
5336: /* stop discard thread before destroying node manager */
5337: f2fs_stop_discard_thread(sbi);
5338: f2fs_destroy_node_manager(sbi);
5339: free_sm:
5340: f2fs_destroy_segment_manager(sbi);
5341: stop_ckpt_thread:
5342: f2fs_stop_ckpt_thread(sbi);
5343: /* flush s_error_work before sbi destroy */
5344: flush_work(&sbi->s_error_work);
5345: f2fs_destroy_post_read_wq(sbi);
5346: free_devices:
5347: destroy_device_list(sbi);
5348: kvfree(sbi->ckpt);
5349: free_meta_inode:
5350: make_bad_inode(sbi->meta_inode);
5351: iput(sbi->meta_inode);
5352: sbi->meta_inode = NULL;
5353: free_page_array_cache:
5354: f2fs_destroy_page_array_cache(sbi);
5355: free_percpu:
5356: destroy_percpu_info(sbi);
5357: free_iostat:
5358: f2fs_destroy_iostat(sbi);
5359: free_bio_info:
5360: for (i = 0; i < NR_PAGE_TYPE; i++)
5361: kfree(sbi->write_io[i]);
5362:
5363: #if IS_ENABLED(CONFIG_UNICODE)
5364: utf8_unload(sb->s_encoding);
5365: sb->s_encoding = NULL;
5366: #endif
5367: free_options:
5368: #ifdef CONFIG_QUOTA
5369: for (i = 0; i < MAXQUOTAS; i++)
5370: kfree(F2FS_OPTION(sbi).s_qf_names[i]);
5371: #endif
5372: /* no need to free dummy_enc_policy, we just keep it in ctx when failed */
5373: swap(F2FS_CTX_INFO(ctx).dummy_enc_policy, F2FS_OPTION(sbi).dummy_enc_policy);
5374: free_sb_buf:
5375: kfree(raw_super);
5376: free_sbi:
5377: kfree(sbi);
5378: sb->s_fs_info = NULL;
5379:
5380: /* give only one another chance */
5381: if (retry_cnt > 0 && skip_recovery) {
5382: retry_cnt--;
5383: shrink_dcache_sb(sb);
5384: goto try_onemore;
5385: }
5386: return err;
5387: }
SourceFile:fs/f2fs/super.c]
|
| 30/2 |
2026/05/24 04:18 |
llm |
expert |
0m
Model:
Tokens:
input: 44641
output: 21
thoughts: 2609
Thoughts: **F2FS Meta Inode Page Cache Leak Vulnerability Analysis**
Okay, so I'm looking at the F2FS code, specifically the `free_meta_inode` function. It seems there's a problem, and my expert intuition tells me it's a critical issue related to the page cache and error handling during filesystem mount.
My initial observation is that `free_meta_inode` doesn't call `truncate_inode_pages_final` on the `sbi->meta_inode`, but this call does occur in the `free_meta` label, earlier in the error path. The `meta_inode` is populated with checkpoint blocks within the `f2fs_get_valid_checkpoint` function. If `f2fs_get_valid_checkpoint` fails, it jumps to `free_meta_inode`. It looks like the page cache for the `meta_inode` might contain checkpoint blocks that are not deallocated before the `iput` call. The subsequent `iput` then calls `evict`, which leads to `clear_inode`, hitting a `BUG_ON(inode->i_data.nrpages)`!
Yes, it seems this is a known bug. It's a local denial of service (kernel BUG) triggered by mounting a corrupted f2fs image.
Let's dig deeper: I'm verifying this by tracking the potential failure points within `f2fs_get_valid_checkpoint`. The critical point is CRC validation in `get_checkpoint_version`, where an invalid CRC leads to `-EINVAL` which trickles up to cause a `NULL` return from `validate_checkpoint`. That will lead to a failure in the `f2fs_get_valid_checkpoint` function, eventually landing at the `free_meta_inode` label.
There are other potential error paths before `free_meta`, for example if `f2fs_scan_devices` fails. In this case, it jumps to `free_devices`, which falls through to `free_meta_inode`. The vulnerability is still there, because `truncate_inode_pages_final` is skipped. The core problem is that `truncate_inode_pages_final` is only called at `free_meta` which is bypassed in all error cases.
Therefore, this is a clear bug in the error handling of `f2fs_fill_super`. The fix should involve adding the `truncate_inode_pages_final(META_MAPPING(sbi))` call before `iput(sbi->meta_inode)` in `free_meta_inode` or moving it to that location.
Now, let's assess the security impact. The `BUG_ON` in `clear_inode` results in a kernel panic β a Denial of Service. While it's a clean crash without memory corruption if the `BUG_ON` is triggered, it's still a significant issue.
However, if `BUG_ON` is somehow compiled out, `clear_inode` continues without freeing the pages, and you end up with a use-after-free scenario when trying to access those pages through the page cache. But that will almost never happen, as `CONFIG_BUG` is almost always enabled.
Is this exploitable by unprivileged users? Mounting the filesystem usually needs `CAP_SYS_ADMIN` in the initial user namespace, but unprivileged users can create user namespaces and then mount a filesystem. So, let's check if F2FS has the `FS_USERNS_MOUNT` flag.
|
| 31/2 |
2026/05/24 04:18 |
tool |
grepper |
0m
Args: map[Expression:FS_USERNS_MOUNT]
Results: map[Output:drivers/android/binder/rust_binderfs.c=758=static struct file_system_type binder_fs_type = {
--
drivers/android/binder/rust_binderfs.c-762- .kill_sb = binderfs_kill_super,
drivers/android/binder/rust_binderfs.c:763: .fs_flags = FS_USERNS_MOUNT,
drivers/android/binder/rust_binderfs.c-764-};
--
drivers/android/binderfs.c=749=static struct file_system_type binder_fs_type = {
--
drivers/android/binderfs.c-753- .kill_sb = binderfs_kill_super,
drivers/android/binderfs.c:754: .fs_flags = FS_USERNS_MOUNT,
drivers/android/binderfs.c-755-};
--
fs/binfmt_misc.c=1020=static struct file_system_type bm_fs_type = {
--
fs/binfmt_misc.c-1023- .init_fs_context = bm_init_fs_context,
fs/binfmt_misc.c:1024: .fs_flags = FS_USERNS_MOUNT,
fs/binfmt_misc.c-1025- .kill_sb = kill_anon_super,
--
fs/devpts/inode.c=453=static struct file_system_type devpts_fs_type = {
--
fs/devpts/inode.c-457- .kill_sb = devpts_kill_sb,
fs/devpts/inode.c:458: .fs_flags = FS_USERNS_MOUNT,
fs/devpts/inode.c-459-};
--
fs/fuse/inode.c=2130=static struct file_system_type fuse_fs_type = {
--
fs/fuse/inode.c-2132- .name = "fuse",
fs/fuse/inode.c:2133: .fs_flags = FS_HAS_SUBTYPE | FS_USERNS_MOUNT | FS_ALLOW_IDMAP,
fs/fuse/inode.c-2134- .init_fs_context = fuse_init_fs_context,
--
fs/overlayfs/params.c=760=static const struct fs_context_operations ovl_context_ops = {
--
fs/overlayfs/params.c-769- * This is called during fsopen() and will record the user namespace of
fs/overlayfs/params.c:770: * the caller in fc->user_ns since we've raised FS_USERNS_MOUNT. We'll
fs/overlayfs/params.c-771- * need it when we actually create the superblock to verify that the
--
fs/overlayfs/super.c=1578=struct file_system_type ovl_fs_type = {
--
fs/overlayfs/super.c-1582- .parameters = ovl_parameter_spec,
fs/overlayfs/super.c:1583: .fs_flags = FS_USERNS_MOUNT,
fs/overlayfs/super.c-1584- .kill_sb = kill_anon_super,
--
fs/proc/root.c=357=static struct file_system_type proc_fs_type = {
--
fs/proc/root.c-361- .kill_sb = proc_kill_sb,
fs/proc/root.c:362: .fs_flags = FS_USERNS_MOUNT | FS_DISALLOW_NOTIFY_PERM,
fs/proc/root.c-363-};
--
fs/ramfs/inode.c=317=static struct file_system_type ramfs_fs_type = {
--
fs/ramfs/inode.c-321- .kill_sb = ramfs_kill_sb,
fs/ramfs/inode.c:322: .fs_flags = FS_USERNS_MOUNT,
fs/ramfs/inode.c-323-};
--
fs/super.c=691=bool mount_capable(struct fs_context *fc)
fs/super.c-692-{
fs/super.c:693: if (!(fc->fs_type->fs_flags & FS_USERNS_MOUNT))
fs/super.c-694- return capable(CAP_SYS_ADMIN);
--
fs/super.c=731=struct super_block *sget_fc(struct fs_context *fc,
--
fs/super.c-740- /*
fs/super.c:741: * Never allow s_user_ns != &init_user_ns when FS_USERNS_MOUNT is
fs/super.c-742- * not set, as the filesystem is likely unprepared to handle it.
--
fs/super.c-745- */
fs/super.c:746: if (user_ns != &init_user_ns && !(fc->fs_type->fs_flags & FS_USERNS_MOUNT)) {
fs/super.c-747- errorfc(fc, "VFS: Mounting from non-initial user namespace is not allowed");
--
fs/sysfs/mount.c=90=static struct file_system_type sysfs_fs_type = {
--
fs/sysfs/mount.c-93- .kill_sb = sysfs_kill_sb,
fs/sysfs/mount.c:94: .fs_flags = FS_USERNS_MOUNT,
fs/sysfs/mount.c-95-};
--
include/linux/fs.h=2264=struct file_system_type {
--
include/linux/fs.h-2269-#define FS_HAS_SUBTYPE 4
include/linux/fs.h:2270:#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
include/linux/fs.h-2271-#define FS_DISALLOW_NOTIFY_PERM 16 /* Disable fanotify permission events */
--
ipc/mqueue.c=1619=static struct file_system_type mqueue_fs_type = {
--
ipc/mqueue.c-1622- .kill_sb = kill_anon_super,
ipc/mqueue.c:1623: .fs_flags = FS_USERNS_MOUNT,
ipc/mqueue.c-1624-};
--
kernel/bpf/inode.c=1082=static struct file_system_type bpf_fs_type = {
--
kernel/bpf/inode.c-1087- .kill_sb = bpf_kill_super,
kernel/bpf/inode.c:1088: .fs_flags = FS_USERNS_MOUNT,
kernel/bpf/inode.c-1089-};
--
kernel/cgroup/cgroup.c=2392=struct file_system_type cgroup_fs_type = {
--
kernel/cgroup/cgroup.c-2396- .kill_sb = cgroup_kill_sb,
kernel/cgroup/cgroup.c:2397: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2398-};
--
kernel/cgroup/cgroup.c=2400=static struct file_system_type cgroup2_fs_type = {
--
kernel/cgroup/cgroup.c-2404- .kill_sb = cgroup_kill_sb,
kernel/cgroup/cgroup.c:2405: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2406-};
--
kernel/cgroup/cgroup.c=2473=static struct file_system_type cpuset_fs_type = {
--
kernel/cgroup/cgroup.c-2476- .parameters = cpuset_fs_parameters,
kernel/cgroup/cgroup.c:2477: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2478-};
--
mm/shmem.c=5358=static struct file_system_type shmem_fs_type = {
--
mm/shmem.c-5365- .kill_sb = kill_anon_super,
mm/shmem.c:5366: .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP | FS_MGTIME,
mm/shmem.c-5367-};
--
mm/shmem.c=5768=static struct file_system_type shmem_fs_type = {
--
mm/shmem.c-5772- .kill_sb = ramfs_kill_sb,
mm/shmem.c:5773: .fs_flags = FS_USERNS_MOUNT,
mm/shmem.c-5774-};
]
|
| 32/2 |
2026/05/24 04:18 |
llm |
expert |
1m
Model:
Tokens:
input: 50338
output: 66
thoughts: 2357
Thoughts: **F2FS Vulnerability Analysis: A Deep Dive**
Okay, let's break this down systematically. My initial assessment is that this is a denial-of-service (DoS) vulnerability within the f2fs filesystem. It's triggered when an f2fs filesystem fails to mount correctly, resulting in a kernel panic. I've walked through the logic, and here's what I've found.
First, it's clear that this is *not* accessible via user namespaces or unprivileged processes due to the lack of `FS_USERNS_MOUNT` support in f2fs. The only attack vector is mounting a malicious image. So, "Malicious Filesystem Trigger" and "Peripheral Trigger" (if auto-mounted) are the relevant triggers. The key function is `f2fs_fill_super`. This is the single entry point.
The vulnerability stems from an incorrect error handling path within the mount process. The core issue revolves around the `BUG_ON(inode->i_data.nrpages)` call. If triggered, this leads to a kernel panic. *But*, if `CONFIG_BUG` is disabled, this `BUG_ON` becomes a no-op, and the vulnerability escalates to a use-after-free scenario. In most production kernels, `CONFIG_BUG` is enabled, which limits the impact to a DoS.
Further investigation reveals the problem originates with `f2fs_get_valid_checkpoint`. If this function fails to read valid checkpoint information, or fails to initialize various F2FS components between that call and the release of inode pages, `f2fs_fill_super` will proceed to free the metadata inode without properly freeing its associated page cache. Essentially, there's a missing call to `truncate_inode_pages_final` in these error paths.
The key lie in the `f2fs_get_valid_checkpoint` and later functions, specifically any error returns between `f2fs_get_valid_checkpoint` and `free_meta`. The page cache may be used to store F2FS metadata, and `truncate_inode_pages_final` is not called to clean up the page cache of `sbi->meta_inode` before freeing. If, after `f2fs_get_valid_checkpoint` is called, any subsequent step such as `f2fs_scan_devices`, or `f2fs_register_sysfs` fails, the path to `free_meta_inode` is taken, but the page cache is not cleaned up. This is a very common scenario for corrupt filesystems.
So, any failure during the mounting process after a valid checkpoint is found (but before the inode is released) creates the vulnerability. It's all about missing the crucial call to `truncate_inode_pages_final(META_MAPPING(sbi))` before `iput` is called on the metadata inode.
The syzkaller reproducer confirms this. It crafts an f2fs image with an invalid checkpoint, which, causes `f2fs_get_valid_checkpoint` to fail, thus jumping to `free_meta_inode`.
* **Exploitable:** False (DoS)
* **DenialOfService:** True
* **Unprivileged:** False
* **UserNamespace:** False
* **VMGuestTrigger:** False
* **VMHostTrigger:** False
* **NetworkTrigger:** False
* **RemoteTrigger:** False
* **PeripheralTrigger:** True
* **FilesystemTrigger:** True
|
| 33/2 |
2026/05/24 04:19 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 34/2 |
2026/05/24 04:19 |
llm |
expert |
0m
Model:
Tokens:
input: 53580
output: 631
thoughts: 437
Thoughts: **My Analysis of the F2FS Filesystem Bug**
Okay, the dust has settled, and the results are in. Now I need to articulate this clearly in the final response β explaining the bug and its security implications for the user.
Basically, the problem boils down to an error in how `f2fs_fill_super` handles errors during the f2fs filesystem mount process. Specifically, it's triggered when `f2fs_get_valid_checkpoint` is called during the mounting and hits an invalid checkpoint block. This function uses `f2fs_get_meta_folio` to read checkpoint blocks, which have likely been placed within the page cache of `sbi->meta_inode`. If something goes wrongβan invalid CRC, for instanceβ`f2fs_get_valid_checkpoint` returns an error.
The bug is in the error handling logic within `f2fs_fill_super`. It jumps to the `free_meta_inode` label and calls `iput(sbi->meta_inode)`. Here's the key: it *doesn't* call `truncate_inode_pages_final(META_MAPPING(sbi))` before dropping the inode reference. This is what's causing the problem, the cache data is still there when `iput` is called.
The `iput` function will call `evict`, and eventually calls `clear_inode`, which is where the kernel explodes. Because `clear_inode` expects the inode's page cache to be empty, and it isn't, due to the missing `truncate_inode_pages_final` call, we trigger a `BUG_ON(inode->i_data.nrpages)` β a kernel panic.
The good news is that while it's a kernel panic, itβs a clean panic via `BUG_ON`. I see no risk of memory corruption or privilege escalation, which means that it isn't directly exploitable in that sense. It's a straightforward Denial of Service.
This bug can be triggered by mounting a carefully crafted malicious f2fs image. And because f2fs doesn't support unprivileged mounts, an attacker needs `CAP_SYS_ADMIN` in the initial user namespace. However, this raises an important question: what about auto-mounting? If a system is configured to automatically mount external media (like USB drives), it means the attacker can trigger this bug without any special privileges, which makes this bug a Peripheral and Malicious Filesystem trigger. Also, since it's a mount issue, it's not possible to trigger this over the network or from a VM guest (unless the guest image is somehow mounted by the host).
|
| 35/1 |
2026/05/24 04:20 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the bug report and the kernel source code, here is the
assessment of the bug and its security impact:
### Bug Explanation
The bug is located in the error handling path of `f2fs_fill_super()` during the
mount process of an f2fs filesystem.
When the kernel attempts to mount the filesystem, it calls
`f2fs_get_valid_checkpoint()` to read and validate the filesystem's checkpoint
blocks. This function uses `f2fs_get_meta_folio()` to read the blocks, which
populates the page cache of the special `sbi->meta_inode`.
If the checkpoint blocks are corrupted (e.g., they have an invalid CRC, as seen
in the bug report logs), `f2fs_get_valid_checkpoint()` returns an error. The
error handling path in `f2fs_fill_super()` then jumps to the `free_meta_inode`
label and calls `iput(sbi->meta_inode)` to free the inode.
However, the error path fails to call
`truncate_inode_pages_final(META_MAPPING(sbi))` before dropping the inode
reference. Consequently, the `iput()` call triggers the inode eviction process
(`evict()`), which eventually calls `clear_inode()`. Because the invalid
checkpoint blocks were left behind in the inode's page cache, `clear_inode()`
hits the `BUG_ON(inode->i_data.nrpages)` assertion, causing a kernel panic.
### Security Impact
* **Exploitable:** **False**. The bug triggers a deterministic `BUG_ON`
assertion which cleanly halts the kernel (or oopses the current process,
depending on the configuration). It does not cause memory corruption,
use-after-free, or alter control flow in a way that could be leveraged for
privilege escalation or arbitrary code execution.
* **Denial Of Service:** **True**. Triggering the bug results in a kernel panic,
leading to a complete system crash.
* **Accessible From Unprivileged Processes / User Namespaces:** **False**. The
`f2fs` filesystem does not have the `FS_USERNS_MOUNT` flag. Therefore, it cannot
be mounted by unprivileged users, even within a new user namespace. Mounting it
requires `CAP_SYS_ADMIN` in the initial user namespace.
* **Malicious Filesystem / Peripheral Trigger:** **True**. The bug is triggered
by parsing a maliciously crafted f2fs image. While an unprivileged user cannot
mount it directly, the bug poses a significant risk in Desktop or Mobile
environments that are configured to auto-mount external media (like USB drives).
An attacker could plug in a malicious USB drive to crash the system.
* **Network / Remote / VM Triggers:** **False**. The bug is strictly tied to the
local filesystem mount path and cannot be triggered over the network or by a VM
guest (unless the host is explicitly configured to mount an untrusted f2fs image
provided by the guest).]
|