| 0/0 |
2026/05/23 17:04 |
flow |
assessment-security |
47m
Results: map[DenialOfService:true Explanation:**Bug Analysis:**
The bug is a `BUG_ON(*phys == 0)` in `ocfs2_write_cluster()` located in
`fs/ocfs2/aops.c`.
This `BUG_ON` is triggered when a write operation allocates a new cluster for a
file, but a subsequent call to `ocfs2_get_clusters()` reports that the newly
allocated cluster is a hole (indicated by `*phys == 0`).
This contradictory state occurs because `ocfs2_get_clusters()` is hardcoded to
treat any extent with a physical block number of 0 (`e_blkno == 0`) as a sparse
hole. In a valid OCFS2 filesystem, block 0 (which resides in cluster 0) contains
the superblock and is always marked as allocated in the global bitmap.
Therefore, a valid allocator will never allocate block 0 for file data.
However, a maliciously crafted filesystem image can manipulate the global bitmap
to mark bit 0 (cluster 0) as free. When a file write operation requests a new
cluster, the allocator may claim cluster 0 and insert an extent with `e_blkno =
0` into the extent tree. When `ocfs2_write_cluster()` immediately reads back the
extent via `ocfs2_get_clusters()`, the function sees `e_blkno == 0`, assumes it
is a hole, and returns `*phys = 0`. This violates the assumption that a newly
allocated cluster cannot be a hole, hitting the `BUG_ON` and crashing the
kernel.
**Security Impact:**
* **Exploitable:** No. The bug results in a deterministic `BUG_ON` which halts
the kernel. There is no memory corruption, use-after-free, or out-of-bounds
access involved that could be leveraged for privilege escalation or code
execution.
* **Denial Of Service:** Yes. Triggering the bug causes a kernel panic, leading
to a complete system crash.
* **Accessible From Unprivileged Processes & User Namespaces:** No. The OCFS2
filesystem does not have the `FS_USERNS_MOUNT` flag set. Therefore, it cannot be
mounted from within a user namespace. Mounting the filesystem requires
`CAP_SYS_ADMIN` in the initial (root) user namespace.
* **Malicious Filesystem & Peripheral Trigger:** Yes. The bug is triggered by
mounting and interacting with a maliciously crafted OCFS2 filesystem image. This
is particularly relevant for desktop environments that might auto-mount external
media (like USB drives) containing the crafted image. Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/23 17:04 |
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;
}
uint64_t r[1] = {0xffffffffffffffff};
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;
intptr_t res = 0;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// syz_mount_image$ocfs2 arguments: [
// fs: ptr[in, buffer] {
// buffer: {6f 63 66 73 32 00} (length 0x6)
// }
// dir: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// flags: mount_flags = 0x8c0 (8 bytes)
// opts: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {61 63 6c 2c 68 65 61 72 74 62 65 61 74 3d 6e 6f 6e 65 2c 6e 6f 75 73 65 72 5f 78 61 74 74 72 2c 63 6f 68 65 72 65 6e 63 79 3d 66 75 6c 6c 2c 64 61 74 61 3d 77 72 69 74 65 62 61 63 6b 2c 6c 6f 63 61 6c 61 6c 6c 6f 63 3d 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 31 2c 61 63 6c 2c 6e 6f 61 63 6c 2c 6c 6f 63 61 6c 61 6c 6c 6f 63 3d 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 2c 00 a8 9f 6b 8d 58 00 aa 95 4e 6c 87 35 dc d5 29 21 ce 08 46 2f b4 ce 7c 16 00 88 32 51 44 3a c3 32 f4 d1 7b 77 d2 98 67 e4 32 16 10 91 6d bc 59 63 e9 fb 59 a0 32 c9 2e 32 eb ff c3 b7 39 95 1e 86 6d 52 bf f6 bd 63 13 6a 65 62 22 06 2a 8e ea 0c f9 74 80 bc 8a c6 c0 e8 a2 aa 38 ff a8 fa 75 8c d5 4b 9e f3 9a 7f 53 6d 7b 85 17 3a 83 c3 4d 78 e2 10 ec f4 d0 40 81 7b be 98 9e 9e b0 15 ac b8 4b 99 b2 ed 90 f7 18 10 cd 92 ee ca 69 f5 27 5c b7 b7 02 7d 4b ab f6 43 bd 69 b0 a6 81 34 c0 22 fe 5d cd 03 83 4f 54 73 25 ac 2d 1a 5d 16 f0 74 d8 98 94 6f f7 1a fa 90 18 0b 31 7e 64 5d d5 8a 92 2e 5d 90 74 62 cd 50 dc 23 80 1c 48 c0 d4 9a b0 12 96 1d 84 d2 eb 85 a3 73 0a 3f 31 77 da 04 8c 3b c9 91 21 6b 11 dc a0 20 af ef c2 4a e7 58 3b 59 53 4a 0a b1 fc a8 2b f4 73 21 61 41 db 8e 98 64 f7 86 1d 05 00 a9 20 e1 a8 d3 35 2d 06 62 f5 86 e7 43 38 6e 87 b6 c0 c5 ed 6e 4d 19 2e f2 99 0b 44 ce db b7 08 e7 a1 8b 20 dc d5 57 3b 60 3c 4d 65 99 92 64 7f fa 82 10 ab f4 e9 d2 32 b5 2a 8e a1 dd 1c 04 5a fd 8e 47 2c d1 fb b7 75 e8 9c ca 49 d1 36 a6} (length 0x1dc)
// }
// }
// }
// chdir: int8 = 0x1 (1 bytes)
// size: len = 0x4446 (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x4446)
// }
// ]
// returns fd_dir
memcpy((void*)0x200000004440, "ocfs2\000", 6);
memcpy((void*)0x200000000040, "./file1\000", 8);
memcpy((void*)0x200000000400, "... [truncated large byte array] ...", 476);
memcpy((void*)0x2000000088c0, "... [truncated large byte array] ...", 17478);
syz_mount_image(/*fs=*/0x200000004440, /*dir=*/0x200000000040, /*flags=MS_NODIRATIME|MS_MANDLOCK|MS_DIRSYNC*/0x8c0, /*opts=*/0x200000000400, /*chdir=*/1, /*size=*/0x4446, /*img=*/0x2000000088c0);
// openat arguments: [
// fd: fd_dir (resource)
// file: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// flags: open_flags = 0x183042 (4 bytes)
// mode: open_mode = 0x15 (2 bytes)
// ]
// returns fd
memcpy((void*)0x200000000740, "./file1\000", 8);
res = syscall(__NR_openat, /*fd=*/0xffffff9c, /*file=*/0x200000000740ul, /*flags=O_SYNC|O_CREAT|O_CLOEXEC|FASYNC|O_RDWR*/0x183042, /*mode=S_IXOTH|S_IROTH|S_IWGRP*/0x15);
if (res != -1)
r[0] = res;
// pwrite64 arguments: [
// fd: fd (resource)
// buf: ptr[in, buffer] {
// buffer: {32} (length 0x1)
// }
// count: len = 0xfdef (8 bytes)
// pos: intptr = 0xe7c (8 bytes)
// ]
memset((void*)0x200000000140, 50, 1);
syscall(__NR_pwrite64, /*fd=*/r[0], /*buf=*/0x200000000140ul, /*count=*/0xfdeful, /*pos=*/0xe7cul);
return 0;
}
]
|
| 2/1 |
2026/05/23 17:04 |
action |
kernel-checkouter |
2m
Results: map[KernelSrc:/app/workdir/cache/src/6cb2e6561db479b2fb5d97db3b1e81fb3573f2f8]
|
| 3/1 |
2026/05/23 17:06 |
action |
kernel-builder |
29m
Results: map[KernelObj:/app/workdir/cache/build/49e0e8cc5a5b72a2cba297789999132c60584ef4]
|
| 4/1 |
2026/05/23 17:36 |
action |
codesearch-prepare |
12m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/23 17:49 |
agent |
expert |
3m
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:
option from the mount to silence this warning.
=======================================================
ocfs2: Mounting device (7,0) on (node local, slot 0) with writeback data mode.
------------[ cut here ]------------
kernel BUG at fs/ocfs2/aops.c:1159!
Oops: invalid opcode: 0000 [#1] SMP KASAN PTI
CPU: 0 UID: 0 PID: 6030 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT_{RT,(full)}
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/02/2025
RIP: 0010:ocfs2_write_cluster fs/ocfs2/aops.c:1159 [inline]
RIP: 0010:ocfs2_write_cluster_by_desc+0x1cc1/0x1cf0 fs/ocfs2/aops.c:1219
Code: 03 48 8d bc 24 10 02 00 00 48 c7 c6 04 9d dc 8c ba 6a 04 00 00 48 c7 c1 80 77 2a 8b e8 98 50 1b 00 90 0f 0b e8 00 1a 2a fe 90 <0f> 0b e8 f8 19 2a fe 90 0f 0b e8 f0 19 2a fe 90 0f 0b e8 e8 19 2a
RSP: 0018:ffffc90003c7ee60 EFLAGS: 00010293
RAX: ffffffff8394a380 RBX: ffff8880351d4014 RCX: ffff88803ce7da00
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
RBP: ffffc90003c7f190 R08: 0000000000000000 R09: 0000000000000000
R10: dffffc0000000000 R11: fffffbfff1dac52f R12: ffff888033e04000
R13: dffffc0000000000 R14: 0000000000000000 R15: ffff8880351d4014
FS: 0000555560337500(0000) GS:ffff888126dfc000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fff79522b2c CR3: 0000000032ce8000 CR4: 00000000003526f0
Call Trace:
<TASK>
ocfs2_write_begin_nolock+0x31a3/0x4340 fs/ocfs2/aops.c:1796
ocfs2_write_begin+0x1bb/0x310 fs/ocfs2/aops.c:1884
generic_perform_write+0x29d/0x8c0 mm/filemap.c:4242
ocfs2_file_write_iter+0x157d/0x1d20 fs/ocfs2/file.c:2469
new_sync_write fs/read_write.c:593 [inline]
vfs_write+0x5d5/0xb40 fs/read_write.c:686
ksys_pwrite64 fs/read_write.c:793 [inline]
__do_sys_pwrite64 fs/read_write.c:801 [inline]
__se_sys_pwrite64 fs/read_write.c:798 [inline]
__x64_sys_pwrite64+0x196/0x220 fs/read_write.c:798
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xfa/0xfa0 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f548cd4efc9
Code: ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 a8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffd1ff86138 EFLAGS: 00000246 ORIG_RAX: 0000000000000012
RAX: ffffffffffffffda RBX: 00007f548cfa5fa0 RCX: 00007f548cd4efc9
RDX: 000000000000fdef RSI: 0000200000000140 RDI: 0000000000000004
RBP: 00007f548cdd1f91 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000e7c R11: 0000000000000246 R12: 0000000000000000
R13: 00007f548cfa5fa0 R14: 00007f548cfa5fa0 R15: 0000000000000004
</TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:ocfs2_write_cluster fs/ocfs2/aops.c:1159 [inline]
RIP: 0010:ocfs2_write_cluster_by_desc+0x1cc1/0x1cf0 fs/ocfs2/aops.c:1219
Code: 03 48 8d bc 24 10 02 00 00 48 c7 c6 04 9d dc 8c ba 6a 04 00 00 48 c7 c1 80 77 2a 8b e8 98 50 1b 00 90 0f 0b e8 00 1a 2a fe 90 <0f> 0b e8 f8 19 2a fe 90 0f 0b e8 f0 19 2a fe 90 0f 0b e8 e8 19 2a
RSP: 0018:ffffc90003c7ee60 EFLAGS: 00010293
RAX: ffffffff8394a380 RBX: ffff8880351d4014 RCX: ffff88803ce7da00
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
RBP: ffffc90003c7f190 R08: 0000000000000000 R09: 0000000000000000
R10: dffffc0000000000 R11: fffffbfff1dac52f R12: ffff888033e04000
R13: dffffc0000000000 R14: 0000000000000000 R15: ffff8880351d4014
FS: 0000555560337500(0000) GS:ffff888126dfc000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fff79522b2c CR3: 0000000032ce8000 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;
}
uint64_t r[1] = {0xffffffffffffffff};
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;
intptr_t res = 0;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// syz_mount_image$ocfs2 arguments: [
// fs: ptr[in, buffer] {
// buffer: {6f 63 66 73 32 00} (length 0x6)
// }
// dir: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// flags: mount_flags = 0x8c0 (8 bytes)
// opts: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {61 63 6c 2c 68 65 61 72 74 62 65 61 74 3d 6e 6f 6e 65 2c 6e 6f 75 73 65 72 5f 78 61 74 74 72 2c 63 6f 68 65 72 65 6e 63 79 3d 66 75 6c 6c 2c 64 61 74 61 3d 77 72 69 74 65 62 61 63 6b 2c 6c 6f 63 61 6c 61 6c 6c 6f 63 3d 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 31 2c 61 63 6c 2c 6e 6f 61 63 6c 2c 6c 6f 63 61 6c 61 6c 6c 6f 63 3d 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 2c 00 a8 9f 6b 8d 58 00 aa 95 4e 6c 87 35 dc d5 29 21 ce 08 46 2f b4 ce 7c 16 00 88 32 51 44 3a c3 32 f4 d1 7b 77 d2 98 67 e4 32 16 10 91 6d bc 59 63 e9 fb 59 a0 32 c9 2e 32 eb ff c3 b7 39 95 1e 86 6d 52 bf f6 bd 63 13 6a 65 62 22 06 2a 8e ea 0c f9 74 80 bc 8a c6 c0 e8 a2 aa 38 ff a8 fa 75 8c d5 4b 9e f3 9a 7f 53 6d 7b 85 17 3a 83 c3 4d 78 e2 10 ec f4 d0 40 81 7b be 98 9e 9e b0 15 ac b8 4b 99 b2 ed 90 f7 18 10 cd 92 ee ca 69 f5 27 5c b7 b7 02 7d 4b ab f6 43 bd 69 b0 a6 81 34 c0 22 fe 5d cd 03 83 4f 54 73 25 ac 2d 1a 5d 16 f0 74 d8 98 94 6f f7 1a fa 90 18 0b 31 7e 64 5d d5 8a 92 2e 5d 90 74 62 cd 50 dc 23 80 1c 48 c0 d4 9a b0 12 96 1d 84 d2 eb 85 a3 73 0a 3f 31 77 da 04 8c 3b c9 91 21 6b 11 dc a0 20 af ef c2 4a e7 58 3b 59 53 4a 0a b1 fc a8 2b f4 73 21 61 41 db 8e 98 64 f7 86 1d 05 00 a9 20 e1 a8 d3 35 2d 06 62 f5 86 e7 43 38 6e 87 b6 c0 c5 ed 6e 4d 19 2e f2 99 0b 44 ce db b7 08 e7 a1 8b 20 dc d5 57 3b 60 3c 4d 65 99 92 64 7f fa 82 10 ab f4 e9 d2 32 b5 2a 8e a1 dd 1c 04 5a fd 8e 47 2c d1 fb b7 75 e8 9c ca 49 d1 36 a6} (length 0x1dc)
// }
// }
// }
// chdir: int8 = 0x1 (1 bytes)
// size: len = 0x4446 (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x4446)
// }
// ]
// returns fd_dir
memcpy((void*)0x200000004440, "ocfs2\000", 6);
memcpy((void*)0x200000000040, "./file1\000", 8);
memcpy((void*)0x200000000400, "... [truncated large byte array] ...", 476);
memcpy((void*)0x2000000088c0, "... [truncated large byte array] ...", 17478);
syz_mount_image(/*fs=*/0x200000004440, /*dir=*/0x200000000040, /*flags=MS_NODIRATIME|MS_MANDLOCK|MS_DIRSYNC*/0x8c0, /*opts=*/0x200000000400, /*chdir=*/1, /*size=*/0x4446, /*img=*/0x2000000088c0);
// openat arguments: [
// fd: fd_dir (resource)
// file: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// flags: open_flags = 0x183042 (4 bytes)
// mode: open_mode = 0x15 (2 bytes)
// ]
// returns fd
memcpy((void*)0x200000000740, "./file1\000", 8);
res = syscall(__NR_openat, /*fd=*/0xffffff9c, /*file=*/0x200000000740ul, /*flags=O_SYNC|O_CREAT|O_CLOEXEC|FASYNC|O_RDWR*/0x183042, /*mode=S_IXOTH|S_IROTH|S_IWGRP*/0x15);
if (res != -1)
r[0] = res;
// pwrite64 arguments: [
// fd: fd (resource)
// buf: ptr[in, buffer] {
// buffer: {32} (length 0x1)
// }
// count: len = 0xfdef (8 bytes)
// pos: intptr = 0xe7c (8 bytes)
// ]
memset((void*)0x200000000140, 50, 1);
syscall(__NR_pwrite64, /*fd=*/r[0], /*buf=*/0x200000000140ul, /*count=*/0xfdeful, /*pos=*/0xe7cul);
return 0;
}
Reply: **Bug Analysis:**
The bug is a `BUG_ON(*phys == 0)` in `ocfs2_write_cluster()` located in `fs/ocfs2/aops.c`.
This `BUG_ON` is triggered when a write operation allocates a new cluster for a file, but a subsequent call to `ocfs2_get_clusters()` reports that the newly allocated cluster is a hole (indicated by `*phys == 0`).
This contradictory state occurs because `ocfs2_get_clusters()` is hardcoded to treat any extent with a physical block number of 0 (`e_blkno == 0`) as a sparse hole. In a valid OCFS2 filesystem, block 0 (which resides in cluster 0) contains the superblock and is always marked as allocated in the global bitmap. Therefore, a valid allocator will never allocate block 0 for file data.
However, a maliciously crafted filesystem image can manipulate the global bitmap to mark bit 0 (cluster 0) as free. When a file write operation requests a new cluster, the allocator may claim cluster 0 and insert an extent with `e_blkno = 0` into the extent tree. When `ocfs2_write_cluster()` immediately reads back the extent via `ocfs2_get_clusters()`, the function sees `e_blkno == 0`, assumes it is a hole, and returns `*phys = 0`. This violates the assumption that a newly allocated cluster cannot be a hole, hitting the `BUG_ON` and crashing the kernel.
**Security Impact:**
* **Exploitable:** No. The bug results in a deterministic `BUG_ON` which halts the kernel. There is no memory corruption, use-after-free, or out-of-bounds access involved that could be leveraged for privilege escalation or code execution.
* **Denial Of Service:** Yes. Triggering the bug causes a kernel panic, leading to a complete system crash.
* **Accessible From Unprivileged Processes & User Namespaces:** No. The OCFS2 filesystem does not have the `FS_USERNS_MOUNT` flag set. Therefore, it cannot be mounted from within a user namespace. Mounting the filesystem requires `CAP_SYS_ADMIN` in the initial (root) user namespace.
* **Malicious Filesystem & Peripheral Trigger:** Yes. The bug is triggered by mounting and interacting with a maliciously crafted OCFS2 filesystem image. This is particularly relevant for desktop environments that might auto-mount external media (like USB drives) containing the crafted image.
|
| 6/2 |
2026/05/23 17:49 |
llm |
expert |
0m
Model:
Tokens:
input: 16068
output: 46
thoughts: 270
Thoughts: **Analyzing a Kernel Bug in OCFS2**
Okay, so I've got this bug report, a kernel panic, right? The classic `kernel BUG` message, right at `fs/ocfs2/aops.c:1159!` That's the first thing I need to drill into. It looks like it's buried deep inside the OCFS2 file system, which, given the stack trace, makes sense.
Let me take a look at the call stack: `ocfs2_write_cluster`, `ocfs2_write_cluster_by_desc`, `ocfs2_write_begin_nolock`, `ocfs2_write_begin`, then into the generic filemap code with `generic_perform_write`, and ultimately in `ocfs2_file_write_iter`. It seems to be happening on a write path. I need to understand what's happening in those functions, specifically around `aops.c:1159`. The stack trace gives me a good starting point, showing the path that's leading to the issue, it suggests there's a problem with how OCFS2 handles the write operations, likely with respect to a cluster write operation.
I'm thinking I'll need to correlate the code at that line with the surrounding context to see if there's an obvious race condition, a null pointer dereference, a bad calculation, or some other memory access problem. Time to pull up the source code and start digging!
|
| 7/2 |
2026/05/23 17:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/aops.c IncludeLines:true Name:ocfs2_write_cluster]
Results: map[SourceCode:1089: /*
1090: * Prepare a single cluster for write one cluster into the file.
1091: */
1092: static int ocfs2_write_cluster(struct address_space *mapping,
1093: u32 *phys, unsigned int new,
1094: unsigned int clear_unwritten,
1095: unsigned int should_zero,
1096: struct ocfs2_alloc_context *data_ac,
1097: struct ocfs2_alloc_context *meta_ac,
1098: struct ocfs2_write_ctxt *wc, u32 cpos,
1099: loff_t user_pos, unsigned user_len)
1100: {
1101: int ret, i;
1102: u64 p_blkno;
1103: struct inode *inode = mapping->host;
1104: struct ocfs2_extent_tree et;
1105: int bpc = ocfs2_clusters_to_blocks(inode->i_sb, 1);
1106:
1107: if (new) {
1108: u32 tmp_pos;
1109:
1110: /*
1111: * This is safe to call with the page locks - it won't take
1112: * any additional semaphores or cluster locks.
1113: */
1114: tmp_pos = cpos;
1115: ret = ocfs2_add_inode_data(OCFS2_SB(inode->i_sb), inode,
1116: &tmp_pos, 1, !clear_unwritten,
1117: wc->w_di_bh, wc->w_handle,
1118: data_ac, meta_ac, NULL);
1119: /*
1120: * This shouldn't happen because we must have already
1121: * calculated the correct meta data allocation required. The
1122: * internal tree allocation code should know how to increase
1123: * transaction credits itself.
1124: *
1125: * If need be, we could handle -EAGAIN for a
1126: * RESTART_TRANS here.
1127: */
1128: mlog_bug_on_msg(ret == -EAGAIN,
1129: "Inode %llu: EAGAIN return during allocation.\n",
1130: (unsigned long long)OCFS2_I(inode)->ip_blkno);
1131: if (ret < 0) {
1132: mlog_errno(ret);
1133: goto out;
1134: }
1135: } else if (clear_unwritten) {
1136: ocfs2_init_dinode_extent_tree(&et, INODE_CACHE(inode),
1137: wc->w_di_bh);
1138: ret = ocfs2_mark_extent_written(inode, &et,
1139: wc->w_handle, cpos, 1, *phys,
1140: meta_ac, &wc->w_dealloc);
1141: if (ret < 0) {
1142: mlog_errno(ret);
1143: goto out;
1144: }
1145: }
1146:
1147: /*
1148: * The only reason this should fail is due to an inability to
1149: * find the extent added.
1150: */
1151: ret = ocfs2_get_clusters(inode, cpos, phys, NULL, NULL);
1152: if (ret < 0) {
1153: mlog(ML_ERROR, "Get physical blkno failed for inode %llu, "
1154: "at logical cluster %u",
1155: (unsigned long long)OCFS2_I(inode)->ip_blkno, cpos);
1156: goto out;
1157: }
1158:
1159: BUG_ON(*phys == 0);
1160:
1161: p_blkno = ocfs2_clusters_to_blocks(inode->i_sb, *phys);
1162: if (!should_zero)
1163: p_blkno += (user_pos >> inode->i_sb->s_blocksize_bits) & (u64)(bpc - 1);
1164:
1165: for (i = 0; i < wc->w_num_folios; i++) {
1166: int tmpret;
1167:
1168: /* This is the direct io target page. */
1169: if (wc->w_folios[i] == NULL) {
1170: p_blkno += (1 << (PAGE_SHIFT - inode->i_sb->s_blocksize_bits));
1171: continue;
1172: }
1173:
1174: tmpret = ocfs2_prepare_folio_for_write(inode, &p_blkno, wc,
1175: wc->w_folios[i], cpos, user_pos, user_len,
1176: should_zero);
1177: if (tmpret) {
1178: mlog_errno(tmpret);
1179: if (ret == 0)
1180: ret = tmpret;
1181: }
1182: }
1183:
1184: /*
1185: * We only have cleanup to do in case of allocating write.
1186: */
1187: if (ret && new)
1188: ocfs2_write_failure(inode, wc, user_pos, user_len);
1189:
1190: out:
1191:
1192: return ret;
1193: }
SourceFile:fs/ocfs2/aops.c]
|
| 8/2 |
2026/05/23 17:49 |
llm |
expert |
0m
Model:
Tokens:
input: 18371
output: 50
thoughts: 206
Thoughts: **Debugging a Zero Physical Cluster Issue**
Okay, so I've hit a nasty bug: `BUG_ON(*phys == 0)` within `ocfs2_write_cluster`. That's a classic sign of trying to write to an unallocated cluster. The root cause is likely in `ocfs2_get_clusters` returning a zero value for the physical cluster number. This function is supposed to provide the physical location for a logical cluster position, `cpos`. A zero value implies the cluster is unallocated β a hole in our data. But... hang on, that doesn't make sense.
We *should* have already dealt with the allocation. If `new` is true when calling `ocfs2_get_clusters`, we call `ocfs2_add_inode_data`, which *allocates* the cluster. If `clear_unwritten` is set, we use `ocfs2_mark_extent_written`, implying it was recently allocated. And if *neither* is true, the cluster *should* already be allocated! So why is `ocfs2_get_clusters` returning zero?! There's a fundamental inconsistency here.
I need to dive deeper into `ocfs2_write_cluster_by_desc` to trace the data flow and understand how this invalid state is reached. This is where the real investigation begins.
|
| 9/2 |
2026/05/23 17:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/aops.c IncludeLines:true Name:ocfs2_write_cluster_by_desc]
Results: map[SourceCode:1195: static int ocfs2_write_cluster_by_desc(struct address_space *mapping,
1196: struct ocfs2_alloc_context *data_ac,
1197: struct ocfs2_alloc_context *meta_ac,
1198: struct ocfs2_write_ctxt *wc,
1199: loff_t pos, unsigned len)
1200: {
1201: int ret, i;
1202: loff_t cluster_off;
1203: unsigned int local_len = len;
1204: struct ocfs2_write_cluster_desc *desc;
1205: struct ocfs2_super *osb = OCFS2_SB(mapping->host->i_sb);
1206:
1207: for (i = 0; i < wc->w_clen; i++) {
1208: desc = &wc->w_desc[i];
1209:
1210: /*
1211: * We have to make sure that the total write passed in
1212: * doesn't extend past a single cluster.
1213: */
1214: local_len = len;
1215: cluster_off = pos & (osb->s_clustersize - 1);
1216: if ((cluster_off + local_len) > osb->s_clustersize)
1217: local_len = osb->s_clustersize - cluster_off;
1218:
1219: ret = ocfs2_write_cluster(mapping, &desc->c_phys,
1220: desc->c_new,
1221: desc->c_clear_unwritten,
1222: desc->c_needs_zero,
1223: data_ac, meta_ac,
1224: wc, desc->c_cpos, pos, local_len);
1225: if (ret) {
1226: mlog_errno(ret);
1227: goto out;
1228: }
1229:
1230: len -= local_len;
1231: pos += local_len;
1232: }
1233:
1234: ret = 0;
1235: out:
1236: return ret;
1237: }
SourceFile:fs/ocfs2/aops.c]
|
| 10/2 |
2026/05/23 17:49 |
llm |
expert |
0m
Model:
Tokens:
input: 19602
output: 48
thoughts: 190
Thoughts: **Analyzing a Potential OCFS2 Corruption Scenario**
Okay, so I'm looking at this reproducer, and it seems like it's mounting an OCFS2 image and performing a `pwrite64` operation on a file. The key here is that the image is *likely* corrupted. My initial instinct is that something funky is happening within `ocfs2_get_clusters`. Specifically, it looks like it's returning `*phys == 0` for a cluster that *should* be allocated.
Wait a second⦠`ocfs2_get_clusters` returns `0` if the extent record indicates it's a hole, right? So, is this a standard unallocated cluster, or something more nefarious? It's making me wonder if this filesystem image is maliciously crafted. Could a carefully crafted image trick `ocfs2_get_clusters` into returning zero for a *genuinely* allocated cluster? Or, even worse, perhaps `ocfs2_write_cluster` is being called with `desc->c_new == 0` but the cluster is actually a hole. That would be bad.
My next step is to trace the flow of `wc->w_desc` and see where it gets populated. Let me check the code. I'm pretty sure it's happening in either `ocfs2_write_begin_nolock` or `ocfs2_populate_write_desc`. Let's confirm that and go from there. This needs more investigation!
|
| 11/2 |
2026/05/23 17:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/aops.c IncludeLines:true Name:ocfs2_populate_write_desc]
Results: map[SourceCode:1351: /*
1352: * Populate each single-cluster write descriptor in the write context
1353: * with information about the i/o to be done.
1354: *
1355: * Returns the number of clusters that will have to be allocated, as
1356: * well as a worst case estimate of the number of extent records that
1357: * would have to be created during a write to an unwritten region.
1358: */
1359: static int ocfs2_populate_write_desc(struct inode *inode,
1360: struct ocfs2_write_ctxt *wc,
1361: unsigned int *clusters_to_alloc,
1362: unsigned int *extents_to_split)
1363: {
1364: int ret;
1365: struct ocfs2_write_cluster_desc *desc;
1366: unsigned int num_clusters = 0;
1367: unsigned int ext_flags = 0;
1368: u32 phys = 0;
1369: int i;
1370:
1371: *clusters_to_alloc = 0;
1372: *extents_to_split = 0;
1373:
1374: for (i = 0; i < wc->w_clen; i++) {
1375: desc = &wc->w_desc[i];
1376: desc->c_cpos = wc->w_cpos + i;
1377:
1378: if (num_clusters == 0) {
1379: /*
1380: * Need to look up the next extent record.
1381: */
1382: ret = ocfs2_get_clusters(inode, desc->c_cpos, &phys,
1383: &num_clusters, &ext_flags);
1384: if (ret) {
1385: mlog_errno(ret);
1386: goto out;
1387: }
1388:
1389: /* We should already CoW the refcountd extent. */
1390: BUG_ON(ext_flags & OCFS2_EXT_REFCOUNTED);
1391:
1392: /*
1393: * Assume worst case - that we're writing in
1394: * the middle of the extent.
1395: *
1396: * We can assume that the write proceeds from
1397: * left to right, in which case the extent
1398: * insert code is smart enough to coalesce the
1399: * next splits into the previous records created.
1400: */
1401: if (ext_flags & OCFS2_EXT_UNWRITTEN)
1402: *extents_to_split = *extents_to_split + 2;
1403: } else if (phys) {
1404: /*
1405: * Only increment phys if it doesn't describe
1406: * a hole.
1407: */
1408: phys++;
1409: }
1410:
1411: /*
1412: * If w_first_new_cpos is < UINT_MAX, we have a non-sparse
1413: * file that got extended. w_first_new_cpos tells us
1414: * where the newly allocated clusters are so we can
1415: * zero them.
1416: */
1417: if (desc->c_cpos >= wc->w_first_new_cpos) {
1418: BUG_ON(phys == 0);
1419: desc->c_needs_zero = 1;
1420: }
1421:
1422: desc->c_phys = phys;
1423: if (phys == 0) {
1424: desc->c_new = 1;
1425: desc->c_needs_zero = 1;
1426: desc->c_clear_unwritten = 1;
1427: *clusters_to_alloc = *clusters_to_alloc + 1;
1428: }
1429:
1430: if (ext_flags & OCFS2_EXT_UNWRITTEN) {
1431: desc->c_clear_unwritten = 1;
1432: desc->c_needs_zero = 1;
1433: }
1434:
1435: ret = ocfs2_unwritten_check(inode, wc, desc);
1436: if (ret) {
1437: mlog_errno(ret);
1438: goto out;
1439: }
1440:
1441: num_clusters--;
1442: }
1443:
1444: ret = 0;
1445: out:
1446: return ret;
1447: }
SourceFile:fs/ocfs2/aops.c]
|
| 12/2 |
2026/05/23 17:49 |
llm |
expert |
0m
Model:
Tokens:
input: 21583
output: 48
thoughts: 301
Thoughts: **Analyzing a Potential OCFS2 Cluster Allocation Issue**
Okay, let's break this down. In `ocfs2_populate_write_desc`, we're calling `ocfs2_get_clusters`. If `phys` comes back as zero, we're marking `desc->c_new` as 1. So far, so good. Then, downstream in `ocfs2_write_cluster_by_desc`, this `desc->c_new` flag is passed to `ocfs2_write_cluster`. And if `desc->c_new` is set, `ocfs2_write_cluster` then calls `ocfs2_add_inode_data`, which I understand allocates a cluster.
Here's where the problem arises. After the allocation, it calls `ocfs2_get_clusters` again, but this time expects a non-zero value for `*phys`. If `*phys` *is* zero, then it throws `BUG_ON(*phys == 0)`. That's the crux of the issue.
Now, I understand what `ocfs2_add_inode_data` is supposed to do. It allocates the data and then inserts it into the extent tree. The critical thing here is that the extent tree should *immediately* reflect this change.
Therefore, the fact that `ocfs2_get_clusters` is returning 0 *after* `ocfs2_add_inode_data` is called strongly suggests one of several possibilities. Either the extent tree update didn't happen correctly, `ocfs2_get_clusters` might be reading from an inconsistent location, there's some kind of file system corruption that allows `ocfs2_add_inode_data` to succeed without actually adding the extent, or perhaps `ocfs2_get_clusters` itself is somehow failing to find what's been added. It's a pretty serious condition.
My next step is to examine `ocfs2_add_inode_data` closely. I'll need to confirm how it interacts with the extent tree and whether there are any race conditions or logical errors that could lead to this discrepancy. This will likely involve tracing data structure updates and checking for potential inconsistencies.
|
| 13/2 |
2026/05/23 17:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/aops.c IncludeLines:true Name:ocfs2_add_inode_data]
Results: map[SourceCode: 526: /*
527: * extend file allocation only here.
528: * we'll update all the disk stuff, and oip->alloc_size
529: *
530: * expect stuff to be locked, a transaction started and enough data /
531: * metadata reservations in the contexts.
532: *
533: * Will return -EAGAIN, and a reason if a restart is needed.
534: * If passed in, *reason will always be set, even in error.
535: */
536: int ocfs2_add_inode_data(struct ocfs2_super *osb,
537: struct inode *inode,
538: u32 *logical_offset,
539: u32 clusters_to_add,
540: int mark_unwritten,
541: struct buffer_head *fe_bh,
542: handle_t *handle,
543: struct ocfs2_alloc_context *data_ac,
544: struct ocfs2_alloc_context *meta_ac,
545: enum ocfs2_alloc_restarted *reason_ret)
546: {
547: struct ocfs2_extent_tree et;
548:
549: ocfs2_init_dinode_extent_tree(&et, INODE_CACHE(inode), fe_bh);
550: return ocfs2_add_clusters_in_btree(handle, &et, logical_offset,
551: clusters_to_add, mark_unwritten,
552: data_ac, meta_ac, reason_ret);
553: }
SourceFile:fs/ocfs2/file.c]
|
| 14/2 |
2026/05/23 17:49 |
llm |
expert |
0m
Model:
Tokens:
input: 22908
output: 49
thoughts: 17
Thoughts: Okay, let's dive into this `ocfs2_add_clusters_in_btree` function. I need to understand its role within the larger OCFS2 file system context. Specifically, I need to see how it's integrated with B-tree structures. My expectation is this function is responsible for adding, either singly or in batches, cluster descriptions to the B-tree, presumably for metadata, allocation or possibly even data storage purposes. I need to quickly identify the key data structures involved. What are the arguments it takes? What internal functions does it call? Does it use the OCFS2 B-tree library directly, or does it abstract that away somehow? I want to see how this function handles concurrency - does it take any locks? Does it protect against race conditions? Also I need to understand error handling. How does it handle failures during cluster addition? What are the implications for data consistency if something goes wrong during the addition of these clusters? Finally, is this function used during normal operation or does it mainly occur during filesystem creation, resize, or repair? That context could be quite important. I will focus on understanding the logic flow and pinpointing the important decision points and error paths.
|
| 15/2 |
2026/05/23 17:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/file.c IncludeLines:true Name:ocfs2_add_clusters_in_btree]
Results: map[SourceCode:4777: /*
4778: * Allocate and add clusters into the extent b-tree.
4779: * The new clusters(clusters_to_add) will be inserted at logical_offset.
4780: * The extent b-tree's root is specified by et, and
4781: * it is not limited to the file storage. Any extent tree can use this
4782: * function if it implements the proper ocfs2_extent_tree.
4783: */
4784: int ocfs2_add_clusters_in_btree(handle_t *handle,
4785: struct ocfs2_extent_tree *et,
4786: u32 *logical_offset,
4787: u32 clusters_to_add,
4788: int mark_unwritten,
4789: struct ocfs2_alloc_context *data_ac,
4790: struct ocfs2_alloc_context *meta_ac,
4791: enum ocfs2_alloc_restarted *reason_ret)
4792: {
4793: int status = 0, err = 0;
4794: int need_free = 0;
4795: int free_extents;
4796: enum ocfs2_alloc_restarted reason = RESTART_NONE;
4797: u32 bit_off, num_bits;
4798: u64 block;
4799: u8 flags = 0;
4800: struct ocfs2_super *osb =
4801: OCFS2_SB(ocfs2_metadata_cache_get_super(et->et_ci));
4802:
4803: BUG_ON(!clusters_to_add);
4804:
4805: if (mark_unwritten)
4806: flags = OCFS2_EXT_UNWRITTEN;
4807:
4808: free_extents = ocfs2_num_free_extents(et);
4809: if (free_extents < 0) {
4810: status = free_extents;
4811: mlog_errno(status);
4812: goto leave;
4813: }
4814:
4815: /* there are two cases which could cause us to EAGAIN in the
4816: * we-need-more-metadata case:
4817: * 1) we haven't reserved *any*
4818: * 2) we are so fragmented, we've needed to add metadata too
4819: * many times. */
4820: if (!free_extents && !meta_ac) {
4821: err = -1;
4822: status = -EAGAIN;
4823: reason = RESTART_META;
4824: goto leave;
4825: } else if ((!free_extents)
4826: && (ocfs2_alloc_context_bits_left(meta_ac)
4827: < ocfs2_extend_meta_needed(et->et_root_el))) {
4828: err = -2;
4829: status = -EAGAIN;
4830: reason = RESTART_META;
4831: goto leave;
4832: }
4833:
4834: status = __ocfs2_claim_clusters(handle, data_ac, 1,
4835: clusters_to_add, &bit_off, &num_bits);
4836: if (status < 0) {
4837: if (status != -ENOSPC)
4838: mlog_errno(status);
4839: goto leave;
4840: }
4841:
4842: BUG_ON(num_bits > clusters_to_add);
4843:
4844: /* reserve our write early -- insert_extent may update the tree root */
4845: status = ocfs2_et_root_journal_access(handle, et,
4846: OCFS2_JOURNAL_ACCESS_WRITE);
4847: if (status < 0) {
4848: mlog_errno(status);
4849: need_free = 1;
4850: goto bail;
4851: }
4852:
4853: block = ocfs2_clusters_to_blocks(osb->sb, bit_off);
4854: trace_ocfs2_add_clusters_in_btree(
4855: (unsigned long long)ocfs2_metadata_cache_owner(et->et_ci),
4856: bit_off, num_bits);
4857: status = ocfs2_insert_extent(handle, et, *logical_offset, block,
4858: num_bits, flags, meta_ac);
4859: if (status < 0) {
4860: mlog_errno(status);
4861: need_free = 1;
4862: goto bail;
4863: }
4864:
4865: ocfs2_journal_dirty(handle, et->et_root_bh);
4866:
4867: clusters_to_add -= num_bits;
4868: *logical_offset += num_bits;
4869:
4870: if (clusters_to_add) {
4871: err = clusters_to_add;
4872: status = -EAGAIN;
4873: reason = RESTART_TRANS;
4874: }
4875:
4876: bail:
4877: if (need_free) {
4878: if (data_ac->ac_which == OCFS2_AC_USE_LOCAL)
4879: ocfs2_free_local_alloc_bits(osb, handle, data_ac,
4880: bit_off, num_bits);
4881: else
4882: ocfs2_free_clusters(handle,
4883: data_ac->ac_inode,
4884: data_ac->ac_bh,
4885: ocfs2_clusters_to_blocks(osb->sb, bit_off),
4886: num_bits);
4887: }
4888:
4889: leave:
4890: if (reason_ret)
4891: *reason_ret = reason;
4892: trace_ocfs2_add_clusters_in_btree_ret(status, reason, err);
4893: return status;
4894: }
SourceFile:fs/ocfs2/alloc.c]
|
| 16/2 |
2026/05/23 17:49 |
llm |
expert |
0m
Model:
Tokens:
input: 25089
output: 46
thoughts: 382
Thoughts: **Investigating Potential Cluster Allocation Issues in OCFS2**
Okay, so I'm looking at how OCFS2 allocates and manages clusters, specifically within the B-tree context. The code path I'm examining starts with `ocfs2_add_clusters_in_btree`. It seems pretty straightforward: `__ocfs2_claim_clusters` is called to grab the clusters, then `ocfs2_insert_extent` inserts the extent into the B-tree. The `logical_offset` is then updated. Hold on, though, `ocfs2_add_inode_data` also seems to update that `logical_offset` - that's something to keep in mind.
Now, I'm digging into `ocfs2_write_cluster`. I see that when there's a new allocation (`new` is true), `ocfs2_add_inode_data` is called. It uses this temporary variable, `tmp_pos`, which is initialized with `cpos` - the original logical offset. Crucially, `ocfs2_add_inode_data` updates `tmp_pos`. But then, `ocfs2_get_clusters` is called with the original `cpos`, *not* the updated `tmp_pos`.
So, `ocfs2_add_inode_data` appears to insert the extent into the tree at the position defined by `tmp_pos`, which started as `cpos`. If `ocfs2_insert_extent` succeeds, the extent should be in the B-tree. My concern is, why would `ocfs2_get_clusters` return a physical cluster of zero (`*phys == 0`) when it's looking up the original `cpos` after the allocation and insertion? Assuming `ocfs2_insert_extent` works correctly, I can't quite see why that would happen. The extent should be in the tree. I need to understand this logic better. I think I need to trace through `ocfs2_get_clusters` now to fully understand the flow.
|
| 17/2 |
2026/05/23 17:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/aops.c IncludeLines:true Name:ocfs2_get_clusters]
Results: map[SourceCode: 605: int ocfs2_get_clusters(struct inode *inode, u32 v_cluster,
606: u32 *p_cluster, u32 *num_clusters,
607: unsigned int *extent_flags)
608: {
609: int ret;
610: unsigned int hole_len, flags = 0;
611: struct buffer_head *di_bh = NULL;
612: struct ocfs2_extent_rec rec;
613:
614: if (OCFS2_I(inode)->ip_dyn_features & OCFS2_INLINE_DATA_FL) {
615: ret = -ERANGE;
616: mlog_errno(ret);
617: goto out;
618: }
619:
620: ret = ocfs2_extent_map_lookup(inode, v_cluster, p_cluster,
621: num_clusters, extent_flags);
622: if (ret == 0)
623: goto out;
624:
625: ret = ocfs2_read_inode_block(inode, &di_bh);
626: if (ret) {
627: mlog_errno(ret);
628: goto out;
629: }
630:
631: ret = ocfs2_get_clusters_nocache(inode, di_bh, v_cluster, &hole_len,
632: &rec, NULL);
633: if (ret) {
634: mlog_errno(ret);
635: goto out;
636: }
637:
638: if (rec.e_blkno == 0ULL) {
639: /*
640: * A hole was found. Return some canned values that
641: * callers can key on. If asked for, num_clusters will
642: * be populated with the size of the hole.
643: */
644: *p_cluster = 0;
645: if (num_clusters) {
646: *num_clusters = hole_len;
647: }
648: } else {
649: ocfs2_relative_extent_offsets(inode->i_sb, v_cluster, &rec,
650: p_cluster, num_clusters);
651: flags = rec.e_flags;
652:
653: ocfs2_extent_map_insert_rec(inode, &rec);
654: }
655:
656: if (extent_flags)
657: *extent_flags = flags;
658:
659: out:
660: brelse(di_bh);
661: return ret;
662: }
SourceFile:fs/ocfs2/extent_map.c]
|
| 18/2 |
2026/05/23 17:49 |
llm |
expert |
0m
Model:
Tokens:
input: 26817
output: 45
thoughts: 91
Thoughts: Okay, let's break this down.
**Extent Map Lookups and Cache Coherency**
Right, so `ocfs2_get_clusters` starts by hitting `ocfs2_extent_map_lookup`. That's smart. Use the extent map first - the in-memory cache - for a quick win. If it's there, great, we avoid a B-tree search and can return immediately.
But...wait a minute. My concern is, does adding an extent via `ocfs2_add_inode_data`, which I know involves the B-tree, automatically update the extent map cache? That's the critical question here. Cache invalidation is the devil! I need to confirm that the map stays coherent with the B-tree changes. To confirm this, I should examine `ocfs2_insert_extent`. That's where the actual B-tree insertion logic lies, and itβs where the extent map would have to be updated if that logic is correct.
|
| 19/2 |
2026/05/23 17:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/alloc.c IncludeLines:true Name:ocfs2_insert_extent]
Results: map[SourceCode:4709: /*
4710: * Insert an extent into a btree.
4711: *
4712: * The caller needs to update the owning btree's cluster count.
4713: */
4714: int ocfs2_insert_extent(handle_t *handle,
4715: struct ocfs2_extent_tree *et,
4716: u32 cpos,
4717: u64 start_blk,
4718: u32 new_clusters,
4719: u8 flags,
4720: struct ocfs2_alloc_context *meta_ac)
4721: {
4722: int status;
4723: int free_records;
4724: struct buffer_head *last_eb_bh = NULL;
4725: struct ocfs2_insert_type insert = {0, };
4726: struct ocfs2_extent_rec rec;
4727:
4728: trace_ocfs2_insert_extent_start(
4729: (unsigned long long)ocfs2_metadata_cache_owner(et->et_ci),
4730: cpos, new_clusters);
4731:
4732: memset(&rec, 0, sizeof(rec));
4733: rec.e_cpos = cpu_to_le32(cpos);
4734: rec.e_blkno = cpu_to_le64(start_blk);
4735: rec.e_leaf_clusters = cpu_to_le16(new_clusters);
4736: rec.e_flags = flags;
4737: status = ocfs2_et_insert_check(et, &rec);
4738: if (status) {
4739: mlog_errno(status);
4740: goto bail;
4741: }
4742:
4743: status = ocfs2_figure_insert_type(et, &last_eb_bh, &rec,
4744: &free_records, &insert);
4745: if (status < 0) {
4746: mlog_errno(status);
4747: goto bail;
4748: }
4749:
4750: trace_ocfs2_insert_extent(insert.ins_appending, insert.ins_contig,
4751: insert.ins_contig_index, free_records,
4752: insert.ins_tree_depth);
4753:
4754: if (insert.ins_contig == CONTIG_NONE && free_records == 0) {
4755: status = ocfs2_grow_tree(handle, et,
4756: &insert.ins_tree_depth, &last_eb_bh,
4757: meta_ac);
4758: if (status) {
4759: mlog_errno(status);
4760: goto bail;
4761: }
4762: }
4763:
4764: /* Finally, we can add clusters. This might rotate the tree for us. */
4765: status = ocfs2_do_insert_extent(handle, et, &rec, &insert);
4766: if (status < 0)
4767: mlog_errno(status);
4768: else
4769: ocfs2_et_extent_map_insert(et, &rec);
4770:
4771: bail:
4772: brelse(last_eb_bh);
4773:
4774: return status;
4775: }
SourceFile:fs/ocfs2/alloc.c]
|
| 20/2 |
2026/05/23 17:49 |
llm |
expert |
0m
Model:
Tokens:
input: 28210
output: 47
thoughts: 379
Thoughts: **Analyzing Extent Insertion in OCFS2**
Okay, so I'm looking at how extent maps are updated in OCFS2, specifically within the `ocfs2_insert_extent` function. This function calls `ocfs2_et_extent_map_insert`, which should, theoretically, update the extent map successfully. If that call returns without error, the extent map *should* contain the newly added record. But, hang on... what if `ocfs2_add_inode_data` returns a success code of 0, but doesn't *actually* insert the extent? That's the critical question I need to explore.
I'm digging deeper now, into the `ocfs2_add_clusters_in_btree` function. I'm focusing on that section of code where `__ocfs2_claim_clusters` is called. It returns a value `num_bits`, which is apparently the number of clusters successfully claimed. Following this, `ocfs2_insert_extent` is called. The crucial lines are:
```c
status = __ocfs2_claim_clusters(handle, data_ac, 1,
clusters_to_add, &bit_off, &num_bits);
if (status < 0) { ... }
...
status = ocfs2_insert_extent(handle, et, *logical_offset, block,
num_bits, flags, meta_ac);
...
clusters_to_add -= num_bits;
*logical_offset += num_bits;
if (clusters_to_add) {
err = clusters_to_add;
status = -EAGAIN;
reason = RESTART_TRANS;
}
```
Now, it's clear that `ocfs2_add_inode_data` can be called with `clusters_to_add = 1`. If `__ocfs2_claim_clusters` returns `num_bits = 1`, and `ocfs2_insert_extent` claims the cluster then it's all good. But, if `clusters_to_add` is 1, and the `num_bits` is also 1, the code returns 0. That's fine.
But what if, and this is where I'm getting hung up, what if `__ocfs2_claim_clusters` *returns* `num_bits = 0`? The code does have a check `BUG_ON(num_bits > clusters_to_add);`. This prevents the case where `num_bits` is greater than `clusters_to_add`. But nothing prevents a zero return from `__ocfs2_claim_clusters`. This seems like a potential flaw. I need to understand the implications of `num_bits` being zero in this scenario. This has to be addressed, so I'm going to jump into `__ocfs2_claim_clusters` and check that immediately.
|
| 21/2 |
2026/05/23 17:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/suballoc.c IncludeLines:true Name:__ocfs2_claim_clusters]
Results: map[SourceCode:2372: /*
2373: * min_bits - minimum contiguous chunk from this total allocation we
2374: * can handle. set to what we asked for originally for a full
2375: * contig. allocation, set to '1' to indicate we can deal with extents
2376: * of any size.
2377: */
2378: int __ocfs2_claim_clusters(handle_t *handle,
2379: struct ocfs2_alloc_context *ac,
2380: u32 min_clusters,
2381: u32 max_clusters,
2382: u32 *cluster_start,
2383: u32 *num_clusters)
2384: {
2385: int status;
2386: unsigned int bits_wanted = max_clusters;
2387: struct ocfs2_suballoc_result res = { .sr_blkno = 0, };
2388: struct ocfs2_super *osb = OCFS2_SB(ac->ac_inode->i_sb);
2389:
2390: BUG_ON(ac->ac_bits_given >= ac->ac_bits_wanted);
2391:
2392: BUG_ON(ac->ac_which != OCFS2_AC_USE_LOCAL
2393: && ac->ac_which != OCFS2_AC_USE_MAIN
2394: && ac->ac_which != OCFS2_AC_USE_MAIN_DISCONTIG);
2395:
2396: if (ac->ac_which == OCFS2_AC_USE_LOCAL) {
2397: WARN_ON(min_clusters > 1);
2398:
2399: status = ocfs2_claim_local_alloc_bits(osb,
2400: handle,
2401: ac,
2402: bits_wanted,
2403: cluster_start,
2404: num_clusters);
2405: if (!status)
2406: atomic_inc(&osb->alloc_stats.local_data);
2407: } else {
2408: if (min_clusters > (osb->bitmap_cpg - 1)) {
2409: /* The only paths asking for contiguousness
2410: * should know about this already. */
2411: mlog(ML_ERROR, "minimum allocation requested %u exceeds "
2412: "group bitmap size %u!\n", min_clusters,
2413: osb->bitmap_cpg);
2414: status = -ENOSPC;
2415: goto bail;
2416: }
2417: /* clamp the current request down to a realistic size. */
2418: if (bits_wanted > (osb->bitmap_cpg - 1))
2419: bits_wanted = osb->bitmap_cpg - 1;
2420:
2421: status = ocfs2_claim_suballoc_bits(ac,
2422: handle,
2423: bits_wanted,
2424: min_clusters,
2425: &res);
2426: if (!status) {
2427: BUG_ON(res.sr_blkno); /* cluster alloc can't set */
2428: *cluster_start =
2429: ocfs2_desc_bitmap_to_cluster_off(ac->ac_inode,
2430: res.sr_bg_blkno,
2431: res.sr_bit_offset);
2432: atomic_inc(&osb->alloc_stats.bitmap_data);
2433: *num_clusters = res.sr_bits;
2434: }
2435: }
2436: if (status < 0) {
2437: if (status != -ENOSPC)
2438: mlog_errno(status);
2439: goto bail;
2440: }
2441:
2442: ac->ac_bits_given += *num_clusters;
2443:
2444: bail:
2445: if (status)
2446: mlog_errno(status);
2447: return status;
2448: }
SourceFile:fs/ocfs2/suballoc.c]
|
| 22/2 |
2026/05/23 17:49 |
llm |
expert |
0m
Model:
Tokens:
input: 30525
output: 47
thoughts: 1204
Thoughts: **Debugging a Persistent Zero in `ocfs2_get_clusters`**
Okay, let's see. If `__ocfs2_claim_clusters` is successful, we know we've allocated at least one cluster, and `ocfs2_insert_extent` should have then added it to the extent map. So, following this, a subsequent `ocfs2_get_clusters` call should *definitely* find it. But something's going wrong: I'm seeing `*phys == 0` after this process, which shouldn't happen.
My initial thought was, what if `ocfs2_add_inode_data` is skipped? `ocfs2_write_cluster` has a path where if `new` and `clear_unwritten` are both zero, it simply calls `ocfs2_get_clusters` directly. But when would *that* happen? Looking at `ocfs2_populate_write_desc`, it looks like a scenario where `phys` is non-zero, and `ext_flags & OCFS2_EXT_UNWRITTEN` is zero. In this case `desc->c_new = 0` and `desc->c_clear_unwritten = 0`. So, if `ocfs2_get_clusters` *still* returns `0` in that context, something's seriously wrong.
Why would `ocfs2_get_clusters` return different results? The code's single-threaded, so a race seems unlikely. Could the extent map be corrupted, or cleared? I think a corruption would be readily visible.
Wait, looking closer at the calls, `ocfs2_write_cluster` *overwrites* the value of `desc->c_phys` with the result from `ocfs2_get_clusters`. So, no matter what happened before `ocfs2_get_clusters` call in `ocfs2_populate_write_desc`, it is overwritten by whatever `ocfs2_get_clusters` says in `ocfs2_write_cluster`. That explains how `*phys == 0` is being hit. The `BUG_ON` fires because `ocfs2_get_clusters` returned a zero.
Now, what if `new == 1`? Then `ocfs2_add_inode_data` is called, but does `ocfs2_add_inode_data` *always* insert an extent? Can it return success (0) but fail to actually insert the extent? Looking at `ocfs2_add_clusters_in_btree` in turn, that function calls `__ocfs2_claim_clusters`. If `clusters_to_add` is 1, `__ocfs2_claim_clusters` sets `num_bits = 1`, and then `ocfs2_insert_extent` is called. So, can *that* return success, but not actually insert? I need to revisit `ocfs2_insert_extent` to be sure. This feels like the right place to look deeper.
|
| 23/2 |
2026/05/23 17:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/alloc.c IncludeLines:true Name:ocfs2_do_insert_extent]
Results: map[SourceCode:4251: static int ocfs2_do_insert_extent(handle_t *handle,
4252: struct ocfs2_extent_tree *et,
4253: struct ocfs2_extent_rec *insert_rec,
4254: struct ocfs2_insert_type *type)
4255: {
4256: int ret, rotate = 0;
4257: u32 cpos;
4258: struct ocfs2_path *right_path = NULL;
4259: struct ocfs2_path *left_path = NULL;
4260: struct ocfs2_extent_list *el;
4261:
4262: el = et->et_root_el;
4263:
4264: ret = ocfs2_et_root_journal_access(handle, et,
4265: OCFS2_JOURNAL_ACCESS_WRITE);
4266: if (ret) {
4267: mlog_errno(ret);
4268: goto out;
4269: }
4270:
4271: if (le16_to_cpu(el->l_tree_depth) == 0) {
4272: ocfs2_insert_at_leaf(et, insert_rec, el, type);
4273: goto out_update_clusters;
4274: }
4275:
4276: right_path = ocfs2_new_path_from_et(et);
4277: if (!right_path) {
4278: ret = -ENOMEM;
4279: mlog_errno(ret);
4280: goto out;
4281: }
4282:
4283: /*
4284: * Determine the path to start with. Rotations need the
4285: * rightmost path, everything else can go directly to the
4286: * target leaf.
4287: */
4288: cpos = le32_to_cpu(insert_rec->e_cpos);
4289: if (type->ins_appending == APPEND_NONE &&
4290: type->ins_contig == CONTIG_NONE) {
4291: rotate = 1;
4292: cpos = UINT_MAX;
4293: }
4294:
4295: ret = ocfs2_find_path(et->et_ci, right_path, cpos);
4296: if (ret) {
4297: mlog_errno(ret);
4298: goto out;
4299: }
4300:
4301: /*
4302: * Rotations and appends need special treatment - they modify
4303: * parts of the tree's above them.
4304: *
4305: * Both might pass back a path immediate to the left of the
4306: * one being inserted to. This will be cause
4307: * ocfs2_insert_path() to modify the rightmost records of
4308: * left_path to account for an edge insert.
4309: *
4310: * XXX: When modifying this code, keep in mind that an insert
4311: * can wind up skipping both of these two special cases...
4312: */
4313: if (rotate) {
4314: ret = ocfs2_rotate_tree_right(handle, et, type->ins_split,
4315: le32_to_cpu(insert_rec->e_cpos),
4316: right_path, &left_path);
4317: if (ret) {
4318: mlog_errno(ret);
4319: goto out;
4320: }
4321:
4322: /*
4323: * ocfs2_rotate_tree_right() might have extended the
4324: * transaction without re-journaling our tree root.
4325: */
4326: ret = ocfs2_et_root_journal_access(handle, et,
4327: OCFS2_JOURNAL_ACCESS_WRITE);
4328: if (ret) {
4329: mlog_errno(ret);
4330: goto out;
4331: }
4332: } else if (type->ins_appending == APPEND_TAIL
4333: && type->ins_contig != CONTIG_LEFT) {
4334: ret = ocfs2_append_rec_to_path(handle, et, insert_rec,
4335: right_path, &left_path);
4336: if (ret) {
4337: mlog_errno(ret);
4338: goto out;
4339: }
4340: }
4341:
4342: ret = ocfs2_insert_path(handle, et, left_path, right_path,
4343: insert_rec, type);
4344: if (ret) {
4345: mlog_errno(ret);
4346: goto out;
4347: }
4348:
4349: out_update_clusters:
4350: if (type->ins_split == SPLIT_NONE)
4351: ocfs2_et_update_clusters(et,
4352: le16_to_cpu(insert_rec->e_leaf_clusters));
4353:
4354: ocfs2_journal_dirty(handle, et->et_root_bh);
4355:
4356: out:
4357: ocfs2_free_path(left_path);
4358: ocfs2_free_path(right_path);
4359:
4360: return ret;
4361: }
SourceFile:fs/ocfs2/alloc.c]
|
| 24/2 |
2026/05/23 17:50 |
llm |
expert |
0m
Model:
Tokens:
input: 34104
output: 49
thoughts: 58
Thoughts: Okay, here's my take on those thoughts, framed from my perspective as an expert in this area:
**Digging into the Extent Insertion Process**
Alright, so `ocfs2_do_insert_extent` seems to have done its job and successfully placed the extent into the tree. Good, good. Now, the follow-up is that `ocfs2_et_extent_map_insert` gets called. My brain immediately jumps to "let's verify what happens in `ocfs2_et_extent_map_insert`." I mean, the extent is *in* the tree, but how exactly is the extent map being updated to reflect this change? We need to make sure that the index tracking the extent is up to date and correct. Is this where the metadata for the extent map gets written? Any locking considerations, synchronization... those are all questions that need answering. It is time to look into that function directly to see how everything fits together. Time to dive deeper.
|
| 25/2 |
2026/05/23 17:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/alloc.c IncludeLines:true Name:ocfs2_et_extent_map_insert]
Results: map[SourceCode: 508: static inline void ocfs2_et_extent_map_insert(struct ocfs2_extent_tree *et,
509: struct ocfs2_extent_rec *rec)
510: {
511: if (et->et_ops->eo_extent_map_insert)
512: et->et_ops->eo_extent_map_insert(et, rec);
513: }
SourceFile:fs/ocfs2/alloc.c]
|
| 26/2 |
2026/05/23 17:50 |
llm |
expert |
0m
Model:
Tokens:
input: 34569
output: 50
thoughts: 20
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `eo_extent_map_insert` for `dinode`**
Okay, so I'm looking at `eo_extent_map_insert` and, specifically, how it interacts with a `dinode`. I need to refresh my mental model here. Essentially, I'm trying to figure out how this function fits into the overall scheme of extent management within the file system, particularly when dealing with i-node level operations. My primary questions are likely centered around:
* **Placement Strategy:** What's the core logic? Is this a simple insertion, or is there a sophisticated algorithm to optimize the extent map (e.g., merging, splitting, re-balancing the tree)? I'm betting there's some kind of tree-like structure involved, given the `extent_map` name. I want to see if the insertion maintains any order or if it is simple appending/prepending.
* **Contiguity Implications:** How does this function handle contiguous extents? Does it try to consolidate fragmented extents, or does it primarily work with individual blocks? Considering this deals with the `dinode` itself, it's probably related to how the on-disk format is defined, so I need to check for any updates or modifications.
* **Error Handling:** What kind of error conditions are considered? Does it check for space exhaustion, metadata corruption, or conflicting extents? I'll pay close attention to the return values and error codes to understand the resiliency of this insertion. Does it do things like transaction logging or journaling to ensure data integrity?
* **Concurrency Considerations:** Is this function thread-safe? Does it employ any locks or synchronization primitives to protect the extent map from concurrent modification? This will be crucial if this is going to be used in a highly concurrent environment.
* **Edge Cases:** I also need to be thinking about edge cases, like inserting an empty extent, inserting an extent that completely overlaps an existing one, or inserting an extent at the beginning/end of the map. I can anticipate where the subtle bugs will be hiding.
* **Performance:** I'm curious about the time complexity of this insertion; is it O(1), O(log n), or even O(n)? The larger the `extent_map`, the more critical the efficiency. This can quickly become a bottleneck, especially with large files or frequent writes.
I'll start by tracing the calls, looking at the data structures involved, and checking for any notable optimization techniques. Then I should be in a good position to understand how this is used at a low level.
|
| 27/2 |
2026/05/23 17:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/alloc.c IncludeLines:true Name:ocfs2_dinode_extent_map_insert]
Results: map[SourceCode: 199: static void ocfs2_dinode_extent_map_insert(struct ocfs2_extent_tree *et,
200: struct ocfs2_extent_rec *rec)
201: {
202: struct inode *inode = &cache_info_to_inode(et->et_ci)->vfs_inode;
203:
204: ocfs2_extent_map_insert_rec(inode, rec);
205: }
SourceFile:fs/ocfs2/alloc.c]
|
| 28/2 |
2026/05/23 17:50 |
llm |
expert |
0m
Model:
Tokens:
input: 35363
output: 51
thoughts: 52
Thoughts: Okay, here's my interpretation of these thoughts, as if they were my own:
**Working Through an OCFS2 Extent Insertion**
Alright, so I'm thinking about how this extent mapping works in OCFS2. The process starts with inserting the new extent information. My initial understanding is that this insertion goes directly into the extent map β simple enough. Then, `ocfs2_get_clusters` should be able to look at the extent map and successfully locate the extent details. But wait a minute... I'm just re-reading the code for `ocfs2_extent_map_insert_rec`. That function doesn't return anything! It's `void`. I need to ensure the insert doesn't fail, or at least that an error handling mechanism is being employed, because I can't check its return value. Time to re-evaluate how I'm handling potential insertion failures; this could be a major problem. I have to make sure I am prepared for this event.
|
| 29/2 |
2026/05/23 17:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/extent_map.c IncludeLines:true Name:ocfs2_extent_map_insert_rec]
Results: map[SourceCode: 205: /*
206: * In order to reduce complexity on the caller, this insert function
207: * is intentionally liberal in what it will accept.
208: *
209: * The only rule is that the truncate call *must* be used whenever
210: * records have been deleted. This avoids inserting overlapping
211: * records with different physical mappings.
212: */
213: void ocfs2_extent_map_insert_rec(struct inode *inode,
214: struct ocfs2_extent_rec *rec)
215: {
216: struct ocfs2_inode_info *oi = OCFS2_I(inode);
217: struct ocfs2_extent_map *em = &oi->ip_extent_map;
218: struct ocfs2_extent_map_item *emi, *new_emi = NULL;
219: struct ocfs2_extent_map_item ins;
220:
221: ins.ei_cpos = le32_to_cpu(rec->e_cpos);
222: ins.ei_phys = ocfs2_blocks_to_clusters(inode->i_sb,
223: le64_to_cpu(rec->e_blkno));
224: ins.ei_clusters = le16_to_cpu(rec->e_leaf_clusters);
225: ins.ei_flags = rec->e_flags;
226:
227: search:
228: spin_lock(&oi->ip_lock);
229:
230: list_for_each_entry(emi, &em->em_list, ei_list) {
231: if (ocfs2_try_to_merge_extent_map(emi, &ins)) {
232: list_move(&emi->ei_list, &em->em_list);
233: spin_unlock(&oi->ip_lock);
234: goto out;
235: }
236: }
237:
238: /*
239: * No item could be merged.
240: *
241: * Either allocate and add a new item, or overwrite the last recently
242: * inserted.
243: */
244:
245: if (em->em_num_items < OCFS2_MAX_EXTENT_MAP_ITEMS) {
246: if (new_emi == NULL) {
247: spin_unlock(&oi->ip_lock);
248:
249: new_emi = kmalloc(sizeof(*new_emi), GFP_NOFS);
250: if (new_emi == NULL)
251: goto out;
252:
253: goto search;
254: }
255:
256: ocfs2_copy_emi_fields(new_emi, &ins);
257: list_add(&new_emi->ei_list, &em->em_list);
258: em->em_num_items++;
259: new_emi = NULL;
260: } else {
261: BUG_ON(list_empty(&em->em_list) || em->em_num_items == 0);
262: emi = list_entry(em->em_list.prev,
263: struct ocfs2_extent_map_item, ei_list);
264: list_move(&emi->ei_list, &em->em_list);
265: ocfs2_copy_emi_fields(emi, &ins);
266: }
267:
268: spin_unlock(&oi->ip_lock);
269:
270: out:
271: kfree(new_emi);
272: }
SourceFile:fs/ocfs2/extent_map.c]
|
| 30/2 |
2026/05/23 17:50 |
llm |
expert |
0m
Model:
Tokens:
input: 36797
output: 50
thoughts: 589
Thoughts: **Investigating a Potential Cluster Allocation Vulnerability**
Okay, so I'm digging into `ocfs2_extent_map_insert_rec` and how it handles extent insertion. I noticed that `ocfs2_blocks_to_clusters` is used to translate the extent's block number (`e_blkno`) to its physical cluster address (`ei_phys`). My mind immediately jumps to the potential issue: if `e_blkno` isn't a multiple of the cluster size, could the resulting `ei_phys` actually be zero? That's the crux of this.
Wait, `e_blkno` is a *block* number, that's important. `ocfs2_blocks_to_clusters` is essentially shifting it. If `e_blkno` is small enough β specifically less than `clustersize / blocksize` β this shift *will* result in `ei_phys` being zero. This raises a red flag; is it even *possible* for the allocator to return a physical block number that translates to cluster zero? Usually, that's reserved for superblock information, so this warrants further investigation.
Let's check `__ocfs2_claim_clusters`. It calls either `ocfs2_claim_suballoc_bits` or `ocfs2_claim_local_alloc_bits`. Ah, if `ocfs2_claim_local_alloc_bits` returns a `cluster_start = 0`, we have a problem. Wait, `cluster_start` is already in *clusters*, that clarifies things.
Looking closer at `__ocfs2_claim_clusters`, if `cluster_start` is zero, then `block = ocfs2_clusters_to_blocks(osb->sb, bit_off);` will also be zero. Then, `ocfs2_insert_extent` would end up inserting an `e_blkno = 0`. That's bad. That's *really* bad. Because `e_blkno = 0` is treated as a hole by `ocfs2_get_clusters`! The code explicitly checks for `rec.e_blkno == 0ULL` and marks it as such.
So the million dollar question is, can `ocfs2_claim_local_alloc_bits` actually return a `cluster_start = 0`? That's the critical question that needs to be answered. Time to drill down into `ocfs2_claim_local_alloc_bits`.
|
| 31/2 |
2026/05/23 17:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/localalloc.c IncludeLines:true Name:ocfs2_claim_local_alloc_bits]
Results: map[SourceCode: 714: int ocfs2_claim_local_alloc_bits(struct ocfs2_super *osb,
715: handle_t *handle,
716: struct ocfs2_alloc_context *ac,
717: u32 bits_wanted,
718: u32 *bit_off,
719: u32 *num_bits)
720: {
721: int status, start;
722: struct inode *local_alloc_inode;
723: void *bitmap;
724: struct ocfs2_dinode *alloc;
725: struct ocfs2_local_alloc *la;
726:
727: BUG_ON(ac->ac_which != OCFS2_AC_USE_LOCAL);
728:
729: local_alloc_inode = ac->ac_inode;
730: alloc = (struct ocfs2_dinode *) osb->local_alloc_bh->b_data;
731: la = OCFS2_LOCAL_ALLOC(alloc);
732:
733: start = ocfs2_local_alloc_find_clear_bits(osb, alloc, &bits_wanted,
734: ac->ac_resv);
735: if (start == -1) {
736: /* TODO: Shouldn't we just BUG here? */
737: status = -ENOSPC;
738: mlog_errno(status);
739: goto bail;
740: }
741:
742: bitmap = la->la_bitmap;
743: *bit_off = le32_to_cpu(la->la_bm_off) + start;
744: *num_bits = bits_wanted;
745:
746: status = ocfs2_journal_access_di(handle,
747: INODE_CACHE(local_alloc_inode),
748: osb->local_alloc_bh,
749: OCFS2_JOURNAL_ACCESS_WRITE);
750: if (status < 0) {
751: mlog_errno(status);
752: goto bail;
753: }
754:
755: ocfs2_resmap_claimed_bits(&osb->osb_la_resmap, ac->ac_resv, start,
756: bits_wanted);
757:
758: while(bits_wanted--)
759: ocfs2_set_bit(start++, bitmap);
760:
761: le32_add_cpu(&alloc->id1.bitmap1.i_used, *num_bits);
762: ocfs2_journal_dirty(handle, osb->local_alloc_bh);
763:
764: bail:
765: if (status)
766: mlog_errno(status);
767: return status;
768: }
SourceFile:fs/ocfs2/localalloc.c]
|
| 32/2 |
2026/05/23 17:50 |
llm |
expert |
0m
Model:
Tokens:
input: 38858
output: 48
thoughts: 324
Thoughts: **Analyzing a Potential Vulnerability in OCFS2 Local Allocation**
Okay, let's break this down. I'm looking at this bit of code: `*bit_off = le32_to_cpu(la->la_bm_off) + start;`. My immediate thought is, if `la->la_bm_off` is zero and `start` is also zero, then `*bit_off` will be zero. That zero value then gets passed into `ocfs2_clusters_to_blocks`, and the resulting `block` will also be zero.
Now, `ocfs2_insert_extent` ends up storing `e_blkno = 0`. Here's where the red flag goes up. `e_blkno = 0` implies a hole, which is often intended as such! However, a hole at block zero is problematic, isn't it?
The real question is whether `la->la_bm_off` can *actually* be zero. Where is this value coming from? It's being read from the local allocation dinode. Ah, now I see the potential exploit. If someone maliciously crafts the filesystem, they could set `la_bm_off` to zero. That would be a trivial change to any block and hence makes the vulnerability trivially exploitable.
Okay, let's think about the intended use. `la_bm_off` represents the starting cluster offset of the local allocation window. If that window starts at cluster zero, then the system is technically allowed to allocate cluster zero. But, and here's the catch, cluster zero *contains* the superblock. We know the superblock has to be at block 2, which is contained in cluster 0. It's impossible for cluster zero not to contain the superblock. So, a *valid* filesystem will never have `la_bm_off = 0` unless the filesystem itself is invalid.
However, a maliciously crafted filesystem *could* absolutely have `la_bm_off = 0`. That's where the problem lies.
Now, the important question to validate the potential threat, and determine the severity, is... is there any validation performed on `la->la_bm_off` *anywhere* before this calculation? If not, we have a potentially serious vulnerability.
|
| 33/2 |
2026/05/23 17:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/localalloc.c IncludeLines:true Name:ocfs2_load_local_alloc]
Results: map[SourceCode: 269: int ocfs2_load_local_alloc(struct ocfs2_super *osb)
270: {
271: int status = 0;
272: struct ocfs2_dinode *alloc = NULL;
273: struct buffer_head *alloc_bh = NULL;
274: u32 num_used;
275: struct inode *inode = NULL;
276: struct ocfs2_local_alloc *la;
277:
278: if (osb->local_alloc_bits == 0)
279: goto bail;
280:
281: if (osb->local_alloc_bits >= osb->bitmap_cpg) {
282: mlog(ML_NOTICE, "Requested local alloc window %d is larger "
283: "than max possible %u. Using defaults.\n",
284: osb->local_alloc_bits, (osb->bitmap_cpg - 1));
285: osb->local_alloc_bits =
286: ocfs2_megabytes_to_clusters(osb->sb,
287: ocfs2_la_default_mb(osb));
288: }
289:
290: /* read the alloc off disk */
291: inode = ocfs2_get_system_file_inode(osb, LOCAL_ALLOC_SYSTEM_INODE,
292: osb->slot_num);
293: if (!inode) {
294: status = -EINVAL;
295: mlog_errno(status);
296: goto bail;
297: }
298:
299: status = ocfs2_read_inode_block_full(inode, &alloc_bh,
300: OCFS2_BH_IGNORE_CACHE);
301: if (status < 0) {
302: mlog_errno(status);
303: goto bail;
304: }
305:
306: alloc = (struct ocfs2_dinode *) alloc_bh->b_data;
307: la = OCFS2_LOCAL_ALLOC(alloc);
308:
309: if (!(le32_to_cpu(alloc->i_flags) &
310: (OCFS2_LOCAL_ALLOC_FL|OCFS2_BITMAP_FL))) {
311: mlog(ML_ERROR, "Invalid local alloc inode, %llu\n",
312: (unsigned long long)OCFS2_I(inode)->ip_blkno);
313: status = -EINVAL;
314: goto bail;
315: }
316:
317: if ((la->la_size == 0) ||
318: (le16_to_cpu(la->la_size) > ocfs2_local_alloc_size(inode->i_sb))) {
319: mlog(ML_ERROR, "Local alloc size is invalid (la_size = %u)\n",
320: le16_to_cpu(la->la_size));
321: status = -EINVAL;
322: goto bail;
323: }
324:
325: /* do a little verification. */
326: num_used = ocfs2_local_alloc_count_bits(alloc);
327:
328: /* hopefully the local alloc has always been recovered before
329: * we load it. */
330: if (num_used
331: || alloc->id1.bitmap1.i_used
332: || alloc->id1.bitmap1.i_total
333: || la->la_bm_off) {
334: mlog(ML_ERROR, "inconsistent detected, clean journal with"
335: " unrecovered local alloc, please run fsck.ocfs2!\n"
336: "found = %u, set = %u, taken = %u, off = %u\n",
337: num_used, le32_to_cpu(alloc->id1.bitmap1.i_used),
338: le32_to_cpu(alloc->id1.bitmap1.i_total),
339: le32_to_cpu(OCFS2_LOCAL_ALLOC(alloc)->la_bm_off));
340:
341: status = -EINVAL;
342: goto bail;
343: }
344:
345: osb->local_alloc_bh = alloc_bh;
346: osb->local_alloc_state = OCFS2_LA_ENABLED;
347:
348: bail:
349: if (status < 0)
350: brelse(alloc_bh);
351: iput(inode);
352:
353: trace_ocfs2_load_local_alloc(osb->local_alloc_bits);
354:
355: if (status)
356: mlog_errno(status);
357: return status;
358: }
SourceFile:fs/ocfs2/localalloc.c]
|
| 34/2 |
2026/05/23 17:50 |
llm |
expert |
0m
Model:
Tokens:
input: 41231
output: 50
thoughts: 293
Thoughts: **Analyzing the Local Allocation Loading Process**
Okay, so I'm looking at this bit of code, `ocfs2_load_local_alloc`, and something's immediately jumping out at me. The check here, right at the beginning, it's explicitly verifying that `la->la_bm_off` is zero. That's a crucial constraint. The code's saying, "Hey, if any of these values are non-zero, especially `la_bm_off`, we have a serious integrity problem, an inconsistency that needs a full file system check."
So, the fact that `la_bm_off` *must* be zero when the local allocation structure is loaded is the initial state. No offset to the main bitmap has been assigned.
The question then is, how does this offset actually *get* a non-zero value? It's when the system reserves local allocation bits. It reserves them from the main bitmap. It's the process of linking the local allocation to the main bitmap that *sets* the offset. This initial check is enforcing a clean slate, ensuring no unexpected offset is present on load, indicating a potentially corrupt state.
|
| 35/2 |
2026/05/23 17:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/localalloc.c IncludeLines:true Name:ocfs2_reserve_local_alloc_bits]
Results: map[SourceCode: 608: /*
609: * make sure we've got at least bits_wanted contiguous bits in the
610: * local alloc. You lose them when you drop i_rwsem.
611: *
612: * We will add ourselves to the transaction passed in, but may start
613: * our own in order to shift windows.
614: */
615: int ocfs2_reserve_local_alloc_bits(struct ocfs2_super *osb,
616: u32 bits_wanted,
617: struct ocfs2_alloc_context *ac)
618: {
619: int status;
620: struct ocfs2_dinode *alloc;
621: struct inode *local_alloc_inode;
622: unsigned int free_bits;
623:
624: BUG_ON(!ac);
625:
626: local_alloc_inode =
627: ocfs2_get_system_file_inode(osb,
628: LOCAL_ALLOC_SYSTEM_INODE,
629: osb->slot_num);
630: if (!local_alloc_inode) {
631: status = -ENOENT;
632: mlog_errno(status);
633: goto bail;
634: }
635:
636: inode_lock(local_alloc_inode);
637:
638: /*
639: * We must double check state and allocator bits because
640: * another process may have changed them while holding i_rwsem.
641: */
642: spin_lock(&osb->osb_lock);
643: if (!ocfs2_la_state_enabled(osb) ||
644: (bits_wanted > osb->local_alloc_bits)) {
645: spin_unlock(&osb->osb_lock);
646: status = -ENOSPC;
647: goto bail;
648: }
649: spin_unlock(&osb->osb_lock);
650:
651: alloc = (struct ocfs2_dinode *) osb->local_alloc_bh->b_data;
652:
653: #ifdef CONFIG_OCFS2_DEBUG_FS
654: if (le32_to_cpu(alloc->id1.bitmap1.i_used) !=
655: ocfs2_local_alloc_count_bits(alloc)) {
656: status = ocfs2_error(osb->sb, "local alloc inode %llu says it has %u used bits, but a count shows %u\n",
657: (unsigned long long)le64_to_cpu(alloc->i_blkno),
658: le32_to_cpu(alloc->id1.bitmap1.i_used),
659: ocfs2_local_alloc_count_bits(alloc));
660: goto bail;
661: }
662: #endif
663:
664: free_bits = le32_to_cpu(alloc->id1.bitmap1.i_total) -
665: le32_to_cpu(alloc->id1.bitmap1.i_used);
666: if (bits_wanted > free_bits) {
667: /* uhoh, window change time. */
668: status =
669: ocfs2_local_alloc_slide_window(osb, local_alloc_inode);
670: if (status < 0) {
671: if (status != -ENOSPC)
672: mlog_errno(status);
673: goto bail;
674: }
675:
676: /*
677: * Under certain conditions, the window slide code
678: * might have reduced the number of bits available or
679: * disabled the local alloc entirely. Re-check
680: * here and return -ENOSPC if necessary.
681: */
682: status = -ENOSPC;
683: if (!ocfs2_la_state_enabled(osb))
684: goto bail;
685:
686: free_bits = le32_to_cpu(alloc->id1.bitmap1.i_total) -
687: le32_to_cpu(alloc->id1.bitmap1.i_used);
688: if (bits_wanted > free_bits)
689: goto bail;
690: }
691:
692: ac->ac_inode = local_alloc_inode;
693: /* We should never use localalloc from another slot */
694: ac->ac_alloc_slot = osb->slot_num;
695: ac->ac_which = OCFS2_AC_USE_LOCAL;
696: get_bh(osb->local_alloc_bh);
697: ac->ac_bh = osb->local_alloc_bh;
698: status = 0;
699: bail:
700: if (status < 0 && local_alloc_inode) {
701: inode_unlock(local_alloc_inode);
702: iput(local_alloc_inode);
703: }
704:
705: trace_ocfs2_reserve_local_alloc_bits(
706: (unsigned long long)ac->ac_max_block,
707: bits_wanted, osb->slot_num, status);
708:
709: if (status)
710: mlog_errno(status);
711: return status;
712: }
SourceFile:fs/ocfs2/localalloc.c]
|
| 36/2 |
2026/05/23 17:50 |
llm |
expert |
0m
Model:
Tokens:
input: 43496
output: 50
thoughts: 43
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in this area:
**Analyzing the Allocation Strategy**
Alright, so the core logic seems to hinge on whether the requested number of bits, `bits_wanted`, exceeds the available free bits, `free_bits`. The code's immediately diving into `ocfs2_local_alloc_slide_window` if that condition is true. That means the allocation strategy is adapting based on immediate availability. If the request is too large for the current "window" of free bits, it's resorting to this `slide_window` function.
My next step would obviously be to understand *exactly* what `ocfs2_local_alloc_slide_window` does. The name suggests some sort of shifting or sliding of a window to find enough free bits. I'll need to look at its implementation β the logic behind how it handles insufficient local free bits. Is it attempting to find a larger contiguous chunk elsewhere, breaking up the request, deferring it, or possibly even triggering more fundamental allocation (maybe on-disk?). I would be expecting to find details of any kind of fragmentation in this function. I need to understand the side effects and the potential overhead this "sliding window" approach introduces, especially in terms of performance and potential for fragmentation.
|
| 37/2 |
2026/05/23 17:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/localalloc.c IncludeLines:true Name:ocfs2_local_alloc_slide_window]
Results: map[SourceCode:1228: /* Note that we do *NOT* lock the local alloc inode here as
1229: * it's been locked already for us. */
1230: static int ocfs2_local_alloc_slide_window(struct ocfs2_super *osb,
1231: struct inode *local_alloc_inode)
1232: {
1233: int status = 0;
1234: struct buffer_head *main_bm_bh = NULL;
1235: struct inode *main_bm_inode = NULL;
1236: handle_t *handle = NULL;
1237: struct ocfs2_dinode *alloc;
1238: struct ocfs2_dinode *alloc_copy = NULL;
1239: struct ocfs2_alloc_context *ac = NULL;
1240:
1241: ocfs2_recalc_la_window(osb, OCFS2_LA_EVENT_SLIDE);
1242:
1243: /* This will lock the main bitmap for us. */
1244: status = ocfs2_local_alloc_reserve_for_window(osb,
1245: &ac,
1246: &main_bm_inode,
1247: &main_bm_bh);
1248: if (status < 0) {
1249: if (status != -ENOSPC)
1250: mlog_errno(status);
1251: goto bail;
1252: }
1253:
1254: handle = ocfs2_start_trans(osb, OCFS2_WINDOW_MOVE_CREDITS);
1255: if (IS_ERR(handle)) {
1256: status = PTR_ERR(handle);
1257: handle = NULL;
1258: mlog_errno(status);
1259: goto bail;
1260: }
1261:
1262: alloc = (struct ocfs2_dinode *) osb->local_alloc_bh->b_data;
1263:
1264: /* We want to clear the local alloc before doing anything
1265: * else, so that if we error later during this operation,
1266: * local alloc shutdown won't try to double free main bitmap
1267: * bits. Make a copy so the sync function knows which bits to
1268: * free. */
1269: alloc_copy = kmemdup(alloc, osb->local_alloc_bh->b_size, GFP_NOFS);
1270: if (!alloc_copy) {
1271: status = -ENOMEM;
1272: mlog_errno(status);
1273: goto bail;
1274: }
1275:
1276: status = ocfs2_journal_access_di(handle,
1277: INODE_CACHE(local_alloc_inode),
1278: osb->local_alloc_bh,
1279: OCFS2_JOURNAL_ACCESS_WRITE);
1280: if (status < 0) {
1281: mlog_errno(status);
1282: goto bail;
1283: }
1284:
1285: ocfs2_clear_local_alloc(alloc);
1286: ocfs2_journal_dirty(handle, osb->local_alloc_bh);
1287:
1288: status = ocfs2_sync_local_to_main(osb, handle, alloc_copy,
1289: main_bm_inode, main_bm_bh);
1290: if (status < 0) {
1291: mlog_errno(status);
1292: goto bail;
1293: }
1294:
1295: status = ocfs2_local_alloc_new_window(osb, handle, ac);
1296: if (status < 0) {
1297: if (status != -ENOSPC)
1298: mlog_errno(status);
1299: goto bail;
1300: }
1301:
1302: atomic_inc(&osb->alloc_stats.moves);
1303:
1304: bail:
1305: if (handle)
1306: ocfs2_commit_trans(osb, handle);
1307:
1308: brelse(main_bm_bh);
1309:
1310: iput(main_bm_inode);
1311: kfree(alloc_copy);
1312:
1313: if (ac)
1314: ocfs2_free_alloc_context(ac);
1315:
1316: if (status)
1317: mlog_errno(status);
1318: return status;
1319: }
SourceFile:fs/ocfs2/localalloc.c]
|
| 38/2 |
2026/05/23 17:50 |
llm |
expert |
0m
Model:
Tokens:
input: 45260
output: 50
thoughts: 15
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in file system internals or OCFS2:
**OCFS2 Allocation Window Creation**
Alright, so the code's calling `ocfs2_local_alloc_new_window`. This tells me we're dealing with a local allocation strategy within OCFS2. The function name itself is pretty clear - we're creating a new "window" for allocation. In OCFS2, these allocation windows are the foundation of how it keeps track of free space within the volume. They are a way to manage extent mapping, block allocation, and ensure that new data blocks are written in a specific zone or region on the disk, making fragmentation less likely. This implies it is a local allocation within the context of a particular node, not a global operation across the cluster, or at least it is the start of a process to get that data. I know that I need to dive into the specific arguments passed to `ocfs2_local_alloc_new_window` to understand *why* this window is being created. Are we extending an existing file, creating a new file, or possibly handling a metadata operation? I will need to look to that next to get a full picture. However, at a glance, this is the building block for file storage within OCFS2's design.
|
| 39/2 |
2026/05/23 17:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/localalloc.c IncludeLines:true Name:ocfs2_local_alloc_new_window]
Results: map[SourceCode:1139: /*
1140: * pass it the bitmap lock in lock_bh if you have it.
1141: */
1142: static int ocfs2_local_alloc_new_window(struct ocfs2_super *osb,
1143: handle_t *handle,
1144: struct ocfs2_alloc_context *ac)
1145: {
1146: int status = 0;
1147: u32 cluster_off, cluster_count;
1148: struct ocfs2_dinode *alloc = NULL;
1149: struct ocfs2_local_alloc *la;
1150:
1151: alloc = (struct ocfs2_dinode *) osb->local_alloc_bh->b_data;
1152: la = OCFS2_LOCAL_ALLOC(alloc);
1153:
1154: trace_ocfs2_local_alloc_new_window(
1155: le32_to_cpu(alloc->id1.bitmap1.i_total),
1156: osb->local_alloc_bits);
1157:
1158: /* Instruct the allocation code to try the most recently used
1159: * cluster group. We'll re-record the group used this pass
1160: * below. */
1161: ac->ac_last_group = osb->la_last_gd;
1162:
1163: /* we used the generic suballoc reserve function, but we set
1164: * everything up nicely, so there's no reason why we can't use
1165: * the more specific cluster api to claim bits. */
1166: status = ocfs2_claim_clusters(handle, ac, osb->local_alloc_bits,
1167: &cluster_off, &cluster_count);
1168: if (status == -ENOSPC) {
1169: retry_enospc:
1170: /*
1171: * Note: We could also try syncing the journal here to
1172: * allow use of any free bits which the current
1173: * transaction can't give us access to. --Mark
1174: */
1175: if (ocfs2_recalc_la_window(osb, OCFS2_LA_EVENT_FRAGMENTED) ==
1176: OCFS2_LA_DISABLED)
1177: goto bail;
1178:
1179: ac->ac_bits_wanted = osb->local_alloc_bits;
1180: status = ocfs2_claim_clusters(handle, ac,
1181: osb->local_alloc_bits,
1182: &cluster_off,
1183: &cluster_count);
1184: if (status == -ENOSPC)
1185: goto retry_enospc;
1186: /*
1187: * We only shrunk the *minimum* number of in our
1188: * request - it's entirely possible that the allocator
1189: * might give us more than we asked for.
1190: */
1191: if (status == 0) {
1192: spin_lock(&osb->osb_lock);
1193: osb->local_alloc_bits = cluster_count;
1194: spin_unlock(&osb->osb_lock);
1195: }
1196: }
1197: if (status < 0) {
1198: if (status != -ENOSPC)
1199: mlog_errno(status);
1200: goto bail;
1201: }
1202:
1203: osb->la_last_gd = ac->ac_last_group;
1204:
1205: la->la_bm_off = cpu_to_le32(cluster_off);
1206: alloc->id1.bitmap1.i_total = cpu_to_le32(cluster_count);
1207: /* just in case... In the future when we find space ourselves,
1208: * we don't have to get all contiguous -- but we'll have to
1209: * set all previously used bits in bitmap and update
1210: * la_bits_set before setting the bits in the main bitmap. */
1211: alloc->id1.bitmap1.i_used = 0;
1212: memset(OCFS2_LOCAL_ALLOC(alloc)->la_bitmap, 0,
1213: le16_to_cpu(la->la_size));
1214:
1215: ocfs2_resmap_restart(&osb->osb_la_resmap, cluster_count,
1216: OCFS2_LOCAL_ALLOC(alloc)->la_bitmap);
1217:
1218: trace_ocfs2_local_alloc_new_window_result(
1219: le32_to_cpu(OCFS2_LOCAL_ALLOC(alloc)->la_bm_off),
1220: le32_to_cpu(alloc->id1.bitmap1.i_total));
1221:
1222: bail:
1223: if (status)
1224: mlog_errno(status);
1225: return status;
1226: }
SourceFile:fs/ocfs2/localalloc.c]
|
| 40/2 |
2026/05/23 17:51 |
llm |
expert |
0m
Model:
Tokens:
input: 47119
output: 46
thoughts: 534
Thoughts: **Analyzing a Potential OCFS2 Vulnerability**
Okay, so I'm digging into this code and I've got a scenario forming in my head that I need to verify. It looks like the `ocfs2_claim_clusters` function is the point of entry here. It calls `__ocfs2_claim_clusters`, which in turn, is calling `ocfs2_claim_suballoc_bits`. This bit is critical. If `ocfs2_claim_clusters` returns `cluster_off = 0`, then `la_bm_off` also becomes 0, which looks suspicious.
My concern is whether `ocfs2_claim_clusters` *can* return `cluster_off = 0`. I know that `ocfs2_claim_suballoc_bits` is used for suballocators, including the main bitmap. Here's where it gets interesting: the main bitmap's bit 0 corresponds to cluster 0, and that's where the superblock lives. Cluster 0 should *never* be free.
However, a maliciously crafted filesystem image *could* have bit 0 free in the main bitmap. If that's the case, then `ocfs2_claim_clusters` would allocate it, and `la_bm_off` would become 0. Then, the code in `ocfs2_claim_local_alloc_bits` would allocate bit 0 from the local allocator, resulting in `bit_off = 0`. This then means that when inserting the extent in `ocfs2_insert_extent` we are going to use an `e_blkno` of 0.
Now here's the kicker: `ocfs2_get_clusters` would read `e_blkno = 0` and treat it as a hole. Then, `ocfs2_write_cluster` ends up calling BUG_ON(*phys == 0).
I need to confirm this. If `e_blkno == 0` is treated as a hole, then allocating block 0 is definitely a bug. I need to see if there's *any* check in this code that prevents the allocation of block 0. Looking at `ocfs2_claim_suballoc_bits`, I see it just searches for the first free bit in the bitmap. There's no inherent protection against allocating bit 0, even if the bitmap is corrupted. So, if bit 0 in the global bitmap inode *is* free, it will be allocated. And if bit 0 is allocated it must mean the superblock is being overwritten which is a serious security issue.
So, the root of the issue is in the global bitmap being loaded, but is there any validation performed? I need to check how the global bitmap is loaded and if there are any integrity checks.
|
| 41/2 |
2026/05/23 17:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/suballoc.c IncludeLines:true Name:ocfs2_reserve_clusters]
Results: map[SourceCode:1225: int ocfs2_reserve_clusters(struct ocfs2_super *osb,
1226: u32 bits_wanted,
1227: struct ocfs2_alloc_context **ac)
1228: {
1229: return ocfs2_reserve_clusters_with_limit(osb, bits_wanted, 0,
1230: ALLOC_NEW_GROUP, ac);
1231: }
SourceFile:fs/ocfs2/suballoc.c]
|
| 42/2 |
2026/05/23 17:51 |
llm |
expert |
0m
Model:
Tokens:
input: 48453
output: 50
thoughts: 17
Thoughts: Okay, let's dive into this. Here's my thought process on checking `ocfs2_reserve_clusters_with_limit`, as if I were considering it myself:
**Analyzing the Cluster Reservation Limit Function**
Alright, time to get into the nitty-gritty and take a look at `ocfs2_reserve_clusters_with_limit`. My initial question is always, "What problem is this function designed to solve?" Given its name, it's pretty clear: cluster reservation, with some kind of controlled limit involved. I need to understand *how* that limit is implemented and, more importantly, *why*. Is this for fairness across nodes? Is it a way to prevent starvation? Or maybe it's just a mechanism to avoid excessive allocation? Understanding the *why* is key to evaluating its effectiveness.
I'll start by tracing the function's call sites and dependencies. Where is it used in the overall OCFS2 ecosystem? Which other functions rely on its output? Are there any potential bottlenecks or race conditions I should be on the lookout for?
Next, I need to look at the internal logic. What are the specific parameters? What checks are performed? How does it actually calculate the amount to reserve? Are there any obvious areas for improvement or potential pitfalls? Are there any error conditions that are not being properly handled? Is this a potential point of failure for the cluster if not implemented and/or implemented correctly?
I'll also keep the bigger picture in mind. How does this fit into the broader cluster management strategy? Does it align with the overall allocation and deallocation strategy? How does it interact with other resources like inodes, blocks, and extents?
Essentially, I want to confirm that this is a well-designed piece of code that does what it's supposed to do efficiently and safely, without introducing any new issues or limitations to the overall system. Time to dig in!
|
| 43/2 |
2026/05/23 17:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/suballoc.c IncludeLines:true Name:ocfs2_reserve_clusters_with_limit]
Results: map[SourceCode:1144: /* Callers don't need to care which bitmap (local alloc or main) to
1145: * use so we figure it out for them, but unfortunately this clutters
1146: * things a bit. */
1147: static int ocfs2_reserve_clusters_with_limit(struct ocfs2_super *osb,
1148: u32 bits_wanted, u64 max_block,
1149: int flags,
1150: struct ocfs2_alloc_context **ac)
1151: {
1152: int status, ret = 0;
1153: int retried = 0;
1154:
1155: *ac = kzalloc(sizeof(struct ocfs2_alloc_context), GFP_KERNEL);
1156: if (!(*ac)) {
1157: status = -ENOMEM;
1158: mlog_errno(status);
1159: goto bail;
1160: }
1161:
1162: (*ac)->ac_bits_wanted = bits_wanted;
1163: (*ac)->ac_max_block = max_block;
1164:
1165: status = -ENOSPC;
1166: if (!(flags & ALLOC_GROUPS_FROM_GLOBAL) &&
1167: ocfs2_alloc_should_use_local(osb, bits_wanted)) {
1168: status = ocfs2_reserve_local_alloc_bits(osb,
1169: bits_wanted,
1170: *ac);
1171: if ((status < 0) && (status != -ENOSPC)) {
1172: mlog_errno(status);
1173: goto bail;
1174: }
1175: }
1176:
1177: if (status == -ENOSPC) {
1178: retry:
1179: status = ocfs2_reserve_cluster_bitmap_bits(osb, *ac);
1180: /* Retry if there is sufficient space cached in truncate log */
1181: if (status == -ENOSPC && !retried) {
1182: retried = 1;
1183: ocfs2_inode_unlock((*ac)->ac_inode, 1);
1184: inode_unlock((*ac)->ac_inode);
1185:
1186: ret = ocfs2_try_to_free_truncate_log(osb, bits_wanted);
1187: if (ret == 1) {
1188: iput((*ac)->ac_inode);
1189: (*ac)->ac_inode = NULL;
1190: goto retry;
1191: }
1192:
1193: if (ret < 0)
1194: mlog_errno(ret);
1195:
1196: inode_lock((*ac)->ac_inode);
1197: ret = ocfs2_inode_lock((*ac)->ac_inode, NULL, 1);
1198: if (ret < 0) {
1199: mlog_errno(ret);
1200: inode_unlock((*ac)->ac_inode);
1201: iput((*ac)->ac_inode);
1202: (*ac)->ac_inode = NULL;
1203: goto bail;
1204: }
1205: }
1206: if (status < 0) {
1207: if (status != -ENOSPC)
1208: mlog_errno(status);
1209: goto bail;
1210: }
1211: }
1212:
1213: status = 0;
1214: bail:
1215: if ((status < 0) && *ac) {
1216: ocfs2_free_alloc_context(*ac);
1217: *ac = NULL;
1218: }
1219:
1220: if (status)
1221: mlog_errno(status);
1222: return status;
1223: }
SourceFile:fs/ocfs2/suballoc.c]
|
| 44/2 |
2026/05/23 17:51 |
llm |
expert |
0m
Model:
Tokens:
input: 50110
output: 49
thoughts: 832
Thoughts: **Analyzing a Potential Zero-Block Allocation Vulnerability in OCFS2**
Okay, so I've been digging into this OCFS2 issue, and here's what I'm seeing: it boils down to the filesystem possibly returning block zero during an allocation, which is treated as a hole, leading to a kernel panic. Specifically, if the global bitmap is maliciously set with bit zero free, or, in many other cases, an allocation returns 0 as the cluster offset, the allocation process ends up with an `e_blkno = 0`. The function `ocfs2_get_clusters` then interprets this `e_blkno = 0` as a hole. This then throws a `BUG_ON` in `ocfs2_write_cluster`, resulting in the panic.
I initially thought this might be limited to scenarios with the local allocation bitmap, but I realized that it's much broader. Any allocation that uses the main bitmap can result in bit zero being returned if that bit is free. The same would apply if the process uses the extent tree or `data_ac` structure. It's really any call to `__ocfs2_claim_clusters` that can lead to this issue. The essential vulnerability is that the code explicitly considers `e_blkno == 0` a hole. This means block 0 can never be used, as it will be immediately read back as a hole, which is the root cause.
My initial thought was to see if there were *other* ways `ocfs2_get_clusters` could return a physical block address of zero. I considered the potential of `ocfs2_add_inode_data` failing and returning 0, or `__ocfs2_claim_clusters` returning 0. That second thought was the breakthrough. It shows the main bitmap and any allocation process that could lead to this. That leads to the `e_blkno = 0` hole being entered into the tree.
So, I've confirmed that `ocfs2_get_clusters` specifically treats `e_blkno == 0` as a hole, which triggers the subsequent issue. It looks like it is not an issue of corruption, but that is a definite vulnerability.
From a security standpoint, it's a denial-of-service vulnerability. While it causes a kernel panic, there doesn't seem to be a vector for privilege escalation or memory corruption. The panic is immediate due to the `BUG_ON(*phys == 0)` check. There's no use-after-free or out-of-bounds access.
I also checked to see if the extent map could cause this issue, but I see that it uses `ocfs2_blocks_to_clusters(inode->i_sb, le64_to_cpu(rec->e_blkno))`, and this function can also return zero if a block of 0 is passed in, resulting in a zero block. This verifies the issue, and, again, is linked to the 0 being returned in the allocation process.
|
| 45/2 |
2026/05/23 17:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/extent_map.c IncludeLines:true Name:ocfs2_extent_map_lookup]
Results: map[SourceCode: 69: static int ocfs2_extent_map_lookup(struct inode *inode, unsigned int cpos,
70: unsigned int *phys, unsigned int *len,
71: unsigned int *flags)
72: {
73: unsigned int coff;
74: struct ocfs2_inode_info *oi = OCFS2_I(inode);
75: struct ocfs2_extent_map_item *emi;
76:
77: spin_lock(&oi->ip_lock);
78:
79: __ocfs2_extent_map_lookup(&oi->ip_extent_map, cpos, &emi);
80: if (emi) {
81: coff = cpos - emi->ei_cpos;
82: *phys = emi->ei_phys + coff;
83: if (len)
84: *len = emi->ei_clusters - coff;
85: if (flags)
86: *flags = emi->ei_flags;
87: }
88:
89: spin_unlock(&oi->ip_lock);
90:
91: if (emi == NULL)
92: return -ENOENT;
93:
94: return 0;
95: }
SourceFile:fs/ocfs2/extent_map.c]
|
| 46/2 |
2026/05/23 17:51 |
llm |
expert |
0m
Model:
Tokens:
input: 52035
output: 21
thoughts: 494
Thoughts: **Analyzing the Ocfs2 BUG_ON**
Okay, let's break this down. If `ei_phys` is zero, then `*phys` becomes zero. `ocfs2_get_clusters` will return success, and `*phys` remains at zero. Then, `ocfs2_write_cluster` does a `BUG_ON(*phys == 0)`. So, yeah, if `e_blkno` is zero, that `BUG_ON` fires.
The question then becomes, can this be triggered legitimately, without a maliciously crafted filesystem? If we're dealing with a normal filesystem, block zero is the superblock. The global bitmap for cluster zero should *always* have bit zero set to allocated. The allocator would *never* allocate cluster zero, so `e_blkno` would *never* be zero in a normal scenario. This looks like a bug that requires a specifically crafted filesystem image with bit zero in the global bitmap set to free.
Aha! The reproducer... it mounts an OCFS2 image:
```c
syz_mount_image(/*fs=*/0x200000004440, /*dir=*/0x200000000040, /*flags=MS_NODIRATIME|MS_MANDLOCK|MS_DIRSYNC*/0x8c0, /*opts=*/0x200000000400, /*chdir=*/1, /*size=*/0x4446, /*img=*/0x2000000088c0);
```
Yes, that confirms it: the reproducer uses a malicious filesystem image.
Now, the important question is: what's the impact?
* Exploitable? No, it's just a kernel panic from a `BUG_ON`, so it doesn't lead to memory corruption.
* Denial of Service? Yes, definitely. Kernel panics *are* a DoS.
* Accessible from Unprivileged Processes? No, mounting a filesystem requires `CAP_SYS_ADMIN` in the initial user namespace. Wait, could it be mounted inside a user namespace?
* Accessible From User Namespaces? We need to verify if OCFS2 supports mounting in user namespaces. I'll need to check if `FS_USERNS_MOUNT` is set for ocfs2.
|
| 47/2 |
2026/05/23 17:51 |
tool |
grepper |
0m
Args: map[Expression:FS_USERNS_MOUNT]
Results: map[Output:drivers/android/binder/rust_binderfs.c=813=static struct file_system_type binder_fs_type = {
--
drivers/android/binder/rust_binderfs.c-817- .kill_sb = binderfs_kill_super,
drivers/android/binder/rust_binderfs.c:818: .fs_flags = FS_USERNS_MOUNT,
drivers/android/binder/rust_binderfs.c-819-};
--
drivers/android/binderfs.c=790=static struct file_system_type binder_fs_type = {
--
drivers/android/binderfs.c-794- .kill_sb = binderfs_kill_super,
drivers/android/binderfs.c:795: .fs_flags = FS_USERNS_MOUNT,
drivers/android/binderfs.c-796-};
--
fs/binfmt_misc.c=1026=static struct file_system_type bm_fs_type = {
--
fs/binfmt_misc.c-1029- .init_fs_context = bm_init_fs_context,
fs/binfmt_misc.c:1030: .fs_flags = FS_USERNS_MOUNT,
fs/binfmt_misc.c-1031- .kill_sb = kill_litter_super,
--
fs/devpts/inode.c=467=static struct file_system_type devpts_fs_type = {
--
fs/devpts/inode.c-471- .kill_sb = devpts_kill_sb,
fs/devpts/inode.c:472: .fs_flags = FS_USERNS_MOUNT,
fs/devpts/inode.c-473-};
--
fs/fuse/inode.c=2138=static struct file_system_type fuse_fs_type = {
--
fs/fuse/inode.c-2140- .name = "fuse",
fs/fuse/inode.c:2141: .fs_flags = FS_HAS_SUBTYPE | FS_USERNS_MOUNT | FS_ALLOW_IDMAP,
fs/fuse/inode.c-2142- .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=1576=struct file_system_type ovl_fs_type = {
--
fs/overlayfs/super.c-1580- .parameters = ovl_parameter_spec,
fs/overlayfs/super.c:1581: .fs_flags = FS_USERNS_MOUNT,
fs/overlayfs/super.c-1582- .kill_sb = kill_anon_super,
--
fs/proc/root.c=363=static struct file_system_type proc_fs_type = {
--
fs/proc/root.c-367- .kill_sb = proc_kill_sb,
fs/proc/root.c:368: .fs_flags = FS_USERNS_MOUNT | FS_DISALLOW_NOTIFY_PERM,
fs/proc/root.c-369-};
--
fs/ramfs/inode.c=319=static struct file_system_type ramfs_fs_type = {
--
fs/ramfs/inode.c-323- .kill_sb = ramfs_kill_sb,
fs/ramfs/inode.c:324: .fs_flags = FS_USERNS_MOUNT,
fs/ramfs/inode.c-325-};
--
fs/super.c=690=bool mount_capable(struct fs_context *fc)
fs/super.c-691-{
fs/super.c:692: if (!(fc->fs_type->fs_flags & FS_USERNS_MOUNT))
fs/super.c-693- return capable(CAP_SYS_ADMIN);
--
fs/super.c=730=struct super_block *sget_fc(struct fs_context *fc,
--
fs/super.c-739- /*
fs/super.c:740: * Never allow s_user_ns != &init_user_ns when FS_USERNS_MOUNT is
fs/super.c-741- * not set, as the filesystem is likely unprepared to handle it.
--
fs/super.c-744- */
fs/super.c:745: if (user_ns != &init_user_ns && !(fc->fs_type->fs_flags & FS_USERNS_MOUNT)) {
fs/super.c-746- 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=2681=struct file_system_type {
--
include/linux/fs.h-2686-#define FS_HAS_SUBTYPE 4
include/linux/fs.h:2687:#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
include/linux/fs.h-2688-#define FS_DISALLOW_NOTIFY_PERM 16 /* Disable fanotify permission events */
--
ipc/mqueue.c=1638=static struct file_system_type mqueue_fs_type = {
--
ipc/mqueue.c-1641- .kill_sb = kill_litter_super,
ipc/mqueue.c:1642: .fs_flags = FS_USERNS_MOUNT,
ipc/mqueue.c-1643-};
--
kernel/bpf/inode.c=1087=static struct file_system_type bpf_fs_type = {
--
kernel/bpf/inode.c-1092- .kill_sb = bpf_kill_super,
kernel/bpf/inode.c:1093: .fs_flags = FS_USERNS_MOUNT,
kernel/bpf/inode.c-1094-};
--
kernel/cgroup/cgroup.c=2395=struct file_system_type cgroup_fs_type = {
--
kernel/cgroup/cgroup.c-2399- .kill_sb = cgroup_kill_sb,
kernel/cgroup/cgroup.c:2400: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2401-};
--
kernel/cgroup/cgroup.c=2403=static struct file_system_type cgroup2_fs_type = {
--
kernel/cgroup/cgroup.c-2407- .kill_sb = cgroup_kill_sb,
kernel/cgroup/cgroup.c:2408: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2409-};
--
kernel/cgroup/cgroup.c=2476=static struct file_system_type cpuset_fs_type = {
--
kernel/cgroup/cgroup.c-2479- .parameters = cpuset_fs_parameters,
kernel/cgroup/cgroup.c:2480: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2481-};
--
mm/shmem.c=5330=static struct file_system_type shmem_fs_type = {
--
mm/shmem.c-5337- .kill_sb = kill_litter_super,
mm/shmem.c:5338: .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP | FS_MGTIME,
mm/shmem.c-5339-};
--
mm/shmem.c=5740=static struct file_system_type shmem_fs_type = {
--
mm/shmem.c-5744- .kill_sb = ramfs_kill_sb,
mm/shmem.c:5745: .fs_flags = FS_USERNS_MOUNT,
mm/shmem.c-5746-};
]
|
| 48/2 |
2026/05/23 17:51 |
llm |
expert |
0m
Model:
Tokens:
input: 55398
output: 66
thoughts: 617
Thoughts: **My Analysis of the OCFS2 Bug**
Okay, so here's what I've got. The core issue is that OCFS2 doesn't play nice with user namespaces. It lacks `FS_USERNS_MOUNT`, meaning it absolutely *needs* `CAP_SYS_ADMIN` in the initial user namespace to even be mounted. That immediately limits its attack surface from unprivileged processes or within user namespaces. That eliminates some easy vectors right away.
Let's break down possible triggers. A VM guest trigger is out β this is clearly a filesystem-level flaw. The same goes for VM host setups within Confidential Computing environments and anything network related that wouldn't involve the filesystem. Forget external network triggers.
The possibilities narrow down to peripheral and malicious filesystem triggers. A malicious USB drive containing a crafted OCFS2 filesystem that auto-mounts... that's a viable attack vector. Itβs a classic *malicious filesystem* scenario.
Now, the specifics of the bug itself: it's a `BUG_ON(*phys == 0)` in `ocfs2_write_cluster`. It happens because `ocfs2_get_clusters` returns a physical address of `0` for an allocated cluster, a clear sign of badness. The crux of the problem lies in the filesystem potentially allocating block zero, which should contain the superblock and be marked allocated. A maliciously crafted filesystem can free up bit 0 in the global bitmap. Then, when a new cluster is allocated, it *might* get cluster zero. That zero cluster gets inserted into the extent tree and extent map with `e_blkno = 0`. Later, when the system calls `ocfs2_get_clusters`, it sees `e_blkno == 0` and, incorrectly, treats it as a hole and returns 0. That violates the assumption in `ocfs2_write_cluster`, leading to the `BUG_ON`.
The impact? A Denial of Service (kernel panic). While not ideal, it's not a privilege escalation or memory corruption vulnerability. The attacker needs to mount the malicious filesystem, which means `CAP_SYS_ADMIN` is a prerequisite. Auto-mounting a bad USB drive is the most realistic scenario.
I'm thinking deeper, given it's a cluster filesystem. Does the network play a role? Ah, yes, OCFS2 relies on DLM (Distributed Lock Manager) over the network. Could a malicious node within the cluster trigger this? If a compromised node manages to modify the global bitmap to free bit 0, then allocate it, it could trigger the bug. The report focused on a local mount of a crafted image. The network trigger is possible if the cluster is formed, but it requires a compromised node. Therefore, the filesystem image itself is the primary attack vector.
|
| 49/2 |
2026/05/23 17:51 |
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]
|
| 50/2 |
2026/05/23 17:51 |
llm |
expert |
0m
Model:
Tokens:
input: 56738
output: 544
thoughts: 413
Thoughts: **My Assessment of the `ocfs2_write_cluster` Bug**
Okay, the analysis is done, and it's time to summarize this. Here's what I've got:
The root of this issue is a `BUG_ON(*phys == 0)` within the `ocfs2_write_cluster` function. It seems that during a write operation, when a new cluster is allocated, `ocfs2_get_clusters` is reporting this newly allocated cluster as a "hole" (`*phys == 0`). This is happening because `ocfs2_get_clusters` interprets an extent where the physical block number is zero (`e_blkno == 0`) as a hole. Now, in a properly formed OCFS2 filesystem, block zero is the superblock and should *always* be marked as allocated in the global bitmap, full stop.
However, a clever attacker could create a malicious filesystem image that tricks the system by marking bit zero as *free* in the global bitmap. During a write operation when space is needed, the system might allocate cluster zero because it thinks it is free. The extent for this allocation gets inserted with `e_blkno = 0`. Then, when `ocfs2_get_clusters` is called, it sees `e_blkno == 0`, and erroneously assumes this represents a hole, so it returns `*phys = 0`. But this violates the fundamental assumption within `ocfs2_write_cluster` that a brand-new, allocated cluster cannot be a hole. That's the trigger for the `BUG_ON`.
The impact here is a straightforward denial of service because the `BUG_ON` results in a kernel panic. Thankfully, it's a deterministic panic, so no memory corruption is involved. This is important to note. And given that OCFS2 doesn't support `FS_USERNS_MOUNT`, mounting a malicious image requires `CAP_SYS_ADMIN` in the initial user namespace. This isn't exploitable via unprivileged processes. However, this is triggered via a filesystem image. The vector could be a USB drive that gets auto-mounted by the desktop environment, meaning this isn't exactly a theoretical vulnerability. It could be triggered by simply mounting the malicious filesystem image.
|
| 51/1 |
2026/05/23 17:52 |
action |
format-explanation |
0m
Results: map[Explanation:**Bug Analysis:**
The bug is a `BUG_ON(*phys == 0)` in `ocfs2_write_cluster()` located in
`fs/ocfs2/aops.c`.
This `BUG_ON` is triggered when a write operation allocates a new cluster for a
file, but a subsequent call to `ocfs2_get_clusters()` reports that the newly
allocated cluster is a hole (indicated by `*phys == 0`).
This contradictory state occurs because `ocfs2_get_clusters()` is hardcoded to
treat any extent with a physical block number of 0 (`e_blkno == 0`) as a sparse
hole. In a valid OCFS2 filesystem, block 0 (which resides in cluster 0) contains
the superblock and is always marked as allocated in the global bitmap.
Therefore, a valid allocator will never allocate block 0 for file data.
However, a maliciously crafted filesystem image can manipulate the global bitmap
to mark bit 0 (cluster 0) as free. When a file write operation requests a new
cluster, the allocator may claim cluster 0 and insert an extent with `e_blkno =
0` into the extent tree. When `ocfs2_write_cluster()` immediately reads back the
extent via `ocfs2_get_clusters()`, the function sees `e_blkno == 0`, assumes it
is a hole, and returns `*phys = 0`. This violates the assumption that a newly
allocated cluster cannot be a hole, hitting the `BUG_ON` and crashing the
kernel.
**Security Impact:**
* **Exploitable:** No. The bug results in a deterministic `BUG_ON` which halts
the kernel. There is no memory corruption, use-after-free, or out-of-bounds
access involved that could be leveraged for privilege escalation or code
execution.
* **Denial Of Service:** Yes. Triggering the bug causes a kernel panic, leading
to a complete system crash.
* **Accessible From Unprivileged Processes & User Namespaces:** No. The OCFS2
filesystem does not have the `FS_USERNS_MOUNT` flag set. Therefore, it cannot be
mounted from within a user namespace. Mounting the filesystem requires
`CAP_SYS_ADMIN` in the initial (root) user namespace.
* **Malicious Filesystem & Peripheral Trigger:** Yes. The bug is triggered by
mounting and interacting with a maliciously crafted OCFS2 filesystem image. This
is particularly relevant for desktop environments that might auto-mount external
media (like USB drives) containing the crafted image.]
|