| 0/0 |
2026/05/30 11:30 |
flow |
assessment-security |
57m
Results: map[DenialOfService:false Explanation:**Analysis of the Bug Report**
The bug report indicates a `WARNING: possible circular locking dependency
detected` by lockdep. The reported circular dependency chain is:
`sb_internal#2` -> `&journal->j_trans_barrier` -> `jbd2_handle` ->
`&ei->xattr_sem` -> `&dquot->dq_lock` ->
`&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]` ->
`&ocfs2_quota_ip_alloc_sem_key` ->
`&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]` -> `sb_internal#2`
**False Positive due to Lock Class Aliasing**
A closer look at the dependency chain reveals that it spans across two entirely
different filesystems: `ext4` and `ocfs2`.
1. The `ext4` filesystem establishes the chain: `jbd2_handle` ->
`&ei->xattr_sem` -> `&dquot->dq_lock` (e.g., during `ext4_setattr` ->
`dquot_transfer`).
2. The `ocfs2` filesystem establishes the chain: `&dquot->dq_lock` ->
`&ocfs2_sysfile_lock_key[...]` -> `sb_internal#2` -> `jbd2_handle` (e.g., during
`ocfs2_acquire_dquot` -> `ocfs2_extend_allocation` -> `ocfs2_start_trans`).
Lockdep tracks lock dependencies based on lock *classes*, not individual lock
*instances*. Both `jbd2_handle` (used by the JBD2 journaling layer) and
`dquot->dq_lock` (used by the VFS quota subsystem) are initialized with global
static lock class keys. Because `ext4` and `ocfs2` both use JBD2 and VFS quotas,
they share these global lock classes.
Lockdep merges the `ext4` and `ocfs2` chains together because they intersect at
these global lock classes, creating an artificial cycle. However, at runtime,
the `jbd2_handle` and `dquot->dq_lock` instances for an `ext4` mount are
completely independent of the instances for an `ocfs2` mount. A thread operating
on `ext4` will never wait for an `ocfs2` lock instance, and vice versa.
Furthermore, within `ocfs2` itself, the locking order is strictly maintained.
Functions like `ocfs2_mark_dquot_dirty`, `ocfs2_acquire_dquot`, and
`ocfs2_write_info` consistently acquire the global quota file locks
(`&ocfs2_quota_ip_alloc_sem_key`) *before* starting a transaction
(`ocfs2_start_trans`, which acquires `sb_internal#2`). There is no internal
cycle.
**Security Impact**
This is a classic lockdep false positive. There is no real deadlock that can
occur.
In production kernel builds, lockdep (`CONFIG_PROVE_LOCKING`) is disabled for
performance reasons. Therefore, this false positive warning will not be printed,
and it will not cause any system crash, hang, or denial of service.
Additionally, triggering this requires mounting an `ocfs2` filesystem, which
lacks the `FS_USERNS_MOUNT` flag and thus requires `CAP_SYS_ADMIN` in the
initial user namespace, making it inaccessible to unprivileged users.
Because this is a false positive lockdep warning with no runtime consequences in
production, it has no security impact. Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/30 11:30 |
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[4] = {0xffffffffffffffff, 0xffffffffffffffff, 0x0, 0x0};
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$ext4 arguments: [
// fs: ptr[in, buffer] {
// buffer: {65 78 74 34 00} (length 0x5)
// }
// dir: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 30 00} (length 0x8)
// }
// flags: mount_flags = 0x4000 (8 bytes)
// opts: ptr[in, fs_options[ext4_options]] {
// fs_options[ext4_options] {
// elems: array[fs_opt_elem[ext4_options]] {
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// usrquota: buffer: {75 73 72 71 75 6f 74 61} (length 0x8)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// grpjquota: buffer: {67 72 70 6a 71 75 6f 74 61 3d} (length 0xa)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// nodelalloc: buffer: {6e 6f 64 65 6c 61 6c 6c 6f 63} (length 0xa)
// }
// comma: const = 0x2c (1 bytes)
// }
// }
// common: array[fs_opt_elem[fs_options_common]] {
// }
// null: const = 0x0 (1 bytes)
// }
// }
// chdir: int8 = 0x1 (1 bytes)
// size: len = 0x4bc (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x4bc)
// }
// ]
// returns fd_dir
memcpy((void*)0x200000000000, "ext4\000", 5);
memcpy((void*)0x200000000500, "./file0\000", 8);
memcpy((void*)0x2000000001c0, "usrquota", 8);
*(uint8_t*)0x2000000001c8 = 0x2c;
memcpy((void*)0x2000000001c9, "grpjquota=", 10);
*(uint8_t*)0x2000000001d3 = 0x2c;
memcpy((void*)0x2000000001d4, "nodelalloc", 10);
*(uint8_t*)0x2000000001de = 0x2c;
*(uint8_t*)0x2000000001df = 0;
memcpy((void*)0x200000000a40, "... [truncated large byte array] ...", 1212);
syz_mount_image(/*fs=*/0x200000000000, /*dir=*/0x200000000500, /*flags=MS_REC*/0x4000, /*opts=*/0x2000000001c0, /*chdir=*/1, /*size=*/0x4bc, /*img=*/0x200000000a40);
// 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 30 00} (length 0x8)
// }
// flags: mount_flags = 0x10000 (8 bytes)
// opts: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {75 73 72 71 75 6f 74 61 2c 63 6f 68 65 72 65 6e 63 79 3d 66 75 6c 6c 2c 65 72 72 6f 72 73 3d 72 65 6d 6f 75 6e 74 2d 72 6f 2c 68 65 61 72 74 62 65 61 74 3d 6e 6f 6e 65 2c 65 72 72 6f 72 73 3d 63 6f 6e 74 69 6e 75 65 2c 6e 6f 69 6e 74 72 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 30 2c 00 2a 71 c4 38 a2 e5 99 a3 a0 f9 cc 00 77 fe 30 27 a6 df 48 cb 02 a6 6d d7 ad ac fe de 4b 80 42 89 3f 35 31 23 51 4c db 95 d9 ec 0c a4 b2 07 09 d1 6a 3e 82 39 0c 39 a4 d0 9f 66 81 59 64 3e 35 16 ae 52 e6 92 c5 0b 02 73 30 f8 23 8f 5a bd cf 85 5a cf dc ab 08 76 5e d6 3a b4 32 65 78 0d d8 0b 28 b5 54 d9 10 05 87 5d 49 86 9e 32 c8 d3 10 e0 4e 19 9d 1b 55 c7 fd f0 f7 2b fc 8d 48 a8 2d 0f f7 26 60 f6 3b 59 6b 5c 55 e6 58 66 e0 d6 6a 97 40 da 03 59 9b 51 a3 95 ce 1e 65 18 70 a1 91 8b c6 98 4b 8f 66 39 af 54 e2 71 22 ca db 13 41 56 7d 9c d2 bc 1c 6a ac d2 76 13 de 47 30 8d fb 48 48 f9 41 1d a3 e8 dc} (length 0x138)
// }
// }
// }
// chdir: int8 = 0x1 (1 bytes)
// size: len = 0x4710 (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x4710)
// }
// ]
// returns fd_dir
memcpy((void*)0x200000000600, "ocfs2\000", 6);
memcpy((void*)0x200000004780, "./file0\000", 8);
memcpy((void*)0x2000000024c0, "... [truncated large byte array] ...", 312);
memcpy((void*)0x200000008f40, "... [truncated large byte array] ...", 18192);
syz_mount_image(/*fs=*/0x200000000600, /*dir=*/0x200000004780, /*flags=MS_POSIXACL*/0x10000, /*opts=*/0x2000000024c0, /*chdir=*/1, /*size=*/0x4710, /*img=*/0x200000008f40);
// openat arguments: [
// fd: fd_dir (resource)
// file: ptr[in, buffer] {
// buffer: {2e 00} (length 0x2)
// }
// flags: open_flags = 0x0 (4 bytes)
// mode: open_mode = 0x0 (2 bytes)
// ]
// returns fd
memcpy((void*)0x200000000040, ".\000", 2);
res = syscall(__NR_openat, /*fd=*/0xffffff9c, /*file=*/0x200000000040ul, /*flags=*/0, /*mode=*/0);
if (res != -1)
r[0] = res;
// openat$binder_debug arguments: [
// fd: const = 0xffffffffffffff9c (8 bytes)
// file: ptr[in, buffer] {
// buffer: {2f 73 79 73 2f 6b 65 72 6e 65 6c 2f 64 65 62 75 67 2f 62 69 6e 64 65 72 2f 74 72 61 6e 73 61 63 74 69 6f 6e 73 00} (length 0x26)
// }
// flags: const = 0x0 (4 bytes)
// mode: const = 0x0 (2 bytes)
// ]
// returns fd
memcpy((void*)0x200000000040, "/sys/kernel/debug/binder/transactions\000", 38);
res = syscall(__NR_openat, /*fd=*/0xffffffffffffff9cul, /*file=*/0x200000000040ul, /*flags=*/0, /*mode=*/0);
if (res != -1)
r[1] = res;
// read$FUSE arguments: [
// fd: fd_fuse (resource)
// buf: ptr[out, fuse_in[read_buffer]] {
// fuse_in[read_buffer] {
// len: len = 0x2020 (4 bytes)
// opcode: int32 = 0x0 (4 bytes)
// unique: fuse_unique (resource)
// uid: uid (resource)
// gid: gid (resource)
// pid: pid (resource)
// padding: int32 = 0x0 (4 bytes)
// payload: buffer: (DirOut)
// }
// }
// len: bytesize = 0x2020 (8 bytes)
// ]
res = syscall(__NR_read, /*fd=*/r[1], /*buf=*/0x200000000480ul, /*len=*/0x2020ul);
if (res != -1) {
r[2] = *(uint32_t*)0x200000000490;
r[3] = *(uint32_t*)0x200000000494;
}
// fchown arguments: [
// fd: fd (resource)
// uid: uid (resource)
// gid: gid (resource)
// ]
syscall(__NR_fchown, /*fd=*/r[0], /*uid=*/r[2], /*gid=*/r[3]);
return 0;
}
]
|
| 2/1 |
2026/05/30 11:30 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/88995d53ee281d3b6d33a8dadadf5e2e9352f1e8]
|
| 3/1 |
2026/05/30 11:31 |
action |
kernel-builder |
28m
Results: map[KernelObj:/app/workdir/cache/build/2e3b7e19d0251905e55e99f1427a34445d145d0f]
|
| 4/1 |
2026/05/30 11:59 |
action |
codesearch-prepare |
23m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/30 12:23 |
agent |
expert |
4m
Model:
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false 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 grepping
".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 completely 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 transfers, that 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 exploitability 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 Confidential 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:
(syz.3.28,6801,1):ocfs2_block_check_validate:402 ERROR: CRC32 failed: stored: 0xb3775c19, computed 0x2dd1c265. Applying ECC.
JBD2: Ignoring recovery information on journal
ocfs2: Mounting device (7,3) on (node local, slot 0) with ordered data mode.
(syz.3.28,6801,1):ocfs2_block_check_validate:402 ERROR: CRC32 failed: stored: 0x98842a5e, computed 0xe74db1cd. Applying ECC.
======================================================
WARNING: possible circular locking dependency detected
6.17.0-rc1-syzkaller-g8f5ae30d69d7 #0 Not tainted
------------------------------------------------------
syz.3.28/6801 is trying to acquire lock:
ffff0000cc700618 (sb_internal#2){.+.+}-{0:0}, at: ocfs2_extend_allocation+0x5d4/0x14cc fs/ocfs2/file.c:597
but task is already holding lock:
ffff0000f6f05100 (&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]){+.+.}-{4:4}, at: inode_lock include/linux/fs.h:869 [inline]
ffff0000f6f05100 (&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]){+.+.}-{4:4}, at: ocfs2_reserve_suballoc_bits+0x12c/0x3b9c fs/ocfs2/suballoc.c:788
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #7 (&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]){+.+.}-{4:4}:
down_write+0x50/0xc0 kernel/locking/rwsem.c:1590
inode_lock include/linux/fs.h:869 [inline]
ocfs2_reserve_suballoc_bits+0x12c/0x3b9c fs/ocfs2/suballoc.c:788
ocfs2_reserve_cluster_bitmap_bits fs/ocfs2/suballoc.c:1134 [inline]
ocfs2_reserve_clusters_with_limit+0x290/0x9e0 fs/ocfs2/suballoc.c:1179
ocfs2_reserve_clusters fs/ocfs2/suballoc.c:1229 [inline]
ocfs2_lock_allocators+0x27c/0x518 fs/ocfs2/suballoc.c:2772
ocfs2_extend_allocation+0x328/0x14cc fs/ocfs2/file.c:589
ocfs2_extend_no_holes+0x1a8/0x424 fs/ocfs2/file.c:1027
ocfs2_acquire_dquot+0x530/0xa10 fs/ocfs2/quota_global.c:853
dqget+0x654/0xccc fs/quota/dquot.c:977
ocfs2_setattr+0xb30/0x17a0 fs/ocfs2/file.c:1233
notify_change+0x9a4/0xc50 fs/attr.c:552
chown_common+0x374/0x4dc fs/open.c:791
vfs_fchown fs/open.c:859 [inline]
ksys_fchown+0xe0/0x158 fs/open.c:871
__do_sys_fchown fs/open.c:876 [inline]
__se_sys_fchown fs/open.c:874 [inline]
__arm64_sys_fchown+0x7c/0x94 fs/open.c:874
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x2b8 arch/arm64/kernel/syscall.c:49
el0_svc_common+0x130/0x23c arch/arm64/kernel/syscall.c:132
do_el0_svc+0x48/0x58 arch/arm64/kernel/syscall.c:151
el0_svc+0x58/0x180 arch/arm64/kernel/entry-common.c:879
el0t_64_sync_handler+0x84/0x12c arch/arm64/kernel/entry-common.c:898
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:596
-> #6 (&ocfs2_quota_ip_alloc_sem_key){++++}-{4:4}:
down_write+0x50/0xc0 kernel/locking/rwsem.c:1590
ocfs2_lock_global_qf+0x1a8/0x22c fs/ocfs2/quota_global.c:314
ocfs2_acquire_dquot+0x258/0xa10 fs/ocfs2/quota_global.c:828
dqget+0x654/0xccc fs/quota/dquot.c:977
ocfs2_setattr+0xb30/0x17a0 fs/ocfs2/file.c:1233
notify_change+0x9a4/0xc50 fs/attr.c:552
chown_common+0x374/0x4dc fs/open.c:791
vfs_fchown fs/open.c:859 [inline]
ksys_fchown+0xe0/0x158 fs/open.c:871
__do_sys_fchown fs/open.c:876 [inline]
__se_sys_fchown fs/open.c:874 [inline]
__arm64_sys_fchown+0x7c/0x94 fs/open.c:874
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x2b8 arch/arm64/kernel/syscall.c:49
el0_svc_common+0x130/0x23c arch/arm64/kernel/syscall.c:132
do_el0_svc+0x48/0x58 arch/arm64/kernel/syscall.c:151
el0_svc+0x58/0x180 arch/arm64/kernel/entry-common.c:879
el0t_64_sync_handler+0x84/0x12c arch/arm64/kernel/entry-common.c:898
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:596
-> #5 (&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]){+.+.}-{4:4}:
down_write+0x50/0xc0 kernel/locking/rwsem.c:1590
inode_lock include/linux/fs.h:869 [inline]
ocfs2_lock_global_qf+0x18c/0x22c fs/ocfs2/quota_global.c:313
ocfs2_acquire_dquot+0x258/0xa10 fs/ocfs2/quota_global.c:828
dqget+0x654/0xccc fs/quota/dquot.c:977
ocfs2_setattr+0xb30/0x17a0 fs/ocfs2/file.c:1233
notify_change+0x9a4/0xc50 fs/attr.c:552
chown_common+0x374/0x4dc fs/open.c:791
vfs_fchown fs/open.c:859 [inline]
ksys_fchown+0xe0/0x158 fs/open.c:871
__do_sys_fchown fs/open.c:876 [inline]
__se_sys_fchown fs/open.c:874 [inline]
__arm64_sys_fchown+0x7c/0x94 fs/open.c:874
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x2b8 arch/arm64/kernel/syscall.c:49
el0_svc_common+0x130/0x23c arch/arm64/kernel/syscall.c:132
do_el0_svc+0x48/0x58 arch/arm64/kernel/syscall.c:151
el0_svc+0x58/0x180 arch/arm64/kernel/entry-common.c:879
el0t_64_sync_handler+0x84/0x12c arch/arm64/kernel/entry-common.c:898
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:596
-> #4 (&dquot->dq_lock){+.+.}-{4:4}:
__mutex_lock_common+0x1d0/0x24ac kernel/locking/mutex.c:598
__mutex_lock kernel/locking/mutex.c:760 [inline]
mutex_lock_nested+0x2c/0x38 kernel/locking/mutex.c:812
wait_on_dquot fs/quota/dquot.c:354 [inline]
dqget+0x5e4/0xccc fs/quota/dquot.c:972
dquot_transfer+0x238/0x560 fs/quota/dquot.c:2140
ext4_setattr+0x738/0x1810 fs/ext4/inode.c:5902
notify_change+0x9a4/0xc50 fs/attr.c:552
chown_common+0x374/0x4dc fs/open.c:791
vfs_fchown fs/open.c:859 [inline]
ksys_fchown+0xe0/0x158 fs/open.c:871
__do_sys_fchown fs/open.c:876 [inline]
__se_sys_fchown fs/open.c:874 [inline]
__arm64_sys_fchown+0x7c/0x94 fs/open.c:874
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x2b8 arch/arm64/kernel/syscall.c:49
el0_svc_common+0x130/0x23c arch/arm64/kernel/syscall.c:132
do_el0_svc+0x48/0x58 arch/arm64/kernel/syscall.c:151
el0_svc+0x58/0x180 arch/arm64/kernel/entry-common.c:879
el0t_64_sync_handler+0x84/0x12c arch/arm64/kernel/entry-common.c:898
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:596
-> #3 (&ei->xattr_sem){++++}-{4:4}:
down_write+0x50/0xc0 kernel/locking/rwsem.c:1590
ext4_write_lock_xattr fs/ext4/xattr.h:157 [inline]
ext4_xattr_set_handle+0x11c/0x1260 fs/ext4/xattr.c:2362
ext4_initxattrs+0xa4/0x11c fs/ext4/xattr_security.c:44
security_inode_init_security+0x6dc/0x7f4 security/security.c:1852
ext4_init_security+0x44/0x58 fs/ext4/xattr_security.c:58
__ext4_new_inode+0x27f4/0x3190 fs/ext4/ialloc.c:1325
ext4_create+0x1f8/0x3fc fs/ext4/namei.c:2822
lookup_open fs/namei.c:3708 [inline]
open_last_lookups fs/namei.c:3807 [inline]
path_openat+0x12d8/0x2c40 fs/namei.c:4043
do_filp_open+0x18c/0x36c fs/namei.c:4073
do_sys_openat2+0x11c/0x1b4 fs/open.c:1435
do_sys_open fs/open.c:1450 [inline]
__do_sys_openat fs/open.c:1466 [inline]
__se_sys_openat fs/open.c:1461 [inline]
__arm64_sys_openat+0x120/0x158 fs/open.c:1461
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x2b8 arch/arm64/kernel/syscall.c:49
el0_svc_common+0x130/0x23c arch/arm64/kernel/syscall.c:132
do_el0_svc+0x48/0x58 arch/arm64/kernel/syscall.c:151
el0_svc+0x58/0x180 arch/arm64/kernel/entry-common.c:879
el0t_64_sync_handler+0x84/0x12c arch/arm64/kernel/entry-common.c:898
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:596
-> #2 (jbd2_handle){++++}-{0:0}:
start_this_handle+0xe74/0x10dc fs/jbd2/transaction.c:444
jbd2__journal_start+0x288/0x51c fs/jbd2/transaction.c:501
jbd2_journal_start+0x3c/0x4c fs/jbd2/transaction.c:540
ocfs2_start_trans+0x368/0x6b0 fs/ocfs2/journal.c:374
ocfs2_modify_bh+0xe4/0x43c fs/ocfs2/quota_local.c:101
ocfs2_local_read_info+0x104c/0x1364 fs/ocfs2/quota_local.c:767
dquot_load_quota_sb+0x6bc/0xa90 fs/quota/dquot.c:2459
dquot_load_quota_inode+0x274/0x4e4 fs/quota/dquot.c:2496
ocfs2_enable_quotas+0x17c/0x3b4 fs/ocfs2/super.c:930
ocfs2_fill_super+0x4004/0x532c fs/ocfs2/super.c:1140
get_tree_bdev_flags+0x360/0x414 fs/super.c:1692
get_tree_bdev+0x2c/0x3c fs/super.c:1715
ocfs2_get_tree+0x28/0x38 fs/ocfs2/super.c:1184
vfs_get_tree+0x90/0x28c fs/super.c:1815
do_new_mount+0x278/0x7f4 fs/namespace.c:3805
path_mount+0x5b4/0xde0 fs/namespace.c:4120
do_mount fs/namespace.c:4133 [inline]
__do_sys_mount fs/namespace.c:4344 [inline]
__se_sys_mount fs/namespace.c:4321 [inline]
__arm64_sys_mount+0x3e8/0x468 fs/namespace.c:4321
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x2b8 arch/arm64/kernel/syscall.c:49
el0_svc_common+0x130/0x23c arch/arm64/kernel/syscall.c:132
do_el0_svc+0x48/0x58 arch/arm64/kernel/syscall.c:151
el0_svc+0x58/0x180 arch/arm64/kernel/entry-common.c:879
el0t_64_sync_handler+0x84/0x12c arch/arm64/kernel/entry-common.c:898
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:596
-> #1 (&journal->j_trans_barrier){.+.+}-{4:4}:
down_read+0x58/0x2f8 kernel/locking/rwsem.c:1537
ocfs2_start_trans+0x35c/0x6b0 fs/ocfs2/journal.c:372
ocfs2_modify_bh+0xe4/0x43c fs/ocfs2/quota_local.c:101
ocfs2_local_read_info+0x104c/0x1364 fs/ocfs2/quota_local.c:767
dquot_load_quota_sb+0x6bc/0xa90 fs/quota/dquot.c:2459
dquot_load_quota_inode+0x274/0x4e4 fs/quota/dquot.c:2496
ocfs2_enable_quotas+0x17c/0x3b4 fs/ocfs2/super.c:930
ocfs2_fill_super+0x4004/0x532c fs/ocfs2/super.c:1140
get_tree_bdev_flags+0x360/0x414 fs/super.c:1692
get_tree_bdev+0x2c/0x3c fs/super.c:1715
ocfs2_get_tree+0x28/0x38 fs/ocfs2/super.c:1184
vfs_get_tree+0x90/0x28c fs/super.c:1815
do_new_mount+0x278/0x7f4 fs/namespace.c:3805
path_mount+0x5b4/0xde0 fs/namespace.c:4120
do_mount fs/namespace.c:4133 [inline]
__do_sys_mount fs/namespace.c:4344 [inline]
__se_sys_mount fs/namespace.c:4321 [inline]
__arm64_sys_mount+0x3e8/0x468 fs/namespace.c:4321
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x2b8 arch/arm64/kernel/syscall.c:49
el0_svc_common+0x130/0x23c arch/arm64/kernel/syscall.c:132
do_el0_svc+0x48/0x58 arch/arm64/kernel/syscall.c:151
el0_svc+0x58/0x180 arch/arm64/kernel/entry-common.c:879
el0t_64_sync_handler+0x84/0x12c arch/arm64/kernel/entry-common.c:898
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:596
-> #0 (sb_internal#2){.+.+}-{0:0}:
check_prev_add kernel/locking/lockdep.c:3165 [inline]
check_prevs_add kernel/locking/lockdep.c:3284 [inline]
validate_chain kernel/locking/lockdep.c:3908 [inline]
__lock_acquire+0x1774/0x30a4 kernel/locking/lockdep.c:5237
lock_acquire+0x14c/0x2e0 kernel/locking/lockdep.c:5868
percpu_down_read_internal include/linux/percpu-rwsem.h:53 [inline]
percpu_down_read_freezable include/linux/percpu-rwsem.h:83 [inline]
__sb_start_write include/linux/fs.h:1798 [inline]
sb_start_intwrite include/linux/fs.h:1981 [inline]
ocfs2_start_trans+0x1f4/0x6b0 fs/ocfs2/journal.c:370
ocfs2_extend_allocation+0x5d4/0x14cc fs/ocfs2/file.c:597
ocfs2_extend_no_holes+0x1a8/0x424 fs/ocfs2/file.c:1027
ocfs2_acquire_dquot+0x530/0xa10 fs/ocfs2/quota_global.c:853
dqget+0x654/0xccc fs/quota/dquot.c:977
ocfs2_setattr+0xb30/0x17a0 fs/ocfs2/file.c:1233
notify_change+0x9a4/0xc50 fs/attr.c:552
chown_common+0x374/0x4dc fs/open.c:791
vfs_fchown fs/open.c:859 [inline]
ksys_fchown+0xe0/0x158 fs/open.c:871
__do_sys_fchown fs/open.c:876 [inline]
__se_sys_fchown fs/open.c:874 [inline]
__arm64_sys_fchown+0x7c/0x94 fs/open.c:874
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x2b8 arch/arm64/kernel/syscall.c:49
el0_svc_common+0x130/0x23c arch/arm64/kernel/syscall.c:132
do_el0_svc+0x48/0x58 arch/arm64/kernel/syscall.c:151
el0_svc+0x58/0x180 arch/arm64/kernel/entry-common.c:879
el0t_64_sync_handler+0x84/0x12c arch/arm64/kernel/entry-common.c:898
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:596
other info that might help us debug this:
Chain exists of:
sb_internal#2 --> &ocfs2_quota_ip_alloc_sem_key --> &ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]);
lock(&ocfs2_quota_ip_alloc_sem_key);
lock(&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]);
rlock(sb_internal#2);
*** DEADLOCK ***
6 locks held by syz.3.28/6801:
#0: ffff0000cc700428 (sb_writers#11){.+.+}-{0:0}, at: mnt_want_write_file+0x64/0x1e8 fs/namespace.c:601
#1: ffff0000f6f009c0 (&type->i_mutex_dir_key#8){+.+.}-{4:4}, at: inode_lock_killable include/linux/fs.h:874 [inline]
#1: ffff0000f6f009c0 (&type->i_mutex_dir_key#8){+.+.}-{4:4}, at: chown_common+0x290/0x4dc fs/open.c:779
#2: ffff0000e2d2a0a8 (&dquot->dq_lock){+.+.}-{4:4}, at: ocfs2_acquire_dquot+0x24c/0xa10 fs/ocfs2/quota_global.c:823
#3: ffff0000f6f05f40 (&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]){+.+.}-{4:4}, at: inode_lock include/linux/fs.h:869 [inline]
#3: ffff0000f6f05f40 (&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]){+.+.}-{4:4}, at: ocfs2_lock_global_qf+0x18c/0x22c fs/ocfs2/quota_global.c:313
#4: ffff0000f6f05be0 (&ocfs2_quota_ip_alloc_sem_key){++++}-{4:4}, at: ocfs2_lock_global_qf+0x1a8/0x22c fs/ocfs2/quota_global.c:314
#5: ffff0000f6f05100 (&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]){+.+.}-{4:4}, at: inode_lock include/linux/fs.h:869 [inline]
#5: ffff0000f6f05100 (&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]){+.+.}-{4:4}, at: ocfs2_reserve_suballoc_bits+0x12c/0x3b9c fs/ocfs2/suballoc.c:788
stack backtrace:
CPU: 1 UID: 0 PID: 6801 Comm: syz.3.28 Not tainted 6.17.0-rc1-syzkaller-g8f5ae30d69d7 #0 PREEMPT
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 06/30/2025
Call trace:
show_stack+0x2c/0x3c arch/arm64/kernel/stacktrace.c:499 (C)
__dump_stack+0x30/0x40 lib/dump_stack.c:94
dump_stack_lvl+0xd8/0x12c lib/dump_stack.c:120
dump_stack+0x1c/0x28 lib/dump_stack.c:129
print_circular_bug+0x324/0x32c kernel/locking/lockdep.c:2043
check_noncircular+0x154/0x174 kernel/locking/lockdep.c:2175
check_prev_add kernel/locking/lockdep.c:3165 [inline]
check_prevs_add kernel/locking/lockdep.c:3284 [inline]
validate_chain kernel/locking/lockdep.c:3908 [inline]
__lock_acquire+0x1774/0x30a4 kernel/locking/lockdep.c:5237
lock_acquire+0x14c/0x2e0 kernel/locking/lockdep.c:5868
percpu_down_read_internal include/linux/percpu-rwsem.h:53 [inline]
percpu_down_read_freezable include/linux/percpu-rwsem.h:83 [inline]
__sb_start_write include/linux/fs.h:1798 [inline]
sb_start_intwrite include/linux/fs.h:1981 [inline]
ocfs2_start_trans+0x1f4/0x6b0 fs/ocfs2/journal.c:370
ocfs2_extend_allocation+0x5d4/0x14cc fs/ocfs2/file.c:597
ocfs2_extend_no_holes+0x1a8/0x424 fs/ocfs2/file.c:1027
ocfs2_acquire_dquot+0x530/0xa10 fs/ocfs2/quota_global.c:853
dqget+0x654/0xccc fs/quota/dquot.c:977
ocfs2_setattr+0xb30/0x17a0 fs/ocfs2/file.c:1233
notify_change+0x9a4/0xc50 fs/attr.c:552
chown_common+0x374/0x4dc fs/open.c:791
vfs_fchown fs/open.c:859 [inline]
ksys_fchown+0xe0/0x158 fs/open.c:871
__do_sys_fchown fs/open.c:876 [inline]
__se_sys_fchown fs/open.c:874 [inline]
__arm64_sys_fchown+0x7c/0x94 fs/open.c:874
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x2b8 arch/arm64/kernel/syscall.c:49
el0_svc_common+0x130/0x23c arch/arm64/kernel/syscall.c:132
do_el0_svc+0x48/0x58 arch/arm64/kernel/syscall.c:151
el0_svc+0x58/0x180 arch/arm64/kernel/entry-common.c:879
el0t_64_sync_handler+0x84/0x12c arch/arm64/kernel/entry-common.c:898
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:596
(syz.3.28,6801,1):ocfs2_block_check_validate:402 ERROR: CRC32 failed: stored: 0xdf8356d3, computed 0xb8c23ae4. Applying ECC.
(syz.3.28,6801,1):ocfs2_block_check_validate:416 ERROR: Fixed CRC32 failed: stored: 0xdf8356d3, computed 0x2acb7e3c
(syz.3.28,6801,1):ocfs2_read_quota_phys_block:160 ERROR: status = -5
(syz.3.28,6801,1):ocfs2_quota_read:201 ERROR: status = -5
Quota error (device loop3): qtree_write_dquot: Error -5 occurred while creating quota
(syz.3.28,6801,1):ocfs2_acquire_dquot:890 ERROR: status = -5
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[4] = {0xffffffffffffffff, 0xffffffffffffffff, 0x0, 0x0};
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$ext4 arguments: [
// fs: ptr[in, buffer] {
// buffer: {65 78 74 34 00} (length 0x5)
// }
// dir: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 30 00} (length 0x8)
// }
// flags: mount_flags = 0x4000 (8 bytes)
// opts: ptr[in, fs_options[ext4_options]] {
// fs_options[ext4_options] {
// elems: array[fs_opt_elem[ext4_options]] {
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// usrquota: buffer: {75 73 72 71 75 6f 74 61} (length 0x8)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// grpjquota: buffer: {67 72 70 6a 71 75 6f 74 61 3d} (length 0xa)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// nodelalloc: buffer: {6e 6f 64 65 6c 61 6c 6c 6f 63} (length 0xa)
// }
// comma: const = 0x2c (1 bytes)
// }
// }
// common: array[fs_opt_elem[fs_options_common]] {
// }
// null: const = 0x0 (1 bytes)
// }
// }
// chdir: int8 = 0x1 (1 bytes)
// size: len = 0x4bc (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x4bc)
// }
// ]
// returns fd_dir
memcpy((void*)0x200000000000, "ext4\000", 5);
memcpy((void*)0x200000000500, "./file0\000", 8);
memcpy((void*)0x2000000001c0, "usrquota", 8);
*(uint8_t*)0x2000000001c8 = 0x2c;
memcpy((void*)0x2000000001c9, "grpjquota=", 10);
*(uint8_t*)0x2000000001d3 = 0x2c;
memcpy((void*)0x2000000001d4, "nodelalloc", 10);
*(uint8_t*)0x2000000001de = 0x2c;
*(uint8_t*)0x2000000001df = 0;
memcpy((void*)0x200000000a40, "... [truncated large byte array] ...", 1212);
syz_mount_image(/*fs=*/0x200000000000, /*dir=*/0x200000000500, /*flags=MS_REC*/0x4000, /*opts=*/0x2000000001c0, /*chdir=*/1, /*size=*/0x4bc, /*img=*/0x200000000a40);
// 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 30 00} (length 0x8)
// }
// flags: mount_flags = 0x10000 (8 bytes)
// opts: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {75 73 72 71 75 6f 74 61 2c 63 6f 68 65 72 65 6e 63 79 3d 66 75 6c 6c 2c 65 72 72 6f 72 73 3d 72 65 6d 6f 75 6e 74 2d 72 6f 2c 68 65 61 72 74 62 65 61 74 3d 6e 6f 6e 65 2c 65 72 72 6f 72 73 3d 63 6f 6e 74 69 6e 75 65 2c 6e 6f 69 6e 74 72 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 30 2c 00 2a 71 c4 38 a2 e5 99 a3 a0 f9 cc 00 77 fe 30 27 a6 df 48 cb 02 a6 6d d7 ad ac fe de 4b 80 42 89 3f 35 31 23 51 4c db 95 d9 ec 0c a4 b2 07 09 d1 6a 3e 82 39 0c 39 a4 d0 9f 66 81 59 64 3e 35 16 ae 52 e6 92 c5 0b 02 73 30 f8 23 8f 5a bd cf 85 5a cf dc ab 08 76 5e d6 3a b4 32 65 78 0d d8 0b 28 b5 54 d9 10 05 87 5d 49 86 9e 32 c8 d3 10 e0 4e 19 9d 1b 55 c7 fd f0 f7 2b fc 8d 48 a8 2d 0f f7 26 60 f6 3b 59 6b 5c 55 e6 58 66 e0 d6 6a 97 40 da 03 59 9b 51 a3 95 ce 1e 65 18 70 a1 91 8b c6 98 4b 8f 66 39 af 54 e2 71 22 ca db 13 41 56 7d 9c d2 bc 1c 6a ac d2 76 13 de 47 30 8d fb 48 48 f9 41 1d a3 e8 dc} (length 0x138)
// }
// }
// }
// chdir: int8 = 0x1 (1 bytes)
// size: len = 0x4710 (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x4710)
// }
// ]
// returns fd_dir
memcpy((void*)0x200000000600, "ocfs2\000", 6);
memcpy((void*)0x200000004780, "./file0\000", 8);
memcpy((void*)0x2000000024c0, "... [truncated large byte array] ...", 312);
memcpy((void*)0x200000008f40, "... [truncated large byte array] ...", 18192);
syz_mount_image(/*fs=*/0x200000000600, /*dir=*/0x200000004780, /*flags=MS_POSIXACL*/0x10000, /*opts=*/0x2000000024c0, /*chdir=*/1, /*size=*/0x4710, /*img=*/0x200000008f40);
// openat arguments: [
// fd: fd_dir (resource)
// file: ptr[in, buffer] {
// buffer: {2e 00} (length 0x2)
// }
// flags: open_flags = 0x0 (4 bytes)
// mode: open_mode = 0x0 (2 bytes)
// ]
// returns fd
memcpy((void*)0x200000000040, ".\000", 2);
res = syscall(__NR_openat, /*fd=*/0xffffff9c, /*file=*/0x200000000040ul, /*flags=*/0, /*mode=*/0);
if (res != -1)
r[0] = res;
// openat$binder_debug arguments: [
// fd: const = 0xffffffffffffff9c (8 bytes)
// file: ptr[in, buffer] {
// buffer: {2f 73 79 73 2f 6b 65 72 6e 65 6c 2f 64 65 62 75 67 2f 62 69 6e 64 65 72 2f 74 72 61 6e 73 61 63 74 69 6f 6e 73 00} (length 0x26)
// }
// flags: const = 0x0 (4 bytes)
// mode: const = 0x0 (2 bytes)
// ]
// returns fd
memcpy((void*)0x200000000040, "/sys/kernel/debug/binder/transactions\000", 38);
res = syscall(__NR_openat, /*fd=*/0xffffffffffffff9cul, /*file=*/0x200000000040ul, /*flags=*/0, /*mode=*/0);
if (res != -1)
r[1] = res;
// read$FUSE arguments: [
// fd: fd_fuse (resource)
// buf: ptr[out, fuse_in[read_buffer]] {
// fuse_in[read_buffer] {
// len: len = 0x2020 (4 bytes)
// opcode: int32 = 0x0 (4 bytes)
// unique: fuse_unique (resource)
// uid: uid (resource)
// gid: gid (resource)
// pid: pid (resource)
// padding: int32 = 0x0 (4 bytes)
// payload: buffer: (DirOut)
// }
// }
// len: bytesize = 0x2020 (8 bytes)
// ]
res = syscall(__NR_read, /*fd=*/r[1], /*buf=*/0x200000000480ul, /*len=*/0x2020ul);
if (res != -1) {
r[2] = *(uint32_t*)0x200000000490;
r[3] = *(uint32_t*)0x200000000494;
}
// fchown arguments: [
// fd: fd (resource)
// uid: uid (resource)
// gid: gid (resource)
// ]
syscall(__NR_fchown, /*fd=*/r[0], /*uid=*/r[2], /*gid=*/r[3]);
return 0;
}
Reply: **Analysis of the Bug Report**
The bug report indicates a `WARNING: possible circular locking dependency detected` by lockdep. The reported circular dependency chain is:
`sb_internal#2` -> `&journal->j_trans_barrier` -> `jbd2_handle` -> `&ei->xattr_sem` -> `&dquot->dq_lock` -> `&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]` -> `&ocfs2_quota_ip_alloc_sem_key` -> `&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]` -> `sb_internal#2`
**False Positive due to Lock Class Aliasing**
A closer look at the dependency chain reveals that it spans across two entirely different filesystems: `ext4` and `ocfs2`.
1. The `ext4` filesystem establishes the chain: `jbd2_handle` -> `&ei->xattr_sem` -> `&dquot->dq_lock` (e.g., during `ext4_setattr` -> `dquot_transfer`).
2. The `ocfs2` filesystem establishes the chain: `&dquot->dq_lock` -> `&ocfs2_sysfile_lock_key[...]` -> `sb_internal#2` -> `jbd2_handle` (e.g., during `ocfs2_acquire_dquot` -> `ocfs2_extend_allocation` -> `ocfs2_start_trans`).
Lockdep tracks lock dependencies based on lock *classes*, not individual lock *instances*. Both `jbd2_handle` (used by the JBD2 journaling layer) and `dquot->dq_lock` (used by the VFS quota subsystem) are initialized with global static lock class keys. Because `ext4` and `ocfs2` both use JBD2 and VFS quotas, they share these global lock classes.
Lockdep merges the `ext4` and `ocfs2` chains together because they intersect at these global lock classes, creating an artificial cycle. However, at runtime, the `jbd2_handle` and `dquot->dq_lock` instances for an `ext4` mount are completely independent of the instances for an `ocfs2` mount. A thread operating on `ext4` will never wait for an `ocfs2` lock instance, and vice versa.
Furthermore, within `ocfs2` itself, the locking order is strictly maintained. Functions like `ocfs2_mark_dquot_dirty`, `ocfs2_acquire_dquot`, and `ocfs2_write_info` consistently acquire the global quota file locks (`&ocfs2_quota_ip_alloc_sem_key`) *before* starting a transaction (`ocfs2_start_trans`, which acquires `sb_internal#2`). There is no internal cycle.
**Security Impact**
This is a classic lockdep false positive. There is no real deadlock that can occur.
In production kernel builds, lockdep (`CONFIG_PROVE_LOCKING`) is disabled for performance reasons. Therefore, this false positive warning will not be printed, and it will not cause any system crash, hang, or denial of service.
Additionally, triggering this requires mounting an `ocfs2` filesystem, which lacks the `FS_USERNS_MOUNT` flag and thus requires `CAP_SYS_ADMIN` in the initial user namespace, making it inaccessible to unprivileged users.
Because this is a false positive lockdep warning with no runtime consequences in production, it has no security impact.
|
| 6/2 |
2026/05/30 12:23 |
llm |
expert |
0m
Model:
Tokens:
input: 23125
output: 31
thoughts: 351
Thoughts: **Analyzing a Potential OCFS2 Lockdep Bug**
Okay, here's what's running through my head. We've got a syzkaller report flagging a "possible circular locking dependency detected" within OCFS2, specifically a lockdep warning. The lock chain is laid out: `sb_internal#2` leading to `&ocfs2_quota_ip_alloc_sem_key`, then to `&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE`, and finally back to `sb_internal#2`. That's the classic circular dependency, and a lockdep warning screams "potential deadlock" to me.
The reproducer involves mounting an ext4 image, then an OCFS2 image, and then performs an `openat`, a `read`, and an `fchown` on the OCFS2 mount. I need to figure out the implications. In a production environment, lockdep warnings might not stop the system outright (hopefully they're not enabled). However, circular dependencies are prime candidates for deadlocks, which translates directly to Denial of Service.
So, is this exploitable in a meaningful way? Deadlocks are generally not useful for privilege escalation or memory corruption as far as I know. It's a DoS, which is still a problem, especially if it can be easily triggered.
Now, can unprivileged users cause this? The reproducer uses `syz_mount_image`, which is a key question. Mounting a filesystem usually needs `CAP_SYS_ADMIN`, typically restricted to the initial user namespace. However, the game changes with user namespaces. If the filesystem is marked with `FS_USERNS_MOUNT` then unprivileged users _within_ that namespace can mount it, assuming they also have `CAP_SYS_ADMIN` in the user namespace.
Let me check the `ocfs2` source. We need to know if `ocfs2` has `FS_USERNS_MOUNT` set. That's the critical question here, determining if we have a DoS vulnerability that's easily triggered by a large set of users.
|
| 7/2 |
2026/05/30 12:23 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile: Name:ocfs2_fs_type]
Results: map[SourceCode:1222: static struct file_system_type ocfs2_fs_type = {
1223: .owner = THIS_MODULE,
1224: .name = "ocfs2",
1225: .kill_sb = kill_block_super,
1226: .fs_flags = FS_REQUIRES_DEV|FS_RENAME_DOES_D_MOVE,
1227: .next = NULL,
1228: .init_fs_context = ocfs2_init_fs_context,
1229: .parameters = ocfs2_param_spec,
1230: };
SourceFile:fs/ocfs2/super.c]
|
| 8/2 |
2026/05/30 12:23 |
llm |
expert |
0m
Model:
Tokens:
input: 24162
output: 44
thoughts: 3082
Thoughts: **Analyzing a Potential Deadlock in OCFS2 and EXT4 Interaction**
Okay, so `ocfs2` doesn't support `FS_USERNS_MOUNT`, meaning it's not mountable by unprivileged users in a user namespace. However, what if a privileged administrator mounts the filesystem and makes it accessible to unprivileged users? Could they trigger a deadlock? The reproducer uses `fchown` on a directory, which can be called by the file's owner. My focus now is on understanding the lockdep chain involved.
The lockdep chain is a complex dance involving locks from both `ocfs2` and `ext4`. It starts with `sb_internal#2`, which is `sb->s_writers.rw_sem[SB_FREEZE_FS-1]`, a critical structure for filesystem metadata operations. This locks to journal transactions, an inode's extended attributes semaphore, `&dquot->dq_lock` (a quota lock), and two `ocfs2` system file locks (`&ocfs2_quota_ip_alloc_sem_key` and `&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]`).
Crucially, `&dquot->dq_lock` appears to be the global dquot lock, shared by all filesystems using quotas. This global nature is what seems to be connecting the `ocfs2` and `ext4` code paths in this chain. The lockdep report confirms a circular dependency involving `sb_internal#2`, the journal barrier, extended attribute locks, the dquot lock, and two `ocfs2` system file locks.
Digging deeper, I confirmed `dquot->dq_lock` is indeed a single lock class used across all filesystems, established during `quota_init`. This explains why lockdep is connecting the `ext4` and `ocfs2` components.
My next step is to carefully analyze how these locks are acquired and released within both `ocfs2` and `ext4`, to fully comprehend the sequence and identify the precise point of the deadlock. This also explains how a simple `fchown` can lead to such a complicated lock dependency.
|
| 9/2 |
2026/05/30 12:23 |
tool |
codesearch-find-references |
0m
Error:
Args: map[ContextFile: IncludeSnippetLines:0 Name:dq_lock SourceTreePrefix:fs/quota/]
Results: map[References:<nil> TruncatedOutput:false]
|
| 10/2 |
2026/05/30 12:23 |
llm |
expert |
0m
Model:
Tokens:
input: 27778
output: 36
thoughts: 12
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert:
**Tracing the Initialization of `dq_lock`**
Alright, so the task at hand is to understand how `dq_lock` is brought to life. I need to get a handle on its initialization process. This isn't just a simple declaration; there's bound to be some underlying mechanism involved, and that's what I want to nail down. I'll start with a search for its initialization. I need to figure out where and how this `dq_lock` is being set up. Knowing this will give me a better understanding of how the data structure it guards functions, and the implications for its lifecycle and potential synchronization issues. The hunt begins!
|
| 11/2 |
2026/05/30 12:23 |
tool |
grepper |
0m
Args: map[Expression:mutex_init\(&dquot->dq_lock\) PathPrefix:fs/quota/]
Results: map[Output:fs/quota/dquot.c=894=static struct dquot *get_empty_dquot(struct super_block *sb, int type)
--
fs/quota/dquot.c-901-
fs/quota/dquot.c:902: mutex_init(&dquot->dq_lock);
fs/quota/dquot.c-903- INIT_LIST_HEAD(&dquot->dq_free);
]
|
| 12/2 |
2026/05/30 12:23 |
llm |
expert |
0m
Model:
Tokens:
input: 28108
output: 43
thoughts: 1397
Thoughts: **Analyzing the Lock Dependency Chain**
Okay, so `mutex_init(&dquot->dq_lock)` is indeed called in `get_empty_dquot`. Since there's no specific lock class key assigned during initialization, lockdep assigns a single lock class based on the call site. This means all `dquot->dq_lock` instances across *all* filesystems are in the same lock class. That's how lockdep connects the dependency chain from `ext4` to `ocfs2`.
Let me trace the `ext4` side. The chain I see initially looks like this: `&ei->xattr_sem` -> `&dquot->dq_lock`. This apparently happens via `ext4_setattr` -> `dquot_transfer` -> `dqget` -> `wait_on_dquot` -> `mutex_lock(&dquot->dq_lock)`. Hmm, but wait, `ext4_setattr` *doesn't* directly hold `&ei->xattr_sem`. This seems inconsistent.
Let's dig into the lockdep report for `ext4`. It shows:
`-> #4 (&dquot->dq_lock){+.+.}-{4:4}:`
... _(call stack to mutex_lock)_ ...
`ext4_setattr` *does* call `dquot_transfer`, so there *is* a connection. Now I need to figure out where `&ei->xattr_sem` is acquired in the chain.
Ah, here it is. The dependency is:
`-> #3 (&ei->xattr_sem){++++}-{4:4}:`
... _(call stack to down_write)_ ...
`ext4_write_lock_xattr` -> `ext4_xattr_set_handle` -> `ext4_initxattrs` -> `security_inode_init_security` -> `ext4_init_security` -> `__ext4_new_inode` -> `ext4_create`
So, `ext4_create` (during inode creation) acquires `&ei->xattr_sem`! That's it. It starts a journal handle (`jbd2_handle`), then calls `__ext4_new_inode`, which leads to `ext4_init_security` and takes the semaphore. Therefore, the chain is: `jbd2_handle` -> `&ei->xattr_sem`.
Now, how does this all connect to `&dquot->dq_lock`?
Let's check the lockdep output more carefully. It shows:
`-> #4 (&dquot->dq_lock){+.+.}-{4:4}:`
... _(call stack)_ ...
`-> #3 (&ei->xattr_sem){++++}-{4:4}:`
... _(call stack)_ ...
But hang on. The order in lockdep is *reversed*. The dependency chain is showing in reverse! It's indicating what depends *on* what.
So, the full (reversed) chain as indicated by lockdep is:
`-> #7 ...`
`-> #6 ...`
`-> #5 ...`
`-> #4 (&dquot->dq_lock)`
`-> #3 (&ei->xattr_sem)`
`-> #2 (jbd2_handle)`
`-> #1 (&journal->j_trans_barrier)`
`-> #0 (sb_internal#2)`
Thus, the *actual* chain is:
* `sb_internal#2` depends on `&journal->j_trans_barrier`
* `&journal->j_trans_barrier` depends on `jbd2_handle`
* `jbd2_handle` depends on `&ei->xattr_sem`
* `&ei->xattr_sem` depends on `&dquot->dq_lock`
* `&dquot->dq_lock` depends on `&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]`
* `&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]` depends on `&ocfs2_quota_ip_alloc_sem_key`
* `&ocfs2_quota_ip_alloc_sem_key` depends on `&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]`
* `&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]` depends on `sb_internal#2`
Okay, so I *must* trace the order of acquisition. Let'
|
| 13/2 |
2026/05/30 12:24 |
tool |
grepper |
0m
Args: map[Expression:down_write\(&EXT4_I\(inode\)->xattr_sem\) PathPrefix:fs/ext4/]
Results: map[Output:fs/ext4/xattr.h=155=static inline void ext4_write_lock_xattr(struct inode *inode, int *save)
fs/ext4/xattr.h-156-{
fs/ext4/xattr.h:157: down_write(&EXT4_I(inode)->xattr_sem);
fs/ext4/xattr.h-158- *save = ext4_test_inode_state(inode, EXT4_STATE_NO_EXPAND);
]
|
| 14/2 |
2026/05/30 12:24 |
llm |
expert |
0m
Model:
Tokens:
input: 30709
output: 37
thoughts: 518
Thoughts: **Analyzing a Lockdep Conflict in ext4_setattr**
Okay, so I'm looking at this `ext4_setattr` function and something's not quite right. My initial thought was that `ext4_setattr` might be taking `xattr_sem`, but that's not the case. Let's dig into this lockdep output. It's pointing to a conflict, specifically this dependency:
-> #4 (&dquot->dq_lock){+.+.}-{4:4} which is `dquot->dq_lock`.
I see the acquisition order in the call stack, but wait, the lockdep output is showing a specific ordering. It indicates a scenario where the system acquired `&ei->xattr_sem` *before* acquiring `&dquot->dq_lock`. The output displays as:
-> #4 (&dquot->dq_lock)
-> #3 (&ei->xattr_sem)
Which, as I understand lockdep's notation, means that `&ei->xattr_sem` is acquired *before* `&dquot->dq_lock`. The chain is Lock A -> Lock B, so A is acquired before B. However, this seems to contradict the intended locking order, and I'm not seeing where that happens directly in `ext4_setattr`. I need to figure out where `&ei->xattr_sem` is being acquired *prior* to `&dquot->dq_lock` in this execution path. The trail leads me to check the code around `ext4_setattr` to ensure I'm not missing something.
|
| 15/2 |
2026/05/30 12:24 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ext4/inode.c Name:ext4_setattr]
Results: map[SourceCode:5821: /*
5822: * ext4_setattr()
5823: *
5824: * Called from notify_change.
5825: *
5826: * We want to trap VFS attempts to truncate the file as soon as
5827: * possible. In particular, we want to make sure that when the VFS
5828: * shrinks i_size, we put the inode on the orphan list and modify
5829: * i_disksize immediately, so that during the subsequent flushing of
5830: * dirty pages and freeing of disk blocks, we can guarantee that any
5831: * commit will leave the blocks being flushed in an unused state on
5832: * disk. (On recovery, the inode will get truncated and the blocks will
5833: * be freed, so we have a strong guarantee that no future commit will
5834: * leave these blocks visible to the user.)
5835: *
5836: * Another thing we have to assure is that if we are in ordered mode
5837: * and inode is still attached to the committing transaction, we must
5838: * we start writeout of all the dirty pages which are being truncated.
5839: * This way we are sure that all the data written in the previous
5840: * transaction are already on disk (truncate waits for pages under
5841: * writeback).
5842: *
5843: * Called with inode->i_rwsem down.
5844: */
5845: int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
5846: struct iattr *attr)
5847: {
5848: struct inode *inode = d_inode(dentry);
5849: int error, rc = 0;
5850: int orphan = 0;
5851: const unsigned int ia_valid = attr->ia_valid;
5852: bool inc_ivers = true;
5853:
5854: error = ext4_emergency_state(inode->i_sb);
5855: if (unlikely(error))
5856: return error;
5857:
5858: if (unlikely(IS_IMMUTABLE(inode)))
5859: return -EPERM;
5860:
5861: if (unlikely(IS_APPEND(inode) &&
5862: (ia_valid & (ATTR_MODE | ATTR_UID |
5863: ATTR_GID | ATTR_TIMES_SET))))
5864: return -EPERM;
5865:
5866: error = setattr_prepare(idmap, dentry, attr);
5867: if (error)
5868: return error;
5869:
5870: error = fscrypt_prepare_setattr(dentry, attr);
5871: if (error)
5872: return error;
5873:
5874: error = fsverity_prepare_setattr(dentry, attr);
5875: if (error)
5876: return error;
5877:
5878: if (is_quota_modification(idmap, inode, attr)) {
5879: error = dquot_initialize(inode);
5880: if (error)
5881: return error;
5882: }
5883:
5884: if (i_uid_needs_update(idmap, attr, inode) ||
5885: i_gid_needs_update(idmap, attr, inode)) {
5886: handle_t *handle;
5887:
5888: /* (user+group)*(old+new) structure, inode write (sb,
5889: * inode block, ? - but truncate inode update has it) */
5890: handle = ext4_journal_start(inode, EXT4_HT_QUOTA,
5891: (EXT4_MAXQUOTAS_INIT_BLOCKS(inode->i_sb) +
5892: EXT4_MAXQUOTAS_DEL_BLOCKS(inode->i_sb)) + 3);
5893: if (IS_ERR(handle)) {
5894: error = PTR_ERR(handle);
5895: goto err_out;
5896: }
5897:
5898: /* dquot_transfer() calls back ext4_get_inode_usage() which
5899: * counts xattr inode references.
5900: */
5901: down_read(&EXT4_I(inode)->xattr_sem);
5902: error = dquot_transfer(idmap, inode, attr);
5903: up_read(&EXT4_I(inode)->xattr_sem);
5904:
5905: if (error) {
5906: ext4_journal_stop(handle);
5907: return error;
5908: }
5909: /* Update corresponding info in inode so that everything is in
5910: * one transaction */
5911: i_uid_update(idmap, attr, inode);
5912: i_gid_update(idmap, attr, inode);
5913: error = ext4_mark_inode_dirty(handle, inode);
5914: ext4_journal_stop(handle);
5915: if (unlikely(error)) {
5916: return error;
5917: }
5918: }
5919:
5920: if (attr->ia_valid & ATTR_SIZE) {
5921: handle_t *handle;
5922: loff_t oldsize = inode->i_size;
5923: loff_t old_disksize;
5924: int shrink = (attr->ia_size < inode->i_size);
5925:
5926: if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) {
5927: struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
5928:
5929: if (attr->ia_size > sbi->s_bitmap_maxbytes) {
5930: return -EFBIG;
5931: }
5932: }
5933: if (!S_ISREG(inode->i_mode)) {
5934: return -EINVAL;
5935: }
5936:
5937: if (attr->ia_size == inode->i_size)
5938: inc_ivers = false;
5939:
5940: if (shrink) {
5941: if (ext4_should_order_data(inode)) {
5942: error = ext4_begin_ordered_truncate(inode,
5943: attr->ia_size);
5944: if (error)
5945: goto err_out;
5946: }
5947: /*
5948: * Blocks are going to be removed from the inode. Wait
5949: * for dio in flight.
5950: */
5951: inode_dio_wait(inode);
5952: }
5953:
5954: filemap_invalidate_lock(inode->i_mapping);
5955:
5956: rc = ext4_break_layouts(inode);
5957: if (rc) {
5958: filemap_invalidate_unlock(inode->i_mapping);
5959: goto err_out;
5960: }
5961:
5962: if (attr->ia_size != inode->i_size) {
5963: /* attach jbd2 jinode for EOF folio tail zeroing */
5964: if (attr->ia_size & (inode->i_sb->s_blocksize - 1) ||
5965: oldsize & (inode->i_sb->s_blocksize - 1)) {
5966: error = ext4_inode_attach_jinode(inode);
5967: if (error)
5968: goto out_mmap_sem;
5969: }
5970:
5971: handle = ext4_journal_start(inode, EXT4_HT_INODE, 3);
5972: if (IS_ERR(handle)) {
5973: error = PTR_ERR(handle);
5974: goto out_mmap_sem;
5975: }
5976: if (ext4_handle_valid(handle) && shrink) {
5977: error = ext4_orphan_add(handle, inode);
5978: orphan = 1;
5979: }
5980: /*
5981: * Update c/mtime and tail zero the EOF folio on
5982: * truncate up. ext4_truncate() handles the shrink case
5983: * below.
5984: */
5985: if (!shrink) {
5986: inode_set_mtime_to_ts(inode,
5987: inode_set_ctime_current(inode));
5988: if (oldsize & (inode->i_sb->s_blocksize - 1))
5989: ext4_block_truncate_page(handle,
5990: inode->i_mapping, oldsize);
5991: }
5992:
5993: if (shrink)
5994: ext4_fc_track_range(handle, inode,
5995: (attr->ia_size > 0 ? attr->ia_size - 1 : 0) >>
5996: inode->i_sb->s_blocksize_bits,
5997: EXT_MAX_BLOCKS - 1);
5998: else
5999: ext4_fc_track_range(
6000: handle, inode,
6001: (oldsize > 0 ? oldsize - 1 : oldsize) >>
6002: inode->i_sb->s_blocksize_bits,
6003: (attr->ia_size > 0 ? attr->ia_size - 1 : 0) >>
6004: inode->i_sb->s_blocksize_bits);
6005:
6006: down_write(&EXT4_I(inode)->i_data_sem);
6007: old_disksize = EXT4_I(inode)->i_disksize;
6008: EXT4_I(inode)->i_disksize = attr->ia_size;
6009:
6010: /*
6011: * We have to update i_size under i_data_sem together
6012: * with i_disksize to avoid races with writeback code
6013: * running ext4_wb_update_i_disksize().
6014: */
6015: if (!error)
6016: i_size_write(inode, attr->ia_size);
6017: else
6018: EXT4_I(inode)->i_disksize = old_disksize;
6019: up_write(&EXT4_I(inode)->i_data_sem);
6020: rc = ext4_mark_inode_dirty(handle, inode);
6021: if (!error)
6022: error = rc;
6023: ext4_journal_stop(handle);
6024: if (error)
6025: goto out_mmap_sem;
6026: if (!shrink) {
6027: pagecache_isize_extended(inode, oldsize,
6028: inode->i_size);
6029: } else if (ext4_should_journal_data(inode)) {
6030: ext4_wait_for_tail_page_commit(inode);
6031: }
6032: }
6033:
6034: /*
6035: * Truncate pagecache after we've waited for commit
6036: * in data=journal mode to make pages freeable.
6037: */
6038: truncate_pagecache(inode, inode->i_size);
6039: /*
6040: * Call ext4_truncate() even if i_size didn't change to
6041: * truncate possible preallocated blocks.
6042: */
6043: if (attr->ia_size <= oldsize) {
6044: rc = ext4_truncate(inode);
6045: if (rc)
6046: error = rc;
6047: }
6048: out_mmap_sem:
6049: filemap_invalidate_unlock(inode->i_mapping);
6050: }
6051:
6052: if (!error) {
6053: if (inc_ivers)
6054: inode_inc_iversion(inode);
6055: setattr_copy(idmap, inode, attr);
6056: mark_inode_dirty(inode);
6057: }
6058:
6059: /*
6060: * If the call to ext4_truncate failed to get a transaction handle at
6061: * all, we need to clean up the in-core orphan list manually.
6062: */
6063: if (orphan && inode->i_nlink)
6064: ext4_orphan_del(NULL, inode);
6065:
6066: if (!error && (ia_valid & ATTR_MODE))
6067: rc = posix_acl_chmod(idmap, dentry, inode->i_mode);
6068:
6069: err_out:
6070: if (error)
6071: ext4_std_error(inode->i_sb, error);
6072: if (!error)
6073: error = rc;
6074: return error;
6075: }
SourceFile:fs/ext4/inode.c]
|
| 16/2 |
2026/05/30 12:24 |
llm |
expert |
0m
Model:
Tokens:
input: 35509
output: 34
thoughts: 386
Thoughts: **My Analysis of a Potential Deadlock**
Okay, let's break this down. In `ext4_setattr`, I see:
```c
down_read(&EXT4_I(inode)->xattr_sem);
error = dquot_transfer(idmap, inode, attr);
up_read(&EXT4_I(inode)->xattr_sem);
```
So `ext4` is holding `&EXT4_I(inode)->xattr_sem` while it calls `dquot_transfer`. Makes sense. Then `dquot_transfer` is using `&dquot->dq_lock`. This gives us a lock ordering of `&ei->xattr_sem` -> `&dquot->dq_lock`. Seems reasonable at this point.
Then, looking further, `&dquot->dq_lock` is acquired before `&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]` in `ocfs2_acquire_dquot`:
```c
-> #5 (&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]){+.+.}-{4:4}:
down_write+0x50/0xc0 kernel/locking/rwsem.c:1590
inode_lock include/linux/fs.h:869 [inline]
ocfs2_lock_global_qf+0x18c/0x22c fs/ocfs2/quota_global.c:313
ocfs2_acquire_dquot+0x258/0xa10 fs/ocfs2/quota_global.c:828
dqget+0x654/0xccc fs/quota/dquot.c:977
```
Hmm...wait a minute. `dqget` is also acquiring `&dquot->dq_lock`? If `dqget` is also acquiring `&dquot->dq_lock`, then we need to see where it might come from, because that could be a problem! Let's go check `dqget`...
|
| 17/2 |
2026/05/30 12:24 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/quota/dquot.c Name:dqget]
Results: map[SourceCode: 915: /*
916: * Get reference to dquot
917: *
918: * Locking is slightly tricky here. We are guarded from parallel quotaoff()
919: * destroying our dquot by:
920: * a) checking for quota flags under dq_list_lock and
921: * b) getting a reference to dquot before we release dq_list_lock
922: */
923: struct dquot *dqget(struct super_block *sb, struct kqid qid)
924: {
925: unsigned int hashent = hashfn(sb, qid);
926: struct dquot *dquot, *empty = NULL;
927:
928: if (!qid_has_mapping(sb->s_user_ns, qid))
929: return ERR_PTR(-EINVAL);
930:
931: if (!sb_has_quota_active(sb, qid.type))
932: return ERR_PTR(-ESRCH);
933: we_slept:
934: spin_lock(&dq_list_lock);
935: spin_lock(&dq_state_lock);
936: if (!sb_has_quota_active(sb, qid.type)) {
937: spin_unlock(&dq_state_lock);
938: spin_unlock(&dq_list_lock);
939: dquot = ERR_PTR(-ESRCH);
940: goto out;
941: }
942: spin_unlock(&dq_state_lock);
943:
944: dquot = find_dquot(hashent, sb, qid);
945: if (!dquot) {
946: if (!empty) {
947: spin_unlock(&dq_list_lock);
948: empty = get_empty_dquot(sb, qid.type);
949: if (!empty)
950: schedule(); /* Try to wait for a moment... */
951: goto we_slept;
952: }
953: dquot = empty;
954: empty = NULL;
955: dquot->dq_id = qid;
956: /* all dquots go on the inuse_list */
957: put_inuse(dquot);
958: /* hash it first so it can be found */
959: insert_dquot_hash(dquot);
960: spin_unlock(&dq_list_lock);
961: dqstats_inc(DQST_LOOKUPS);
962: } else {
963: if (!atomic_read(&dquot->dq_count))
964: remove_free_dquot(dquot);
965: atomic_inc(&dquot->dq_count);
966: spin_unlock(&dq_list_lock);
967: dqstats_inc(DQST_CACHE_HITS);
968: dqstats_inc(DQST_LOOKUPS);
969: }
970: /* Wait for dq_lock - after this we know that either dquot_release() is
971: * already finished or it will be canceled due to dq_count > 0 test */
972: wait_on_dquot(dquot);
973: /* Read the dquot / allocate space in quota file */
974: if (!dquot_active(dquot)) {
975: int err;
976:
977: err = sb->dq_op->acquire_dquot(dquot);
978: if (err < 0) {
979: dqput(dquot);
980: dquot = ERR_PTR(err);
981: goto out;
982: }
983: }
984: /*
985: * Make sure following reads see filled structure - paired with
986: * smp_mb__before_atomic() in dquot_acquire().
987: */
988: smp_rmb();
989: /* Has somebody invalidated entry under us? */
990: WARN_ON_ONCE(hlist_unhashed(&dquot->dq_hash));
991: out:
992: if (empty)
993: do_destroy_dquot(empty);
994:
995: return dquot;
996: }
SourceFile:fs/quota/dquot.c]
|
| 18/2 |
2026/05/30 12:24 |
llm |
expert |
0m
Model:
Tokens:
input: 37691
output: 43
thoughts: 211
Thoughts: Here's my analysis of the provided code snippet:
**Dquot Acquisition Under Scrutiny**
Okay, so I'm looking at the `dqget` function and how it acquires a dquot. The code flow is pretty straightforward: we wait on the dquot, and then if it's not active, we try to acquire it using `sb->dq_op->acquire_dquot`. But then I hit a snag. The `wait_on_dquot` function is calling mutex lock, and *then* mutex unlock on the `dquot->dq_lock`. My immediate reaction is, "Wait a second, what's going on here?!" This function seems to *release* the lock it just acquired!
If `wait_on_dquot` is releasing the lock, then `sb->dq_op->acquire_dquot` is being called without the protection of the `dquot->dq_lock`. This is a *major* potential concurrency issue. We're potentially accessing shared dquot state without proper synchronization. This feels like a race condition waiting to happen. To confirm this issue, I need to check the definition of `ocfs2_acquire_dquot`. That's where I need to look to see what it assumes about the locking state when it's called.
|
| 19/2 |
2026/05/30 12:24 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/quota_global.c Name:ocfs2_acquire_dquot]
Results: map[SourceCode: 804: /*
805: * Read global dquot structure from disk or create it if it does
806: * not exist. Also update use count of the global structure and
807: * create structure in node-local quota file.
808: */
809: static int ocfs2_acquire_dquot(struct dquot *dquot)
810: {
811: int status = 0, err;
812: int ex = 0;
813: struct super_block *sb = dquot->dq_sb;
814: struct ocfs2_super *osb = OCFS2_SB(sb);
815: int type = dquot->dq_id.type;
816: struct ocfs2_mem_dqinfo *info = sb_dqinfo(sb, type)->dqi_priv;
817: struct inode *gqinode = info->dqi_gqinode;
818: int need_alloc = ocfs2_global_qinit_alloc(sb, type);
819: handle_t *handle;
820:
821: trace_ocfs2_acquire_dquot(from_kqid(&init_user_ns, dquot->dq_id),
822: type);
823: mutex_lock(&dquot->dq_lock);
824: /*
825: * We need an exclusive lock, because we're going to update use count
826: * and instantiate possibly new dquot structure
827: */
828: status = ocfs2_lock_global_qf(info, 1);
829: if (status < 0)
830: goto out;
831: status = ocfs2_qinfo_lock(info, 0);
832: if (status < 0)
833: goto out_dq;
834: /*
835: * We always want to read dquot structure from disk because we don't
836: * know what happened with it while it was on freelist.
837: */
838: status = qtree_read_dquot(&info->dqi_gi, dquot);
839: ocfs2_qinfo_unlock(info, 0);
840: if (status < 0)
841: goto out_dq;
842:
843: OCFS2_DQUOT(dquot)->dq_use_count++;
844: OCFS2_DQUOT(dquot)->dq_origspace = dquot->dq_dqb.dqb_curspace;
845: OCFS2_DQUOT(dquot)->dq_originodes = dquot->dq_dqb.dqb_curinodes;
846: if (!dquot->dq_off) { /* No real quota entry? */
847: ex = 1;
848: /*
849: * Add blocks to quota file before we start a transaction since
850: * locking allocators ranks above a transaction start
851: */
852: WARN_ON(journal_current_handle());
853: status = ocfs2_extend_no_holes(gqinode, NULL,
854: i_size_read(gqinode) + (need_alloc << sb->s_blocksize_bits),
855: i_size_read(gqinode));
856: if (status < 0)
857: goto out_dq;
858: }
859:
860: handle = ocfs2_start_trans(osb,
861: ocfs2_calc_global_qinit_credits(sb, type));
862: if (IS_ERR(handle)) {
863: status = PTR_ERR(handle);
864: goto out_dq;
865: }
866: status = ocfs2_qinfo_lock(info, ex);
867: if (status < 0)
868: goto out_trans;
869: status = qtree_write_dquot(&info->dqi_gi, dquot);
870: if (ex && info_dirty(sb_dqinfo(sb, type))) {
871: err = __ocfs2_global_write_info(sb, type);
872: if (!status)
873: status = err;
874: }
875: ocfs2_qinfo_unlock(info, ex);
876: out_trans:
877: ocfs2_commit_trans(osb, handle);
878: out_dq:
879: ocfs2_unlock_global_qf(info, 1);
880: if (status < 0)
881: goto out;
882:
883: status = ocfs2_create_local_dquot(dquot);
884: if (status < 0)
885: goto out;
886: set_bit(DQ_ACTIVE_B, &dquot->dq_flags);
887: out:
888: mutex_unlock(&dquot->dq_lock);
889: if (status)
890: mlog_errno(status);
891: return status;
892: }
SourceFile:fs/ocfs2/quota_global.c]
|
| 20/2 |
2026/05/30 12:24 |
llm |
expert |
0m
Model:
Tokens:
input: 39741
output: 40
thoughts: 263
Thoughts: **Analyzing the OCFS2 Dquot Locking Sequence**
Okay, so I'm digging into this `ocfs2_acquire_dquot` function, and the first thing that jumps out is that it grabs `mutex_lock(&dquot->dq_lock)`. Standard enough, but it's the sequence *after* this that's key. It then calls `ocfs2_lock_global_qf(info, 1)`, which, predictably, acquires a couple of system-level locks: `&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]` and `&ocfs2_quota_ip_alloc_sem_key`.
Then we get to the core allocation process within `ocfs2_extend_no_holes`, which calls the `ocfs2_extend_allocation` function. The function calls `sb_start_intwrite` using the `sb_internal#2` lock.
Right, so that's the chain: `&dquot->dq_lock` -> `&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]` -> `&ocfs2_quota_ip_alloc_sem_key` -> `&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]` -> `sb_internal#2`.
Hmm, hold on. `sb_start_intwrite` within `ocfs2_extend_allocation`? I need to double-check that part. Let's delve into that function to verify the sequence.
|
| 21/2 |
2026/05/30 12:24 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/file.c Name:ocfs2_extend_allocation]
Results: map[SourceCode: 555: static int ocfs2_extend_allocation(struct inode *inode, u32 logical_start,
556: u32 clusters_to_add, int mark_unwritten)
557: {
558: int status = 0;
559: int restart_func = 0;
560: int credits;
561: u32 prev_clusters;
562: struct buffer_head *bh = NULL;
563: struct ocfs2_dinode *fe = NULL;
564: handle_t *handle = NULL;
565: struct ocfs2_alloc_context *data_ac = NULL;
566: struct ocfs2_alloc_context *meta_ac = NULL;
567: enum ocfs2_alloc_restarted why = RESTART_NONE;
568: struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
569: struct ocfs2_extent_tree et;
570: int did_quota = 0;
571:
572: /*
573: * Unwritten extent only exists for file systems which
574: * support holes.
575: */
576: BUG_ON(mark_unwritten && !ocfs2_sparse_alloc(osb));
577:
578: status = ocfs2_read_inode_block(inode, &bh);
579: if (status < 0) {
580: mlog_errno(status);
581: goto leave;
582: }
583: fe = (struct ocfs2_dinode *) bh->b_data;
584:
585: restart_all:
586: BUG_ON(le32_to_cpu(fe->i_clusters) != OCFS2_I(inode)->ip_clusters);
587:
588: ocfs2_init_dinode_extent_tree(&et, INODE_CACHE(inode), bh);
589: status = ocfs2_lock_allocators(inode, &et, clusters_to_add, 0,
590: &data_ac, &meta_ac);
591: if (status) {
592: mlog_errno(status);
593: goto leave;
594: }
595:
596: credits = ocfs2_calc_extend_credits(osb->sb, &fe->id2.i_list);
597: handle = ocfs2_start_trans(osb, credits);
598: if (IS_ERR(handle)) {
599: status = PTR_ERR(handle);
600: handle = NULL;
601: mlog_errno(status);
602: goto leave;
603: }
604:
605: restarted_transaction:
606: trace_ocfs2_extend_allocation(
607: (unsigned long long)OCFS2_I(inode)->ip_blkno,
608: (unsigned long long)i_size_read(inode),
609: le32_to_cpu(fe->i_clusters), clusters_to_add,
610: why, restart_func);
611:
612: status = dquot_alloc_space_nodirty(inode,
613: ocfs2_clusters_to_bytes(osb->sb, clusters_to_add));
614: if (status)
615: goto leave;
616: did_quota = 1;
617:
618: /* reserve a write to the file entry early on - that we if we
619: * run out of credits in the allocation path, we can still
620: * update i_size. */
621: status = ocfs2_journal_access_di(handle, INODE_CACHE(inode), bh,
622: OCFS2_JOURNAL_ACCESS_WRITE);
623: if (status < 0) {
624: mlog_errno(status);
625: goto leave;
626: }
627:
628: prev_clusters = OCFS2_I(inode)->ip_clusters;
629:
630: status = ocfs2_add_inode_data(osb,
631: inode,
632: &logical_start,
633: clusters_to_add,
634: mark_unwritten,
635: bh,
636: handle,
637: data_ac,
638: meta_ac,
639: &why);
640: if ((status < 0) && (status != -EAGAIN)) {
641: if (status != -ENOSPC)
642: mlog_errno(status);
643: goto leave;
644: }
645: ocfs2_update_inode_fsync_trans(handle, inode, 1);
646: ocfs2_journal_dirty(handle, bh);
647:
648: spin_lock(&OCFS2_I(inode)->ip_lock);
649: clusters_to_add -= (OCFS2_I(inode)->ip_clusters - prev_clusters);
650: spin_unlock(&OCFS2_I(inode)->ip_lock);
651: /* Release unused quota reservation */
652: dquot_free_space(inode,
653: ocfs2_clusters_to_bytes(osb->sb, clusters_to_add));
654: did_quota = 0;
655:
656: if (why != RESTART_NONE && clusters_to_add) {
657: if (why == RESTART_META) {
658: restart_func = 1;
659: status = 0;
660: } else {
661: BUG_ON(why != RESTART_TRANS);
662:
663: status = ocfs2_allocate_extend_trans(handle, 1);
664: if (status < 0) {
665: /* handle still has to be committed at
666: * this point. */
667: status = -ENOMEM;
668: mlog_errno(status);
669: goto leave;
670: }
671: goto restarted_transaction;
672: }
673: }
674:
675: trace_ocfs2_extend_allocation_end(OCFS2_I(inode)->ip_blkno,
676: le32_to_cpu(fe->i_clusters),
677: (unsigned long long)le64_to_cpu(fe->i_size),
678: OCFS2_I(inode)->ip_clusters,
679: (unsigned long long)i_size_read(inode));
680:
681: leave:
682: if (status < 0 && did_quota)
683: dquot_free_space(inode,
684: ocfs2_clusters_to_bytes(osb->sb, clusters_to_add));
685: if (handle) {
686: ocfs2_commit_trans(osb, handle);
687: handle = NULL;
688: }
689: if (data_ac) {
690: ocfs2_free_alloc_context(data_ac);
691: data_ac = NULL;
692: }
693: if (meta_ac) {
694: ocfs2_free_alloc_context(meta_ac);
695: meta_ac = NULL;
696: }
697: if ((!status) && restart_func) {
698: restart_func = 0;
699: goto restart_all;
700: }
701: brelse(bh);
702: bh = NULL;
703:
704: return status;
705: }
SourceFile:fs/ocfs2/file.c]
|
| 22/2 |
2026/05/30 12:24 |
llm |
expert |
0m
Model:
Tokens:
input: 42745
output: 39
thoughts: 579
Thoughts: **Analyzing a Potential Locking Conflict in OCFS2's Quota Handling During `setattr`**
Okay, let me break this down. I see a transaction starting with `ocfs2_start_trans` using `sb_internal#2`. This calls `ocfs2_extend_allocation` via `ocfs2_extend_no_holes`, and that takes `sb_internal#2`. But `ocfs2_acquire_dquot`, which calls all that, is itself called from `dqget`, and *that* comes from `dquot_transfer`. And *that* is called by `ocfs2_setattr`! That's where things get interesting.
Now, that's potentially problematic, right? A lockdep trace shows the path, starting with `sb_internal#2`: `ocfs2_start_trans` -> `ocfs2_extend_allocation` -> `ocfs2_extend_no_holes` -> `ocfs2_acquire_dquot` -> `dqget` -> `ocfs2_setattr`.
I'm thinking, is this a potential lock ordering issue? We've got `ocfs2_setattr` calling `dquot_transfer`... and then via `dqget`, that's potentially going to trigger allocation which needs the internal superblock lock? Wait... right... `ocfs2_setattr` *directly* calls `dquot_transfer`. Okay, I need to verify that in the code directly. Let me double-check `ocfs2_setattr` and make sure my understanding is correct.
|
| 23/2 |
2026/05/30 12:24 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/file.c Name:ocfs2_setattr]
Results: map[SourceCode:1114: int ocfs2_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
1115: struct iattr *attr)
1116: {
1117: int status = 0, size_change;
1118: int inode_locked = 0;
1119: struct inode *inode = d_inode(dentry);
1120: struct super_block *sb = inode->i_sb;
1121: struct ocfs2_super *osb = OCFS2_SB(sb);
1122: struct buffer_head *bh = NULL;
1123: handle_t *handle = NULL;
1124: struct dquot *transfer_to[MAXQUOTAS] = { };
1125: int qtype;
1126: int had_lock;
1127: struct ocfs2_lock_holder oh;
1128:
1129: trace_ocfs2_setattr(inode, dentry,
1130: (unsigned long long)OCFS2_I(inode)->ip_blkno,
1131: dentry->d_name.len, dentry->d_name.name,
1132: attr->ia_valid,
1133: attr->ia_valid & ATTR_MODE ? attr->ia_mode : 0,
1134: attr->ia_valid & ATTR_UID ?
1135: from_kuid(&init_user_ns, attr->ia_uid) : 0,
1136: attr->ia_valid & ATTR_GID ?
1137: from_kgid(&init_user_ns, attr->ia_gid) : 0);
1138:
1139: /* ensuring we don't even attempt to truncate a symlink */
1140: if (S_ISLNK(inode->i_mode))
1141: attr->ia_valid &= ~ATTR_SIZE;
1142:
1143: #define OCFS2_VALID_ATTRS (ATTR_ATIME | ATTR_MTIME | ATTR_CTIME | ATTR_SIZE \
1144: | ATTR_GID | ATTR_UID | ATTR_MODE)
1145: if (!(attr->ia_valid & OCFS2_VALID_ATTRS))
1146: return 0;
1147:
1148: status = setattr_prepare(&nop_mnt_idmap, dentry, attr);
1149: if (status)
1150: return status;
1151:
1152: if (is_quota_modification(&nop_mnt_idmap, inode, attr)) {
1153: status = dquot_initialize(inode);
1154: if (status)
1155: return status;
1156: }
1157: size_change = S_ISREG(inode->i_mode) && attr->ia_valid & ATTR_SIZE;
1158: if (size_change) {
1159: /*
1160: * Here we should wait dio to finish before inode lock
1161: * to avoid a deadlock between ocfs2_setattr() and
1162: * ocfs2_dio_end_io_write()
1163: */
1164: inode_dio_wait(inode);
1165:
1166: status = ocfs2_rw_lock(inode, 1);
1167: if (status < 0) {
1168: mlog_errno(status);
1169: goto bail;
1170: }
1171: }
1172:
1173: had_lock = ocfs2_inode_lock_tracker(inode, &bh, 1, &oh);
1174: if (had_lock < 0) {
1175: status = had_lock;
1176: goto bail_unlock_rw;
1177: } else if (had_lock) {
1178: /*
1179: * As far as we know, ocfs2_setattr() could only be the first
1180: * VFS entry point in the call chain of recursive cluster
1181: * locking issue.
1182: *
1183: * For instance:
1184: * chmod_common()
1185: * notify_change()
1186: * ocfs2_setattr()
1187: * posix_acl_chmod()
1188: * ocfs2_iop_get_acl()
1189: *
1190: * But, we're not 100% sure if it's always true, because the
1191: * ordering of the VFS entry points in the call chain is out
1192: * of our control. So, we'd better dump the stack here to
1193: * catch the other cases of recursive locking.
1194: */
1195: mlog(ML_ERROR, "Another case of recursive locking:\n");
1196: dump_stack();
1197: }
1198: inode_locked = 1;
1199:
1200: if (size_change) {
1201: status = inode_newsize_ok(inode, attr->ia_size);
1202: if (status)
1203: goto bail_unlock;
1204:
1205: if (i_size_read(inode) >= attr->ia_size) {
1206: if (ocfs2_should_order_data(inode)) {
1207: status = ocfs2_begin_ordered_truncate(inode,
1208: attr->ia_size);
1209: if (status)
1210: goto bail_unlock;
1211: }
1212: status = ocfs2_truncate_file(inode, bh, attr->ia_size);
1213: } else
1214: status = ocfs2_extend_file(inode, bh, attr->ia_size);
1215: if (status < 0) {
1216: if (status != -ENOSPC)
1217: mlog_errno(status);
1218: status = -ENOSPC;
1219: goto bail_unlock;
1220: }
1221: }
1222:
1223: if ((attr->ia_valid & ATTR_UID && !uid_eq(attr->ia_uid, inode->i_uid)) ||
1224: (attr->ia_valid & ATTR_GID && !gid_eq(attr->ia_gid, inode->i_gid))) {
1225: /*
1226: * Gather pointers to quota structures so that allocation /
1227: * freeing of quota structures happens here and not inside
1228: * dquot_transfer() where we have problems with lock ordering
1229: */
1230: if (attr->ia_valid & ATTR_UID && !uid_eq(attr->ia_uid, inode->i_uid)
1231: && OCFS2_HAS_RO_COMPAT_FEATURE(sb,
1232: OCFS2_FEATURE_RO_COMPAT_USRQUOTA)) {
1233: transfer_to[USRQUOTA] = dqget(sb, make_kqid_uid(attr->ia_uid));
1234: if (IS_ERR(transfer_to[USRQUOTA])) {
1235: status = PTR_ERR(transfer_to[USRQUOTA]);
1236: transfer_to[USRQUOTA] = NULL;
1237: goto bail_unlock;
1238: }
1239: }
1240: if (attr->ia_valid & ATTR_GID && !gid_eq(attr->ia_gid, inode->i_gid)
1241: && OCFS2_HAS_RO_COMPAT_FEATURE(sb,
1242: OCFS2_FEATURE_RO_COMPAT_GRPQUOTA)) {
1243: transfer_to[GRPQUOTA] = dqget(sb, make_kqid_gid(attr->ia_gid));
1244: if (IS_ERR(transfer_to[GRPQUOTA])) {
1245: status = PTR_ERR(transfer_to[GRPQUOTA]);
1246: transfer_to[GRPQUOTA] = NULL;
1247: goto bail_unlock;
1248: }
1249: }
1250: down_write(&OCFS2_I(inode)->ip_alloc_sem);
1251: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS +
1252: 2 * ocfs2_quota_trans_credits(sb));
1253: if (IS_ERR(handle)) {
1254: status = PTR_ERR(handle);
1255: mlog_errno(status);
1256: goto bail_unlock_alloc;
1257: }
1258: status = __dquot_transfer(inode, transfer_to);
1259: if (status < 0)
1260: goto bail_commit;
1261: } else {
1262: down_write(&OCFS2_I(inode)->ip_alloc_sem);
1263: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
1264: if (IS_ERR(handle)) {
1265: status = PTR_ERR(handle);
1266: mlog_errno(status);
1267: goto bail_unlock_alloc;
1268: }
1269: }
1270:
1271: setattr_copy(&nop_mnt_idmap, inode, attr);
1272: mark_inode_dirty(inode);
1273:
1274: status = ocfs2_mark_inode_dirty(handle, inode, bh);
1275: if (status < 0)
1276: mlog_errno(status);
1277:
1278: bail_commit:
1279: ocfs2_commit_trans(osb, handle);
1280: bail_unlock_alloc:
1281: up_write(&OCFS2_I(inode)->ip_alloc_sem);
1282: bail_unlock:
1283: if (status && inode_locked) {
1284: ocfs2_inode_unlock_tracker(inode, 1, &oh, had_lock);
1285: inode_locked = 0;
1286: }
1287: bail_unlock_rw:
1288: if (size_change)
1289: ocfs2_rw_unlock(inode, 1);
1290: bail:
1291:
1292: /* Release quota pointers in case we acquired them */
1293: for (qtype = 0; qtype < OCFS2_MAXQUOTAS; qtype++)
1294: dqput(transfer_to[qtype]);
1295:
1296: if (!status && attr->ia_valid & ATTR_MODE) {
1297: status = ocfs2_acl_chmod(inode, bh);
1298: if (status < 0)
1299: mlog_errno(status);
1300: }
1301: if (inode_locked)
1302: ocfs2_inode_unlock_tracker(inode, 1, &oh, had_lock);
1303:
1304: brelse(bh);
1305: return status;
1306: }
SourceFile:fs/ocfs2/file.c]
|
| 24/2 |
2026/05/30 12:24 |
llm |
expert |
0m
Model:
Tokens:
input: 46950
output: 31
thoughts: 4708
Thoughts: **Analyzing a Potential OCFS2 Deadlock**
Alright, let's break this down. I'm looking at `ocfs2_setattr` and a potential locking issue. The initial concern revolves around quota handling and the order of operations when updating an inode's attributes, specifically the user or group ID. `dqget` gets called before transaction start and, within that call, `ocfs2_acquire_dquot` calls `ocfs2_start_trans`. Then `ocfs2_start_trans` grabs a superblock-level internal write lock, which lockdep labels as `sb_internal#2`, which seems weird.
My immediate thought is, has `vfs_fchown` started a transaction? It has with the call to `mnt_want_write_file`, calling `sb_start_write` that takes the `sb_writers` lock. `sb_internal#2` is being acquired *inside* `ocfs2_start_trans`. Lockdep points out a dependency chain: `sb_internal#2` -> `&ocfs2_quota_ip_alloc_sem_key` -> `&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]`.
The lockdep report suggests a potential deadlock: CPU0 holds `GLOBAL_BITMAP_SYSTEM_INODE` and tries to acquire `sb_internal#2`, while CPU1 holds `ocfs2_quota_ip_alloc_sem_key` and attempts to get `GLOBAL_BITMAP_SYSTEM_INODE`. `GLOBAL_BITMAP_SYSTEM_INODE` is acquired inside `ocfs2_lock_allocators`, which is called before `ocfs2_start_trans`.
So, it's `GLOBAL_BITMAP_SYSTEM_INODE` -> `sb_internal#2`. The dependency chain goes: `sb_internal#2` -> `&journal->j_trans_barrier` -> `jbd2_handle` -> `&ei->xattr_sem` -> `&dquot->dq_lock` -> `&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]` -> `&ocfs2_quota_ip_alloc_sem_key` -> `&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]` -> `sb_internal#2`. It's a closed cycle!
The question now is, is this a *real* deadlock or a false positive? I'm worried it's a false positive, because `sb_internal#2` is a per-superblock lock. The key here is the `struct file_system_type`. Each filesystem should have its own lock key. If lockdep treats different filesystems' `sb_internal#2` locks as the *same* lock, then we have a problem.
The lock class keys are different between ext4 and ocfs2, so it *shouldn't* be considered the same. But let's make sure both are really ocfs2. Oh! It appears that one instance of `sb_internal#2` is from the mount itself, during `ocfs2_fill_super`. It's in a transaction in `ocfs2_start_trans` for mount code as well. Since OCFS2 uses JBD2, we share the `jbd2_handle` lock class with other filesystems. The cycle appears to be legitimate! This is bad. We have a circular dependency!
|
| 25/2 |
2026/05/30 12:25 |
tool |
grepper |
0m
Args: map[Expression:jbd2_handle PathPrefix:fs/jbd2/]
Results: map[Output:fs/jbd2/journal.c=1520=static journal_t *journal_init_common(struct block_device *bdev,
--
fs/jbd2/journal.c-1561- atomic_set(&journal->j_reserved_credits, 0);
fs/jbd2/journal.c:1562: lockdep_init_map(&journal->j_trans_commit_map, "jbd2_handle",
fs/jbd2/journal.c-1563- &jbd2_trans_commit_key, 0);
--
fs/jbd2/journal.c=3050=static void __exit jbd2_remove_jbd_stats_proc_entry(void)
--
fs/jbd2/journal.c-3062-
fs/jbd2/journal.c:3063:struct kmem_cache *jbd2_handle_cache, *jbd2_inode_cache;
fs/jbd2/journal.c-3064-
--
fs/jbd2/journal.c=3076=static int __init jbd2_journal_init_handle_cache(void)
fs/jbd2/journal.c-3077-{
fs/jbd2/journal.c:3078: J_ASSERT(!jbd2_handle_cache);
fs/jbd2/journal.c:3079: jbd2_handle_cache = KMEM_CACHE(jbd2_journal_handle, SLAB_TEMPORARY);
fs/jbd2/journal.c:3080: if (!jbd2_handle_cache) {
fs/jbd2/journal.c-3081- printk(KERN_EMERG "JBD2: failed to create handle cache\n");
--
fs/jbd2/journal.c=3093=static void jbd2_journal_destroy_handle_cache(void)
fs/jbd2/journal.c-3094-{
fs/jbd2/journal.c:3095: kmem_cache_destroy(jbd2_handle_cache);
fs/jbd2/journal.c:3096: jbd2_handle_cache = NULL;
fs/jbd2/journal.c-3097-}
--
fs/jbd2/transaction.c=466=handle_t *jbd2__journal_start(journal_t *journal, int nblocks, int rsv_blocks,
--
fs/jbd2/transaction.c-509- handle->h_line_no = line_no;
fs/jbd2/transaction.c:510: trace_jbd2_handle_start(journal->j_fs_dev->bd_dev,
fs/jbd2/transaction.c-511- handle->h_transaction->t_tid, type,
--
fs/jbd2/transaction.c=580=int jbd2_journal_start_reserved(handle_t *handle, unsigned int type,
--
fs/jbd2/transaction.c-612- handle->h_line_no = line_no;
fs/jbd2/transaction.c:613: trace_jbd2_handle_start(journal->j_fs_dev->bd_dev,
fs/jbd2/transaction.c-614- handle->h_transaction->t_tid, type,
--
fs/jbd2/transaction.c=641=int jbd2_journal_extend(handle_t *handle, int nblocks, int revoke_records)
--
fs/jbd2/transaction.c-678-
fs/jbd2/transaction.c:679: trace_jbd2_handle_extend(journal->j_fs_dev->bd_dev,
fs/jbd2/transaction.c-680- transaction->t_tid,
--
fs/jbd2/transaction.c=759=int jbd2__journal_restart(handle_t *handle, int nblocks, int revoke_records,
--
fs/jbd2/transaction.c-796- ret = start_this_handle(journal, handle, gfp_mask);
fs/jbd2/transaction.c:797: trace_jbd2_handle_restart(journal->j_fs_dev->bd_dev,
fs/jbd2/transaction.c-798- ret ? 0 : handle->h_transaction->t_tid,
--
fs/jbd2/transaction.c=1481=int jbd2_journal_dirty_metadata(handle_t *handle, struct buffer_head *bh)
--
fs/jbd2/transaction.c-1552- */
fs/jbd2/transaction.c:1553: if (WARN_ON_ONCE(jbd2_handle_buffer_credits(handle) <= 0)) {
fs/jbd2/transaction.c-1554- ret = -ENOSPC;
--
fs/jbd2/transaction.c=1821=int jbd2_journal_stop(handle_t *handle)
--
fs/jbd2/transaction.c-1850- jbd2_debug(4, "Handle %p going down\n", handle);
fs/jbd2/transaction.c:1851: trace_jbd2_handle_stats(journal->j_fs_dev->bd_dev,
fs/jbd2/transaction.c-1852- tid, handle->h_type, handle->h_line_no,
]
|
| 26/2 |
2026/05/30 12:25 |
llm |
expert |
0m
Model:
Tokens:
input: 53840
output: 33
thoughts: 6095
Thoughts: **Analyzing a Lockdep False Positive in OCFS2 and Ext4 with JBD2**
Alright, let's break this down. Initially, lockdep flags a potential deadlock scenario. I'm seeing a cycle in the locking order that looks like a serious problem. The chain looks something like this: starting with ocfs2, then jumping through the global JBD2 and quota locks, and back to ocfs2. My concern at first is that a circular dependency like this could lead to deadlocks across filesystems, especially with global resources involved.
Specifically, I'm tracing these locks: `sb_internal#2` from `ocfs2`, the `jbd2_handle` which is shared by all JBD2 filesystems like `ext4`, and `&dquot->dq_lock` that quota uses, also global, and some ocfs2 specific locks. I went through the motions of checking the lock order to see if a single thread could trigger a deadlock by acquiring these locks in the wrong order. However, as I followed the lock acquisitions across different threads, I notice something.
It looks like the `jbd2_handle` for ext4 and the `jbd2_handle` for ocfs2 are different lock instances, *even though* lockdep thinks they are the same because they share the same lock class. Lockdep is reporting a cycle, but it appears to be a false positive due to the aliasing of lock classes. Similarly, the `&dquot->dq_lock` for ext4 and ocfs2, although using the same lock class, are different instances. The lockdep reports seem to chain across these global, *class*-based locks.
It looks like ocfs2 is going to cause the cycle, especially with the `&ocfs2_quota_ip_alloc_sem_key` and `&ocfs2_sysfile_lock_key` and it's calling back to `sb_internal#2`. The thing is, one function calls another and it grabs the lock. For example, `ocfs2_acquire_dquot` calls `ocfs2_extend_allocation` which calls `ocfs2_start_trans` which acquires `sb_internal#2`. But the `ocfs2_acquire_dquot` ALREADY holds `&ocfs2_quota_ip_alloc_sem_key`! That gives the lock order `&ocfs2_quota_ip_alloc_sem_key` -> `sb_internal#2`! But that's not what lockdep sees!
Because I'm tracking the lock classes and not the instances. Lockdep makes these false connections between ext4 and ocfs2. Therefore, this is not a true deadlock. The real lock chain within ocfs2 is: `&ocfs2_quota_ip_alloc_sem_key` -> `GLOBAL_BITMAP_SYSTEM_INODE` -> `sb_internal#2` which seems to make sense. The only potentially confusing part here is the cross-filesystem links that lockdep is building! I have to be careful when using lockdep, especially with global resources!
|
| 27/2 |
2026/05/30 12:26 |
tool |
grepper |
0m
Args: map[Expression:ocfs2_start_trans PathPrefix:fs/ocfs2/]
Results: map[Output:fs/ocfs2/acl.c=158=static int ocfs2_acl_set_mode(struct inode *inode, struct buffer_head *di_bh,
--
fs/ocfs2/acl.c-173- if (handle == NULL) {
fs/ocfs2/acl.c:174: handle = ocfs2_start_trans(OCFS2_SB(inode->i_sb),
fs/ocfs2/acl.c-175- OCFS2_INODE_UPDATE_CREDITS);
--
fs/ocfs2/alloc.c=3118=static int ocfs2_remove_rightmost_empty_extent(struct ocfs2_super *osb,
--
fs/ocfs2/alloc.c-3126-
fs/ocfs2/alloc.c:3127: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/alloc.c-3128- if (IS_ERR(handle)) {
--
fs/ocfs2/alloc.c=5705=int ocfs2_remove_btree_range(struct inode *inode,
--
fs/ocfs2/alloc.c-5759-
fs/ocfs2/alloc.c:5760: handle = ocfs2_start_trans(osb,
fs/ocfs2/alloc.c-5761- ocfs2_remove_extent_credits(osb->sb) + credits);
--
fs/ocfs2/alloc.c=5927=static int ocfs2_replay_truncate_records(struct ocfs2_super *osb,
--
fs/ocfs2/alloc.c-5945- while (i >= 0) {
fs/ocfs2/alloc.c:5946: handle = ocfs2_start_trans(osb, OCFS2_TRUNCATE_LOG_FLUSH_ONE_REC);
fs/ocfs2/alloc.c-5947- if (IS_ERR(handle)) {
--
fs/ocfs2/alloc.c=6274=int ocfs2_complete_truncate_log_recovery(struct ocfs2_super *osb,
--
fs/ocfs2/alloc.c-6305-
fs/ocfs2/alloc.c:6306: handle = ocfs2_start_trans(osb, OCFS2_TRUNCATE_LOG_UPDATE);
fs/ocfs2/alloc.c-6307- if (IS_ERR(handle)) {
--
fs/ocfs2/alloc.c=6415=static int ocfs2_free_cached_blocks(struct ocfs2_super *osb,
--
fs/ocfs2/alloc.c-6447- head->free_bit);
fs/ocfs2/alloc.c:6448: handle = ocfs2_start_trans(osb, OCFS2_SUBALLOC_FREE);
fs/ocfs2/alloc.c-6449- if (IS_ERR(handle)) {
--
fs/ocfs2/alloc.c=6510=static int ocfs2_free_cached_clusters(struct ocfs2_super *osb,
--
fs/ocfs2/alloc.c-6528-
fs/ocfs2/alloc.c:6529: handle = ocfs2_start_trans(osb, OCFS2_TRUNCATE_LOG_UPDATE);
fs/ocfs2/alloc.c-6530- if (IS_ERR(handle)) {
--
fs/ocfs2/alloc.c=7079=int ocfs2_convert_inline_data_to_extents(struct inode *inode,
--
fs/ocfs2/alloc.c-7104-
fs/ocfs2/alloc.c:7105: handle = ocfs2_start_trans(osb,
fs/ocfs2/alloc.c-7106- ocfs2_inline_to_extents_credits(osb->sb));
--
fs/ocfs2/alloc.c=7405=int ocfs2_truncate_inline(struct inode *inode, struct buffer_head *di_bh,
--
fs/ocfs2/alloc.c-7436-
fs/ocfs2/alloc.c:7437: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/alloc.c-7438- if (IS_ERR(handle)) {
--
fs/ocfs2/aops.c=1449=static int ocfs2_write_begin_inline(struct address_space *mapping,
--
fs/ocfs2/aops.c-1458-
fs/ocfs2/aops.c:1459: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/aops.c-1460- if (IS_ERR(handle)) {
--
fs/ocfs2/aops.c=1624=int ocfs2_write_begin_nolock(struct address_space *mapping,
--
fs/ocfs2/aops.c-1748-
fs/ocfs2/aops.c:1749: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/aops.c-1750- if (IS_ERR(handle)) {
--
fs/ocfs2/aops.c=2265=static int ocfs2_dio_end_io_write(struct inode *inode,
--
fs/ocfs2/aops.c-2330-
fs/ocfs2/aops.c:2331: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/aops.c-2332- if (IS_ERR(handle)) {
--
fs/ocfs2/dir.c=2797=static int ocfs2_expand_inline_dir(struct inode *dir, struct buffer_head *di_bh,
--
fs/ocfs2/dir.c-2874-
fs/ocfs2/dir.c:2875: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/dir.c-2876- if (IS_ERR(handle)) {
--
fs/ocfs2/dir.c=3179=static int ocfs2_extend_dir(struct ocfs2_super *osb,
--
fs/ocfs2/dir.c-3293-
fs/ocfs2/dir.c:3294: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/dir.c-3295- if (IS_ERR(handle)) {
--
fs/ocfs2/dir.c=3710=static int ocfs2_dx_dir_rebalance(struct ocfs2_super *osb, struct inode *dir,
--
fs/ocfs2/dir.c-3773- credits = ocfs2_dx_dir_rebalance_credits(osb, dx_root);
fs/ocfs2/dir.c:3774: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/dir.c-3775- if (IS_ERR(handle)) {
--
fs/ocfs2/dir.c=4026=static int ocfs2_expand_inline_dx_root(struct inode *dir,
--
fs/ocfs2/dir.c-4053-
fs/ocfs2/dir.c:4054: handle = ocfs2_start_trans(osb, ocfs2_calc_dxi_expand_credits(osb->sb));
fs/ocfs2/dir.c-4055- if (IS_ERR(handle)) {
--
fs/ocfs2/dir.c=4330=static int ocfs2_dx_dir_remove_index(struct inode *dir,
--
fs/ocfs2/dir.c-4362-
fs/ocfs2/dir.c:4363: handle = ocfs2_start_trans(osb, OCFS2_DX_ROOT_REMOVE_CREDITS);
fs/ocfs2/dir.c-4364- if (IS_ERR(handle)) {
--
fs/ocfs2/file.c=253=int ocfs2_update_inode_atime(struct inode *inode,
--
fs/ocfs2/file.c-260-
fs/ocfs2/file.c:261: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/file.c-262- if (IS_ERR(handle)) {
--
fs/ocfs2/file.c=313=int ocfs2_simple_size_update(struct inode *inode,
--
fs/ocfs2/file.c-320-
fs/ocfs2/file.c:321: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/file.c-322- if (IS_ERR(handle)) {
--
fs/ocfs2/file.c=372=static int ocfs2_orphan_for_truncate(struct ocfs2_super *osb,
--
fs/ocfs2/file.c-395-
fs/ocfs2/file.c:396: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/file.c-397- if (IS_ERR(handle)) {
--
fs/ocfs2/file.c=555=static int ocfs2_extend_allocation(struct inode *inode, u32 logical_start,
--
fs/ocfs2/file.c-596- credits = ocfs2_calc_extend_credits(osb->sb, &fe->id2.i_list);
fs/ocfs2/file.c:597: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/file.c-598- if (IS_ERR(handle)) {
--
fs/ocfs2/file.c=711=static handle_t *ocfs2_zero_start_ordered_transaction(struct inode *inode,
--
fs/ocfs2/file.c-722-
fs/ocfs2/file.c:723: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/file.c-724- if (IS_ERR(handle)) {
--
fs/ocfs2/file.c=1114=int ocfs2_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
--
fs/ocfs2/file.c-1250- down_write(&OCFS2_I(inode)->ip_alloc_sem);
fs/ocfs2/file.c:1251: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS +
fs/ocfs2/file.c-1252- 2 * ocfs2_quota_trans_credits(sb));
--
fs/ocfs2/file.c-1262- down_write(&OCFS2_I(inode)->ip_alloc_sem);
fs/ocfs2/file.c:1263: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/file.c-1264- if (IS_ERR(handle)) {
--
fs/ocfs2/file.c=1373=static int __ocfs2_write_remove_suid(struct inode *inode,
--
fs/ocfs2/file.c-1384-
fs/ocfs2/file.c:1385: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/file.c-1386- if (IS_ERR(handle)) {
--
fs/ocfs2/file.c=1575=static int ocfs2_zero_partial_clusters(struct inode *inode,
--
fs/ocfs2/file.c-1624- }
fs/ocfs2/file.c:1625: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/file.c-1626- if (IS_ERR(handle)) {
--
fs/ocfs2/file.c=1933=static int __ocfs2_change_file_space(struct file *file, struct inode *inode,
--
fs/ocfs2/file.c-2051- */
fs/ocfs2/file.c:2052: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/file.c-2053- if (IS_ERR(handle)) {
--
fs/ocfs2/inode.c=654=static int ocfs2_truncate_for_delete(struct ocfs2_super *osb,
--
fs/ocfs2/inode.c-671-
fs/ocfs2/inode.c:672: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/inode.c-673- if (IS_ERR(handle)) {
--
fs/ocfs2/inode.c=710=static int ocfs2_remove_inode(struct inode *inode,
--
fs/ocfs2/inode.c-739-
fs/ocfs2/inode.c:740: handle = ocfs2_start_trans(osb, OCFS2_DELETE_INODE_CREDITS +
fs/ocfs2/inode.c-741- ocfs2_quota_trans_credits(inode->i_sb));
--
fs/ocfs2/ioctl.c=85=int ocfs2_fileattr_set(struct mnt_idmap *idmap,
--
fs/ocfs2/ioctl.c-118-
fs/ocfs2/ioctl.c:119: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/ioctl.c-120- if (IS_ERR(handle)) {
--
fs/ocfs2/journal.c=312=static int ocfs2_commit_cache(struct ocfs2_super *osb)
--
fs/ocfs2/journal.c-352-
fs/ocfs2/journal.c:353:handle_t *ocfs2_start_trans(struct ocfs2_super *osb, int max_buffs)
fs/ocfs2/journal.c-354-{
--
fs/ocfs2/journal.c=655=static int __ocfs2_journal_access(handle_t *handle,
--
fs/ocfs2/journal.c-701- * thread updating the current transaction id until
fs/ocfs2/journal.c:702: * ocfs2_commit_trans() because ocfs2_start_trans() took
fs/ocfs2/journal.c-703- * j_trans_barrier for us. */
--
fs/ocfs2/journal.h=192=static inline void ocfs2_checkpoint_inode(struct inode *inode)
--
fs/ocfs2/journal.h-216- *
fs/ocfs2/journal.h:217: * ocfs2_start_trans - Begin a transaction. Give it an upper estimate of
fs/ocfs2/journal.h-218- * the number of blocks that will be changed during
--
fs/ocfs2/journal.h-241- * dirtied any buffers. */
fs/ocfs2/journal.h:242:handle_t *ocfs2_start_trans(struct ocfs2_super *osb,
fs/ocfs2/journal.h-243- int max_buffs);
--
fs/ocfs2/localalloc.c=367=void ocfs2_shutdown_local_alloc(struct ocfs2_super *osb)
--
fs/ocfs2/localalloc.c-416- /* WINDOW_MOVE_CREDITS is a bit heavy... */
fs/ocfs2/localalloc.c:417: handle = ocfs2_start_trans(osb, OCFS2_WINDOW_MOVE_CREDITS);
fs/ocfs2/localalloc.c-418- if (IS_ERR(handle)) {
--
fs/ocfs2/localalloc.c=547=int ocfs2_complete_local_alloc_recovery(struct ocfs2_super *osb,
--
fs/ocfs2/localalloc.c-571-
fs/ocfs2/localalloc.c:572: handle = ocfs2_start_trans(osb, OCFS2_WINDOW_MOVE_CREDITS);
fs/ocfs2/localalloc.c-573- if (IS_ERR(handle)) {
--
fs/ocfs2/localalloc.c=1230=static int ocfs2_local_alloc_slide_window(struct ocfs2_super *osb,
--
fs/ocfs2/localalloc.c-1253-
fs/ocfs2/localalloc.c:1254: handle = ocfs2_start_trans(osb, OCFS2_WINDOW_MOVE_CREDITS);
fs/ocfs2/localalloc.c-1255- if (IS_ERR(handle)) {
--
fs/ocfs2/move_extents.c=195=static int ocfs2_defrag_extent(struct ocfs2_move_extents_context *context,
--
fs/ocfs2/move_extents.c-271-
fs/ocfs2/move_extents.c:272: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/move_extents.c-273- if (IS_ERR(handle)) {
--
fs/ocfs2/move_extents.c=562=static int ocfs2_move_extent(struct ocfs2_move_extents_context *context,
--
fs/ocfs2/move_extents.c-641-
fs/ocfs2/move_extents.c:642: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/move_extents.c-643- if (IS_ERR(handle)) {
--
fs/ocfs2/move_extents.c=892=static int ocfs2_move_extents(struct ocfs2_move_extents_context *context)
--
fs/ocfs2/move_extents.c-936- */
fs/ocfs2/move_extents.c:937: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/move_extents.c-938- if (IS_ERR(handle)) {
--
fs/ocfs2/namei.c=229=static int ocfs2_mknod(struct mnt_idmap *idmap,
--
fs/ocfs2/namei.c-363-
fs/ocfs2/namei.c:364: handle = ocfs2_start_trans(osb, ocfs2_mknod_credits(osb->sb,
fs/ocfs2/namei.c-365- S_ISDIR(mode),
--
fs/ocfs2/namei.c=683=static int ocfs2_link(struct dentry *old_dentry,
--
fs/ocfs2/namei.c-780-
fs/ocfs2/namei.c:781: handle = ocfs2_start_trans(osb, ocfs2_link_credits(osb->sb));
fs/ocfs2/namei.c-782- if (IS_ERR(handle)) {
--
fs/ocfs2/namei.c=877=static int ocfs2_unlink(struct inode *dir,
--
fs/ocfs2/namei.c-972-
fs/ocfs2/namei.c:973: handle = ocfs2_start_trans(osb, ocfs2_unlink_credits(osb->sb));
fs/ocfs2/namei.c-974- if (IS_ERR(handle)) {
--
fs/ocfs2/namei.c=1204=static int ocfs2_rename(struct mnt_idmap *idmap,
--
fs/ocfs2/namei.c-1490-
fs/ocfs2/namei.c:1491: handle = ocfs2_start_trans(osb, ocfs2_rename_credits(osb->sb));
fs/ocfs2/namei.c-1492- if (IS_ERR(handle)) {
--
fs/ocfs2/namei.c=1809=static int ocfs2_symlink(struct mnt_idmap *idmap,
--
fs/ocfs2/namei.c-1929-
fs/ocfs2/namei.c:1930: handle = ocfs2_start_trans(osb, credits + xattr_credits);
fs/ocfs2/namei.c-1931- if (IS_ERR(handle)) {
--
fs/ocfs2/namei.c=2516=int ocfs2_create_inode_in_orphan(struct inode *dir,
--
fs/ocfs2/namei.c-2556-
fs/ocfs2/namei.c:2557: handle = ocfs2_start_trans(osb, ocfs2_mknod_credits(osb->sb, 0, 0));
fs/ocfs2/namei.c-2558- if (IS_ERR(handle)) {
--
fs/ocfs2/namei.c=2635=int ocfs2_add_inode_to_orphan(struct ocfs2_super *osb,
--
fs/ocfs2/namei.c-2681-
fs/ocfs2/namei.c:2682: handle = ocfs2_start_trans(osb,
fs/ocfs2/namei.c-2683- OCFS2_INODE_ADD_TO_ORPHAN_CREDITS);
--
fs/ocfs2/namei.c=2711=int ocfs2_del_inode_from_orphan(struct ocfs2_super *osb,
--
fs/ocfs2/namei.c-2738-
fs/ocfs2/namei.c:2739: handle = ocfs2_start_trans(osb,
fs/ocfs2/namei.c-2740- OCFS2_INODE_DEL_FROM_ORPHAN_CREDITS);
--
fs/ocfs2/namei.c=2787=int ocfs2_mv_orphaned_inode_to_new(struct inode *dir,
--
fs/ocfs2/namei.c-2858-
fs/ocfs2/namei.c:2859: handle = ocfs2_start_trans(osb, ocfs2_rename_credits(osb->sb));
fs/ocfs2/namei.c-2860- if (IS_ERR(handle)) {
--
fs/ocfs2/quota_global.c=603=static int ocfs2_sync_dquot_helper(struct dquot *dquot, unsigned long type)
--
fs/ocfs2/quota_global.c-620-
fs/ocfs2/quota_global.c:621: handle = ocfs2_start_trans(osb, OCFS2_QSYNC_CREDITS);
fs/ocfs2/quota_global.c-622- if (IS_ERR(handle)) {
--
fs/ocfs2/quota_global.c=669=static int ocfs2_write_dquot(struct dquot *dquot)
--
fs/ocfs2/quota_global.c-678-
fs/ocfs2/quota_global.c:679: handle = ocfs2_start_trans(osb, OCFS2_QWRITE_CREDITS);
fs/ocfs2/quota_global.c-680- if (IS_ERR(handle)) {
--
fs/ocfs2/quota_global.c=730=static int ocfs2_release_dquot(struct dquot *dquot)
--
fs/ocfs2/quota_global.c-760- goto out;
fs/ocfs2/quota_global.c:761: handle = ocfs2_start_trans(osb,
fs/ocfs2/quota_global.c-762- ocfs2_calc_qdel_credits(dquot->dq_sb, dquot->dq_id.type));
--
fs/ocfs2/quota_global.c=809=static int ocfs2_acquire_dquot(struct dquot *dquot)
--
fs/ocfs2/quota_global.c-859-
fs/ocfs2/quota_global.c:860: handle = ocfs2_start_trans(osb,
fs/ocfs2/quota_global.c-861- ocfs2_calc_global_qinit_credits(sb, type));
--
fs/ocfs2/quota_global.c=925=static int ocfs2_mark_dquot_dirty(struct dquot *dquot)
--
fs/ocfs2/quota_global.c-959- goto out;
fs/ocfs2/quota_global.c:960: handle = ocfs2_start_trans(osb, OCFS2_QSYNC_CREDITS);
fs/ocfs2/quota_global.c-961- if (IS_ERR(handle)) {
--
fs/ocfs2/quota_global.c=988=static int ocfs2_write_info(struct super_block *sb, int type)
--
fs/ocfs2/quota_global.c-996- goto out;
fs/ocfs2/quota_global.c:997: handle = ocfs2_start_trans(OCFS2_SB(sb), OCFS2_QINFO_WRITE_CREDITS);
fs/ocfs2/quota_global.c-998- if (IS_ERR(handle)) {
--
fs/ocfs2/quota_local.c=94=static int ocfs2_modify_bh(struct inode *inode, struct buffer_head *bh,
--
fs/ocfs2/quota_local.c-100-
fs/ocfs2/quota_local.c:101: handle = ocfs2_start_trans(OCFS2_SB(sb),
fs/ocfs2/quota_local.c-102- OCFS2_QUOTA_BLOCK_UPDATE_CREDITS);
--
fs/ocfs2/quota_local.c=457=static int ocfs2_recover_local_quota_file(struct inode *lqinode,
--
fs/ocfs2/quota_local.c-515-
fs/ocfs2/quota_local.c:516: handle = ocfs2_start_trans(OCFS2_SB(sb),
fs/ocfs2/quota_local.c-517- OCFS2_QSYNC_CREDITS);
--
fs/ocfs2/quota_local.c=584=int ocfs2_finish_quota_recovery(struct ocfs2_super *osb,
--
fs/ocfs2/quota_local.c-646- * some other node. */
fs/ocfs2/quota_local.c:647: handle = ocfs2_start_trans(osb,
fs/ocfs2/quota_local.c-648- OCFS2_LOCAL_QINFO_WRITE_CREDITS);
--
fs/ocfs2/quota_local.c=960=static struct ocfs2_quota_chunk *ocfs2_local_quota_add_chunk(
--
fs/ocfs2/quota_local.c-996- /* Local quota info and two new blocks we initialize */
fs/ocfs2/quota_local.c:997: handle = ocfs2_start_trans(OCFS2_SB(sb),
fs/ocfs2/quota_local.c-998- OCFS2_LOCAL_QINFO_WRITE_CREDITS +
--
fs/ocfs2/quota_local.c=1091=static struct ocfs2_quota_chunk *ocfs2_extend_local_quota_file(
--
fs/ocfs2/quota_local.c-1148- /* Local quota info, chunk header and the new block we initialize */
fs/ocfs2/quota_local.c:1149: handle = ocfs2_start_trans(OCFS2_SB(sb),
fs/ocfs2/quota_local.c-1150- OCFS2_LOCAL_QINFO_WRITE_CREDITS +
--
fs/ocfs2/refcounttree.c=550=static int ocfs2_create_refcount_tree(struct inode *inode,
--
fs/ocfs2/refcounttree.c-576-
fs/ocfs2/refcounttree.c:577: handle = ocfs2_start_trans(osb, OCFS2_REFCOUNT_TREE_CREATE_CREDITS);
fs/ocfs2/refcounttree.c-578- if (IS_ERR(handle)) {
--
fs/ocfs2/refcounttree.c=691=static int ocfs2_set_refcount_tree(struct inode *inode,
--
fs/ocfs2/refcounttree.c-712-
fs/ocfs2/refcounttree.c:713: handle = ocfs2_start_trans(osb, OCFS2_REFCOUNT_TREE_SET_CREDITS);
fs/ocfs2/refcounttree.c-714- if (IS_ERR(handle)) {
--
fs/ocfs2/refcounttree.c=755=int ocfs2_remove_refcount_tree(struct inode *inode, struct buffer_head *di_bh)
--
fs/ocfs2/refcounttree.c-813-
fs/ocfs2/refcounttree.c:814: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/refcounttree.c-815- if (IS_ERR(handle)) {
--
fs/ocfs2/refcounttree.c=3166=static int ocfs2_make_clusters_writable(struct super_block *sb,
--
fs/ocfs2/refcounttree.c-3197- credits += context->extra_credits;
fs/ocfs2/refcounttree.c:3198: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/refcounttree.c-3199- if (IS_ERR(handle)) {
--
fs/ocfs2/refcounttree.c=3645=int ocfs2_add_refcount_flag(struct inode *inode,
--
fs/ocfs2/refcounttree.c-3684-
fs/ocfs2/refcounttree.c:3685: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/refcounttree.c-3686- if (IS_ERR(handle)) {
--
fs/ocfs2/refcounttree.c=3722=static int ocfs2_change_ctime(struct inode *inode,
--
fs/ocfs2/refcounttree.c-3728-
fs/ocfs2/refcounttree.c:3729: handle = ocfs2_start_trans(OCFS2_SB(inode->i_sb),
fs/ocfs2/refcounttree.c-3730- OCFS2_INODE_UPDATE_CREDITS);
--
fs/ocfs2/refcounttree.c=3858=static int ocfs2_add_refcounted_extent(struct inode *inode,
--
fs/ocfs2/refcounttree.c-3881-
fs/ocfs2/refcounttree.c:3882: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/refcounttree.c-3883- if (IS_ERR(handle)) {
--
fs/ocfs2/refcounttree.c=3918=static int ocfs2_duplicate_inline_data(struct inode *s_inode,
--
fs/ocfs2/refcounttree.c-3930-
fs/ocfs2/refcounttree.c:3931: handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
fs/ocfs2/refcounttree.c-3932- if (IS_ERR(handle)) {
--
fs/ocfs2/refcounttree.c=4013=static int ocfs2_complete_reflink(struct inode *s_inode,
--
fs/ocfs2/refcounttree.c-4024-
fs/ocfs2/refcounttree.c:4025: handle = ocfs2_start_trans(OCFS2_SB(t_inode->i_sb),
fs/ocfs2/refcounttree.c-4026- OCFS2_INODE_UPDATE_CREDITS);
--
fs/ocfs2/refcounttree.c=4446=int ocfs2_reflink_update_dest(struct inode *dest,
--
fs/ocfs2/refcounttree.c-4457-
fs/ocfs2/refcounttree.c:4458: handle = ocfs2_start_trans(OCFS2_SB(dest->i_sb),
fs/ocfs2/refcounttree.c-4459- OCFS2_INODE_UPDATE_CREDITS);
--
fs/ocfs2/resize.c=265=int ocfs2_group_extend(struct inode * inode, int new_clusters)
--
fs/ocfs2/resize.c-341-
fs/ocfs2/resize.c:342: handle = ocfs2_start_trans(osb, OCFS2_GROUP_EXTEND_CREDITS);
fs/ocfs2/resize.c-343- if (IS_ERR(handle)) {
--
fs/ocfs2/resize.c=454=int ocfs2_group_add(struct inode *inode, struct ocfs2_new_group_input *input)
--
fs/ocfs2/resize.c-517-
fs/ocfs2/resize.c:518: handle = ocfs2_start_trans(osb, OCFS2_GROUP_ADD_CREDITS);
fs/ocfs2/resize.c-519- if (IS_ERR(handle)) {
--
fs/ocfs2/suballoc.c=655=static int ocfs2_block_group_alloc(struct ocfs2_super *osb,
--
fs/ocfs2/suballoc.c-684- le16_to_cpu(cl->cl_cpg));
fs/ocfs2/suballoc.c:685: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/suballoc.c-686- if (IS_ERR(handle)) {
--
fs/ocfs2/suballoc.c=2140=int ocfs2_find_new_inode_loc(struct inode *dir,
--
fs/ocfs2/suballoc.c-2166- */
fs/ocfs2/suballoc.c:2167: handle = ocfs2_start_trans(OCFS2_SB(dir->i_sb), OCFS2_SUBALLOC_ALLOC);
fs/ocfs2/suballoc.c-2168- if (IS_ERR(handle)) {
--
fs/ocfs2/xattr.c=2364=static int ocfs2_remove_value_outside(struct inode*inode,
--
fs/ocfs2/xattr.c-2392-
fs/ocfs2/xattr.c:2393: ctxt.handle = ocfs2_start_trans(osb, ref_credits +
fs/ocfs2/xattr.c-2394- ocfs2_remove_extent_credits(osb->sb));
--
fs/ocfs2/xattr.c=2482=static int ocfs2_xattr_free_block(struct inode *inode,
--
fs/ocfs2/xattr.c-2532-
fs/ocfs2/xattr.c:2533: handle = ocfs2_start_trans(osb, OCFS2_SUBALLOC_FREE);
fs/ocfs2/xattr.c-2534- if (IS_ERR(handle)) {
--
fs/ocfs2/xattr.c=2562=int ocfs2_xattr_remove(struct inode *inode, struct buffer_head *di_bh)
--
fs/ocfs2/xattr.c-2608-
fs/ocfs2/xattr.c:2609: handle = ocfs2_start_trans((OCFS2_SB(inode->i_sb)),
fs/ocfs2/xattr.c-2610- OCFS2_INODE_UPDATE_CREDITS);
--
fs/ocfs2/xattr.c=3529=int ocfs2_xattr_set(struct inode *inode,
--
fs/ocfs2/xattr.c-3642- credits += OCFS2_INODE_UPDATE_CREDITS;
fs/ocfs2/xattr.c:3643: ctxt.handle = ocfs2_start_trans(osb, credits + ref_credits);
fs/ocfs2/xattr.c-3644- if (IS_ERR(ctxt.handle)) {
--
fs/ocfs2/xattr.c=5410=static int ocfs2_rm_xattr_cluster(struct inode *inode,
--
fs/ocfs2/xattr.c-5460-
fs/ocfs2/xattr.c:5461: handle = ocfs2_start_trans(osb, ocfs2_remove_extent_credits(osb->sb));
fs/ocfs2/xattr.c-5462- if (IS_ERR(handle)) {
--
fs/ocfs2/xattr.c=5646=static int ocfs2_delete_xattr_in_bucket(struct inode *inode,
--
fs/ocfs2/xattr.c-5681-
fs/ocfs2/xattr.c:5682: ctxt.handle = ocfs2_start_trans(osb, credits + ref_credits);
fs/ocfs2/xattr.c-5683- if (IS_ERR(ctxt.handle)) {
--
fs/ocfs2/xattr.c=6457=static int ocfs2_reflink_xattr_inline(struct ocfs2_xattr_reflink *args)
--
fs/ocfs2/xattr.c-6483-
fs/ocfs2/xattr.c:6484: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/xattr.c-6485- if (IS_ERR(handle)) {
--
fs/ocfs2/xattr.c=6530=static int ocfs2_create_empty_xattr_block(struct inode *inode,
--
fs/ocfs2/xattr.c-6545-
fs/ocfs2/xattr.c:6546: ctxt.handle = ocfs2_start_trans(osb, OCFS2_XATTR_BLOCK_CREATE_CREDITS);
fs/ocfs2/xattr.c-6547- if (IS_ERR(ctxt.handle)) {
--
fs/ocfs2/xattr.c=6566=static int ocfs2_reflink_xattr_block(struct ocfs2_xattr_reflink *args,
--
fs/ocfs2/xattr.c-6595- /* One more credits in case we need to add xattr flags in new inode. */
fs/ocfs2/xattr.c:6596: handle = ocfs2_start_trans(osb, credits + 1);
fs/ocfs2/xattr.c-6597- if (IS_ERR(handle)) {
--
fs/ocfs2/xattr.c=6979=static int ocfs2_reflink_xattr_rec(struct inode *inode,
--
fs/ocfs2/xattr.c-7008-
fs/ocfs2/xattr.c:7009: handle = ocfs2_start_trans(osb, credits);
fs/ocfs2/xattr.c-7010- if (IS_ERR(handle)) {
]
|
| 28/2 |
2026/05/30 12:26 |
llm |
expert |
0m
Model:
Tokens:
input: 71315
output: 45
thoughts: 1117
Thoughts: **Analyzing a Potential Deadlock in OCFS2 Quota Management**
Okay, so I'm diving into this OCFS2 code. My initial thought was to see where `ocfs2_start_trans` is called, because that seems fundamental to transaction management. But the real question is about the lock ordering: specifically, does `sb_internal#2` (which is acquired when starting a write transaction via `sb_start_intwrite`) come *before* or *after* acquiring `&ocfs2_quota_ip_alloc_sem_key`? `sb_start_intwrite` is indeed acquired inside `ocfs2_start_trans`, so any call to `ocfs2_start_trans` grabs `sb_internal#2`.
The next step is to find out if there's any function calling `ocfs2_start_trans` that *then* tries to grab `&ocfs2_quota_ip_alloc_sem_key`. Now, `&ocfs2_quota_ip_alloc_sem_key` is the `ip_alloc_sem` for the global quota file. It's acquired in `ocfs2_lock_global_qf`, which is used in `ocfs2_acquire_dquot`, `ocfs2_release_dquot`, `ocfs2_mark_dquot_dirty`, `ocfs2_write_info`, `ocfs2_read_quota_phys_block`, and `ocfs2_create_local_dquot`.
Let's start by digging into `ocfs2_mark_dquot_dirty`. Looking at the code:
```c
static int ocfs2_mark_dquot_dirty(struct dquot *dquot)
{
...
handle = ocfs2_start_trans(osb, OCFS2_QSYNC_CREDITS);
if (IS_ERR(handle)) {
status = PTR_ERR(handle);
goto out;
}
status = ocfs2_lock_global_qf(info, 1);
if (status < 0)
goto out_trans;
...
}
```
Aha! `ocfs2_mark_dquot_dirty` *does* call `ocfs2_start_trans` first (which takes `sb_internal#2`), and *then* it calls `ocfs2_lock_global_qf` (which takes `&ocfs2_quota_ip_alloc_sem_key`). So that proves there's a real dependency where `sb_internal#2` comes before `&ocfs2_quota_ip_alloc_sem_key` within OCFS2.
Let's confirm that further. In `ocfs2_mark_dquot_dirty`, we can see:
```c
handle = ocfs2_start_trans(osb, OCFS2_QSYNC_CREDITS); // takes sb_internal#2
...
status = ocfs2_lock_global_qf(info, 1); // takes &ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE] and &ocfs2_quota_ip_alloc_sem_key
```
So it looks like the lock order in `ocfs2_mark_dquot_dirty` is `sb_internal#2` -> `&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]` -> `&ocfs2_quota_ip_alloc_sem_key`.
Now let's examine `ocfs2_acquire_dquot`:
```c
status = ocfs2_lock_global_qf(info, 1); // takes &ocfs2_quota_ip_alloc_sem_key
...
if (!dquot->dq_off) {
...
status = ocfs2_extend_no_holes(gqinode, ...); // calls ocfs2_extend_allocation
```
And `ocfs2_extend_allocation`:
```c
status = ocfs2_lock_allocators(inode, ...); // takes &ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]
...
handle = ocfs2_start_trans(osb, credits); // takes sb_internal#2
```
Here's the interesting part: `ocfs2_acquire_dquot` calls `ocfs2_lock_global_qf` (acquiring `&ocfs2_
|
| 29/2 |
2026/05/30 12:26 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/quota_global.c Name:ocfs2_mark_dquot_dirty]
Results: map[SourceCode: 925: static int ocfs2_mark_dquot_dirty(struct dquot *dquot)
926: {
927: unsigned long mask = (1 << (DQ_LASTSET_B + QIF_ILIMITS_B)) |
928: (1 << (DQ_LASTSET_B + QIF_BLIMITS_B)) |
929: (1 << (DQ_LASTSET_B + QIF_INODES_B)) |
930: (1 << (DQ_LASTSET_B + QIF_SPACE_B)) |
931: (1 << (DQ_LASTSET_B + QIF_BTIME_B)) |
932: (1 << (DQ_LASTSET_B + QIF_ITIME_B));
933: int sync = 0;
934: int status;
935: struct super_block *sb = dquot->dq_sb;
936: int type = dquot->dq_id.type;
937: struct ocfs2_mem_dqinfo *oinfo = sb_dqinfo(sb, type)->dqi_priv;
938: handle_t *handle;
939: struct ocfs2_super *osb = OCFS2_SB(sb);
940: unsigned int memalloc;
941:
942: trace_ocfs2_mark_dquot_dirty(from_kqid(&init_user_ns, dquot->dq_id),
943: type);
944:
945: /* In case user set some limits, sync dquot immediately to global
946: * quota file so that information propagates quicker */
947: spin_lock(&dquot->dq_dqb_lock);
948: if (dquot->dq_flags & mask)
949: sync = 1;
950: spin_unlock(&dquot->dq_dqb_lock);
951: /* This is a slight hack but we can't afford getting global quota
952: * lock if we already have a transaction started. */
953: if (!sync || journal_current_handle()) {
954: status = ocfs2_write_dquot(dquot);
955: goto out;
956: }
957: status = ocfs2_lock_global_qf(oinfo, 1);
958: if (status < 0)
959: goto out;
960: handle = ocfs2_start_trans(osb, OCFS2_QSYNC_CREDITS);
961: if (IS_ERR(handle)) {
962: status = PTR_ERR(handle);
963: mlog_errno(status);
964: goto out_ilock;
965: }
966: down_write(&sb_dqopt(sb)->dqio_sem);
967: memalloc = memalloc_nofs_save();
968: status = ocfs2_sync_dquot(dquot);
969: if (status < 0) {
970: mlog_errno(status);
971: goto out_dlock;
972: }
973: /* Now write updated local dquot structure */
974: status = ocfs2_local_write_dquot(dquot);
975: out_dlock:
976: memalloc_nofs_restore(memalloc);
977: up_write(&sb_dqopt(sb)->dqio_sem);
978: ocfs2_commit_trans(osb, handle);
979: out_ilock:
980: ocfs2_unlock_global_qf(oinfo, 1);
981: out:
982: if (status)
983: mlog_errno(status);
984: return status;
985: }
SourceFile:fs/ocfs2/quota_global.c]
|
| 30/2 |
2026/05/30 12:26 |
llm |
expert |
0m
Model:
Tokens:
input: 74617
output: 43
thoughts: 232
Thoughts: **Analyzing Potential Deadlock in OCFS2 Quota Management**
Okay, let's break this down. In looking at the `ocfs2_mark_dquot_dirty` function, I see something interesting in the locking order. The code calls `ocfs2_lock_global_qf` *before* `ocfs2_start_trans`. My initial reaction is "aha!" because that pre-transaction lock acquisition order is crucial for preventing deadlocks. Specifically, it means the code grabs the `&ocfs2_quota_ip_alloc_sem_key` semaphore before it takes the `sb_internal#2` spinlock, right? And that's the *same* order as in `ocfs2_acquire_dquot`! So, there is **no** AB-BA deadlock between those two locks in the `ocfs2_mark_dquot_dirty` function. That's a good find; one potential source of trouble is avoided. Now I need to check `ocfs2_write_dquot` as well, as that function is called if the `sync` flag isn't set, or if there is a journal handle current. That's next on the list.
|
| 31/2 |
2026/05/30 12:26 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/ocfs2/quota_global.c Name:ocfs2_write_dquot]
Results: map[SourceCode: 669: static int ocfs2_write_dquot(struct dquot *dquot)
670: {
671: handle_t *handle;
672: struct ocfs2_super *osb = OCFS2_SB(dquot->dq_sb);
673: int status = 0;
674: unsigned int memalloc;
675:
676: trace_ocfs2_write_dquot(from_kqid(&init_user_ns, dquot->dq_id),
677: dquot->dq_id.type);
678:
679: handle = ocfs2_start_trans(osb, OCFS2_QWRITE_CREDITS);
680: if (IS_ERR(handle)) {
681: status = PTR_ERR(handle);
682: mlog_errno(status);
683: goto out;
684: }
685: down_write(&sb_dqopt(dquot->dq_sb)->dqio_sem);
686: memalloc = memalloc_nofs_save();
687: status = ocfs2_local_write_dquot(dquot);
688: memalloc_nofs_restore(memalloc);
689: up_write(&sb_dqopt(dquot->dq_sb)->dqio_sem);
690: ocfs2_commit_trans(osb, handle);
691: out:
692: return status;
693: }
SourceFile:fs/ocfs2/quota_global.c]
|
| 32/2 |
2026/05/30 12:26 |
llm |
expert |
0m
Model:
Tokens:
input: 75611
output: 37
thoughts: 549
Thoughts: **My Analysis of OCFS2 Dquot Locking**
Okay, let's break this down. My initial thought was that `ocfs2_write_dquot` acquires `sb_internal#2` through `ocfs2_start_trans` but *doesn't* call `ocfs2_lock_global_qf`. Thatβs my starting point. The crucial question is whether `sb_internal#2` is ever acquired *before* `&ocfs2_quota_ip_alloc_sem_key`.
I need to confirm this. Let's look at `ocfs2_setattr`. Examining the code, I see:
```c
down_write(&OCFS2_I(inode)->ip_alloc_sem);
handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS +
2 * ocfs2_quota_trans_credits(sb));
```
Aha! We acquire `&OCFS2_I(inode)->ip_alloc_sem`, which *seems* like the crucial semaphore. Inside the function, the sequence of operations becomes important. Wait, `__dquot_transfer` is called here. Does that call `dqget`? Yes, according to the code, `ocfs2_setattr` calls `dqget` *before* `ocfs2_start_trans`. The relevant code snippet is:
```c
transfer_to[USRQUOTA] = dqget(sb, make_kqid_uid(attr->ia_uid));
...
down_write(&OCFS2_I(inode)->ip_alloc_sem);
handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS +
2 * ocfs2_quota_trans_credits(sb));
...
status = __dquot_transfer(inode, transfer_to);
```
So, `dqget` *is* called before `ocfs2_start_trans`. We know `dqget` calls `ocfs2_acquire_dquot`. This means `ocfs2_acquire_dquot` acquires `&ocfs2_quota_ip_alloc_sem_key` *and then* `sb_internal#2`, via `ocfs2_extend_allocation`.
Therefore, the critical locking order is definitively `&ocfs2_quota_ip_alloc_sem_key` -> `sb_internal#2` in this specific scenario.
Finally, just to be sure, I need to confirm `__dquot_transfer` is a quota function (and not just an intermediary) and I'm good.
|
| 33/2 |
2026/05/30 12:26 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/quota/dquot.c Name:__dquot_transfer]
Results: map[SourceCode:1972: /*
1973: * Transfer the number of inode and blocks from one diskquota to an other.
1974: * On success, dquot references in transfer_to are consumed and references
1975: * to original dquots that need to be released are placed there. On failure,
1976: * references are kept untouched.
1977: *
1978: * This operation can block, but only after everything is updated
1979: * A transaction must be started when entering this function.
1980: *
1981: * We are holding reference on transfer_from & transfer_to, no need to
1982: * protect them by srcu_read_lock().
1983: */
1984: int __dquot_transfer(struct inode *inode, struct dquot **transfer_to)
1985: {
1986: qsize_t cur_space;
1987: qsize_t rsv_space = 0;
1988: qsize_t inode_usage = 1;
1989: struct dquot __rcu **dquots;
1990: struct dquot *transfer_from[MAXQUOTAS] = {};
1991: int cnt, index, ret = 0, err;
1992: char is_valid[MAXQUOTAS] = {};
1993: struct dquot_warn warn_to[MAXQUOTAS];
1994: struct dquot_warn warn_from_inodes[MAXQUOTAS];
1995: struct dquot_warn warn_from_space[MAXQUOTAS];
1996:
1997: if (IS_NOQUOTA(inode))
1998: return 0;
1999:
2000: if (inode->i_sb->dq_op->get_inode_usage) {
2001: ret = inode->i_sb->dq_op->get_inode_usage(inode, &inode_usage);
2002: if (ret)
2003: return ret;
2004: }
2005:
2006: /* Initialize the arrays */
2007: for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
2008: warn_to[cnt].w_type = QUOTA_NL_NOWARN;
2009: warn_from_inodes[cnt].w_type = QUOTA_NL_NOWARN;
2010: warn_from_space[cnt].w_type = QUOTA_NL_NOWARN;
2011: }
2012:
2013: spin_lock(&dq_data_lock);
2014: spin_lock(&inode->i_lock);
2015: if (IS_NOQUOTA(inode)) { /* File without quota accounting? */
2016: spin_unlock(&inode->i_lock);
2017: spin_unlock(&dq_data_lock);
2018: return 0;
2019: }
2020: cur_space = __inode_get_bytes(inode);
2021: rsv_space = __inode_get_rsv_space(inode);
2022: dquots = i_dquot(inode);
2023: /*
2024: * Build the transfer_from list, check limits, and update usage in
2025: * the target structures.
2026: */
2027: for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
2028: /*
2029: * Skip changes for same uid or gid or for turned off quota-type.
2030: */
2031: if (!transfer_to[cnt])
2032: continue;
2033: /* Avoid races with quotaoff() */
2034: if (!sb_has_quota_active(inode->i_sb, cnt))
2035: continue;
2036: is_valid[cnt] = 1;
2037: transfer_from[cnt] = srcu_dereference_check(dquots[cnt],
2038: &dquot_srcu, lockdep_is_held(&dq_data_lock));
2039: ret = dquot_add_inodes(transfer_to[cnt], inode_usage,
2040: &warn_to[cnt]);
2041: if (ret)
2042: goto over_quota;
2043: ret = dquot_add_space(transfer_to[cnt], cur_space, rsv_space,
2044: DQUOT_SPACE_WARN, &warn_to[cnt]);
2045: if (ret) {
2046: spin_lock(&transfer_to[cnt]->dq_dqb_lock);
2047: dquot_decr_inodes(transfer_to[cnt], inode_usage);
2048: spin_unlock(&transfer_to[cnt]->dq_dqb_lock);
2049: goto over_quota;
2050: }
2051: }
2052:
2053: /* Decrease usage for source structures and update quota pointers */
2054: for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
2055: if (!is_valid[cnt])
2056: continue;
2057: /* Due to IO error we might not have transfer_from[] structure */
2058: if (transfer_from[cnt]) {
2059: int wtype;
2060:
2061: spin_lock(&transfer_from[cnt]->dq_dqb_lock);
2062: wtype = info_idq_free(transfer_from[cnt], inode_usage);
2063: if (wtype != QUOTA_NL_NOWARN)
2064: prepare_warning(&warn_from_inodes[cnt],
2065: transfer_from[cnt], wtype);
2066: wtype = info_bdq_free(transfer_from[cnt],
2067: cur_space + rsv_space);
2068: if (wtype != QUOTA_NL_NOWARN)
2069: prepare_warning(&warn_from_space[cnt],
2070: transfer_from[cnt], wtype);
2071: dquot_decr_inodes(transfer_from[cnt], inode_usage);
2072: dquot_decr_space(transfer_from[cnt], cur_space);
2073: dquot_free_reserved_space(transfer_from[cnt],
2074: rsv_space);
2075: spin_unlock(&transfer_from[cnt]->dq_dqb_lock);
2076: }
2077: rcu_assign_pointer(dquots[cnt], transfer_to[cnt]);
2078: }
2079: spin_unlock(&inode->i_lock);
2080: spin_unlock(&dq_data_lock);
2081:
2082: /*
2083: * These arrays are local and we hold dquot references so we don't need
2084: * the srcu protection but still take dquot_srcu to avoid warning in
2085: * mark_all_dquot_dirty().
2086: */
2087: index = srcu_read_lock(&dquot_srcu);
2088: err = mark_all_dquot_dirty((struct dquot __rcu **)transfer_from);
2089: if (err < 0)
2090: ret = err;
2091: err = mark_all_dquot_dirty((struct dquot __rcu **)transfer_to);
2092: if (err < 0)
2093: ret = err;
2094: srcu_read_unlock(&dquot_srcu, index);
2095:
2096: flush_warnings(warn_to);
2097: flush_warnings(warn_from_inodes);
2098: flush_warnings(warn_from_space);
2099: /* Pass back references to put */
2100: for (cnt = 0; cnt < MAXQUOTAS; cnt++)
2101: if (is_valid[cnt])
2102: transfer_to[cnt] = transfer_from[cnt];
2103: return ret;
2104: over_quota:
2105: /* Back out changes we already did */
2106: for (cnt--; cnt >= 0; cnt--) {
2107: if (!is_valid[cnt])
2108: continue;
2109: spin_lock(&transfer_to[cnt]->dq_dqb_lock);
2110: dquot_decr_inodes(transfer_to[cnt], inode_usage);
2111: dquot_decr_space(transfer_to[cnt], cur_space);
2112: dquot_free_reserved_space(transfer_to[cnt], rsv_space);
2113: spin_unlock(&transfer_to[cnt]->dq_dqb_lock);
2114: }
2115: spin_unlock(&inode->i_lock);
2116: spin_unlock(&dq_data_lock);
2117: flush_warnings(warn_to);
2118: return ret;
2119: }
SourceFile:fs/quota/dquot.c]
|
| 34/2 |
2026/05/30 12:26 |
llm |
expert |
0m
Model:
Tokens:
input: 79343
output: 64
thoughts: 4777
Thoughts: **Analysis of Potential Lock Deprivation in OCFS2 Quota Handling**
Okay, here's the breakdown of my thought process on this lockdep report. It appears we're looking at a potential lock ordering issue within the OCFS2 filesystem, specifically concerning its quota implementation. My initial concern centered on the interplay between transactions and quota-related operations, particularly within the `__dquot_transfer` function. The sequence starts with `__dquot_transfer` which calls `mark_all_dquot_dirty`, and `ocfs2_mark_dquot_dirty`. This function appears to conditionally call `ocfs2_write_dquot`, nested within a transaction initiated by `ocfs2_start_trans` which, crucially, acquires `sb_internal#2`. The problem is that sometimes `journal_current_handle()` is false and we end up in code paths that acquire locks in different orders.
My first thought was that there was a situation where the nested transaction within `ocfs2_write_dquot` would acquire locks in the wrong order. This led me to suspect a potential deadlock scenario if there's a point where `sb_internal#2` is acquired *before* `&ocfs2_quota_ip_alloc_sem_key`.
Further investigation revealed that, based on the lockdep report, the problem stemmed from the use of global lock classes and interactions with the ext4 filesystem. The lockdep analysis highlighted a purported cycle: `sb_internal#2` (OCFS2) -> `jbd2_handle` (OCFS2) -> `&dquot->dq_lock` (OCFS2) -> `&ocfs2_quota_ip_alloc_sem_key` (OCFS2) -> `sb_internal#2` (OCFS2).
However, a deeper dive into the code reveals that this perceived cycle is most likely a false positive. Because `jbd2_handle` and `&dquot->dq_lock` are global, lockdep connects the `ocfs2` chain and the `ext4` chain.
In `ext4` calls to `jbd2_handle` *do* happen before calls to `&dquot->dq_lock`. This *also* happens in `ocfs2`, but *only* in the case where the function `dqget` is called *before* `ocfs2_start_trans`! However, in the `ocfs2` code being looked at, the calls to `ocfs2_start_trans` that take the `jbd2_handle` happen *before* the calls to `dqget`. In other words, `&dquot->dq_lock` -> `jbd2_handle`. However, it's just this ordering that shows a conflict, leading to the false positive.
The `jbd2_handle` here, is not a real lock. It's a lockdep annotation. Similarly, `&dquot->dq_lock` is NOT a global lock instance, it's a per-dquot mutex. Therefore, the cycle that lockdep sees doesn't actually exist at runtime. A real deadlock requires a cycle of *instances*, not just lock classes, and we don't have that here.
Therefore, this is a lockdep false positive.
Given that it's a false positive:
- Exploitable: No
- Denial of Service: No
- Unprivileged: No
- User Namespace: No
|
| 35/2 |
2026/05/30 12:27 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:fs/ocfs2/quota_global.c IncludeSnippetLines:2 Name:ocfs2_lock_global_qf SourceTreePrefix:fs/ocfs2/]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ocfs2_acquire_dquot SourceFile:fs/ocfs2/quota_global.c SourceLine:828 SourceSnippet: 826: * and instantiate possibly new dquot structure
827: */
828: status = ocfs2_lock_global_qf(info, 1);
829: if (status < 0)
830: goto out;
] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ocfs2_get_next_id SourceFile:fs/ocfs2/quota_global.c SourceLine:905 SourceSnippet: 903: goto out;
904: }
905: status = ocfs2_lock_global_qf(info, 0);
906: if (status < 0)
907: goto out;
] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ocfs2_global_read_info SourceFile:fs/ocfs2/quota_global.c SourceLine:366 SourceSnippet: 364: }
365:
366: status = ocfs2_lock_global_qf(oinfo, 0);
367: if (status < 0) {
368: mlog_errno(status);
] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ocfs2_mark_dquot_dirty SourceFile:fs/ocfs2/quota_global.c SourceLine:957 SourceSnippet: 955: goto out;
956: }
957: status = ocfs2_lock_global_qf(oinfo, 1);
958: if (status < 0)
959: goto out;
] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ocfs2_recover_local_quota_file SourceFile:fs/ocfs2/quota_local.c SourceLine:510 SourceSnippet: 508: goto out_put_bh;
509: }
510: status = ocfs2_lock_global_qf(oinfo, 1);
511: if (status < 0) {
512: mlog_errno(status);
] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ocfs2_release_dquot SourceFile:fs/ocfs2/quota_global.c SourceLine:758 SourceSnippet: 756: goto out;
757: }
758: status = ocfs2_lock_global_qf(oinfo, 1);
759: if (status < 0)
760: goto out;
] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ocfs2_sync_dquot_helper SourceFile:fs/ocfs2/quota_global.c SourceLine:617 SourceSnippet: 615: if (type != dquot->dq_id.type)
616: goto out;
617: status = ocfs2_lock_global_qf(oinfo, 1);
618: if (status < 0)
619: goto out;
] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:ocfs2_write_info SourceFile:fs/ocfs2/quota_global.c SourceLine:994 SourceSnippet: 992: struct ocfs2_mem_dqinfo *oinfo = sb_dqinfo(sb, type)->dqi_priv;
993:
994: status = ocfs2_lock_global_qf(oinfo, 1);
995: if (status < 0)
996: goto out;
]] TruncatedOutput:false]
|
| 36/2 |
2026/05/30 12:27 |
llm |
expert |
0m
Model:
Tokens:
input: 86045
output: 66
thoughts: 1306
Thoughts: **Analyzing the Lockdep Warning: A Deep Dive into OCFS2 Quota Locking**
Okay, let's break this down. My initial thought was to verify the locking order within the `ocfs2` codebase. I needed to confirm the alleged cycle isn't a legitimate issue, and a systematic code review was the only way.
I dove into several key functions: `ocfs2_recover_local_quota_file`, `ocfs2_release_dquot`, `ocfs2_sync_dquot_helper`, and `ocfs2_write_info`. In each of these, I checked the order of the critical lock acquisitions. My focus was on `ocfs2_lock_global_qf` (which acquires `&ocfs2_quota_ip_alloc_sem_key`) and `ocfs2_start_trans` (which acquires `sb_internal#2`).
In every single instance, without exception, `ocfs2_lock_global_qf` is called *before* `ocfs2_start_trans`. This establishes a consistent locking order within OCFS2: `&ocfs2_quota_ip_alloc_sem_key` -> `sb_internal#2`. This strict ordering is a good sign. There is no internal locking cycle within OCFS2.
The lockdep warning, however, indicates a cycle. Now, let's explore how it can arise. The lockdep tool is flagging a chain that appears to link locks across *different* filesystems: `ext4` and `ocfs2`. The chain reported is: `sb_internal#2` -> `&journal->j_trans_barrier` -> `jbd2_handle` -> `&ei->xattr_sem` -> `&dquot->dq_lock` -> `&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]` -> `&ocfs2_quota_ip_alloc_sem_key` -> `&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]` -> `sb_internal#2`.
Here, the link between the two filesystems happens because lockdep tracks lock *classes* and not lock *instances*. This has critical implications. The shared lock classes, such as `jbd2_handle` and `dquot->dq_lock`, are linked to each other because of their very nature as global across the system.
Specifically,
* `ext4` uses `jbd2_handle` -> `&ei->xattr_sem` -> `&dquot->dq_lock`.
* `ocfs2` establishes the locking order by doing `&dquot->dq_lock` -> `&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]` -> `&ocfs2_quota_ip_alloc_sem_key` -> `sb_internal#2`. In addition, `sb_internal#2` can be linked to `jbd2_handle`.
Because lockdep sees these two independent chains and their shared lock classes, it wrongly believes there's a cycle. But at runtime, it's a different story. In reality, the `jbd2_handle` and `dquot->dq_lock` instances used by `ext4` are completely separate from those used by `ocfs2`. No `ocfs2` thread will ever wait on a lock from `ext4` and vice versa.
In summary, this is a *false positive*. The consistent locking order within `ocfs2` itself ( `&ocfs2_quota_ip_alloc_sem_key` -> `sb_internal#2`) confirms there's no deadlock risk. Furthermore, the likelihood of a real-world issue is zero. In production kernels, lockdep is typically disabled, meaning the warning would never even appear, and even if it did, it would not trigger a crash or DoS. There is no security impact.
I've carefully assessed this. The bug is not exploitable, is not a denial of service, does not require special privileges, and isn't triggered through common attack vectors (network, file system, etc.). This is a clear case of a lockdep false positive.
|
| 37/2 |
2026/05/30 12:27 |
tool |
set-results |
0m
Args: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 38/2 |
2026/05/30 12:27 |
llm |
expert |
0m
Model:
Tokens:
input: 88404
output: 792
thoughts: 13
Thoughts: Okay, here's my attempt at summarizing the thought process, as requested, aimed at an expert user:
**Finalizing the Response**
Alright, the data's locked in, the analysis is complete. Now it's time to craft the final response. I've considered all the angles, weighed the implications, and I'm ready to translate this complex information into a clear and compelling narrative. It's about distilling the essence, communicating the nuance, and making sure the conclusion is both sound and readily accessible. Time to synthesize. I'll make sure to cover all the key findings, address the relevant caveats and ensure it adds value to the conversation.
|
| 39/1 |
2026/05/30 12:28 |
action |
format-explanation |
0m
Results: map[Explanation:**Analysis of the Bug Report**
The bug report indicates a `WARNING: possible circular locking dependency
detected` by lockdep. The reported circular dependency chain is:
`sb_internal#2` -> `&journal->j_trans_barrier` -> `jbd2_handle` ->
`&ei->xattr_sem` -> `&dquot->dq_lock` ->
`&ocfs2_sysfile_lock_key[USER_QUOTA_SYSTEM_INODE]` ->
`&ocfs2_quota_ip_alloc_sem_key` ->
`&ocfs2_sysfile_lock_key[GLOBAL_BITMAP_SYSTEM_INODE]` -> `sb_internal#2`
**False Positive due to Lock Class Aliasing**
A closer look at the dependency chain reveals that it spans across two entirely
different filesystems: `ext4` and `ocfs2`.
1. The `ext4` filesystem establishes the chain: `jbd2_handle` ->
`&ei->xattr_sem` -> `&dquot->dq_lock` (e.g., during `ext4_setattr` ->
`dquot_transfer`).
2. The `ocfs2` filesystem establishes the chain: `&dquot->dq_lock` ->
`&ocfs2_sysfile_lock_key[...]` -> `sb_internal#2` -> `jbd2_handle` (e.g., during
`ocfs2_acquire_dquot` -> `ocfs2_extend_allocation` -> `ocfs2_start_trans`).
Lockdep tracks lock dependencies based on lock *classes*, not individual lock
*instances*. Both `jbd2_handle` (used by the JBD2 journaling layer) and
`dquot->dq_lock` (used by the VFS quota subsystem) are initialized with global
static lock class keys. Because `ext4` and `ocfs2` both use JBD2 and VFS quotas,
they share these global lock classes.
Lockdep merges the `ext4` and `ocfs2` chains together because they intersect at
these global lock classes, creating an artificial cycle. However, at runtime,
the `jbd2_handle` and `dquot->dq_lock` instances for an `ext4` mount are
completely independent of the instances for an `ocfs2` mount. A thread operating
on `ext4` will never wait for an `ocfs2` lock instance, and vice versa.
Furthermore, within `ocfs2` itself, the locking order is strictly maintained.
Functions like `ocfs2_mark_dquot_dirty`, `ocfs2_acquire_dquot`, and
`ocfs2_write_info` consistently acquire the global quota file locks
(`&ocfs2_quota_ip_alloc_sem_key`) *before* starting a transaction
(`ocfs2_start_trans`, which acquires `sb_internal#2`). There is no internal
cycle.
**Security Impact**
This is a classic lockdep false positive. There is no real deadlock that can
occur.
In production kernel builds, lockdep (`CONFIG_PROVE_LOCKING`) is disabled for
performance reasons. Therefore, this false positive warning will not be printed,
and it will not cause any system crash, hang, or denial of service.
Additionally, triggering this requires mounting an `ocfs2` filesystem, which
lacks the `FS_USERNS_MOUNT` flag and thus requires `CAP_SYS_ADMIN` in the
initial user namespace, making it inaccessible to unprivileged users.
Because this is a false positive lockdep warning with no runtime consequences in
production, it has no security impact.]
|