| 0/0 |
2026/05/17 15:53 |
flow |
assessment-security |
44m
Results: map[DenialOfService:true Explanation:Based on the analysis of the bug report and the kernel source code, here is the
assessment of the security impact:
**Exploitable:** False
The bug is a circular locking dependency (ABBA deadlock) between `ip_alloc_sem`,
`j_trans_barrier`, and `ip_xattr_sem` in the `ocfs2` filesystem. Deadlocks lead
to hanging tasks and system unresponsiveness, but they do not cause memory
corruption, out-of-bounds accesses, or control-flow hijacking. Therefore, it is
not exploitable for privilege escalation or arbitrary code execution.
**Denial Of Service:** True
The deadlock will cause the kernel threads attempting to acquire these locks to
hang indefinitely. This can block further operations on the filesystem and
potentially lead to a system-wide hang or panic (if hung task detection is
configured to panic), resulting in a Denial of Service.
**Accessible From Unprivileged Processes / User Namespaces:** False
Triggering this bug requires mounting a crafted `ocfs2` filesystem image. The
`ocfs2` filesystem does not have the `FS_USERNS_MOUNT` flag set in its
`file_system_type` definition. This means it cannot be mounted from within an
unprivileged user namespace. Mounting it requires `CAP_SYS_ADMIN` in the initial
(root) user namespace. Thus, unprivileged users cannot reach this bug directly.
**Malicious Filesystem / Peripheral Trigger:** True
The bug is triggered by interacting with a maliciously crafted `ocfs2`
filesystem image (e.g., via `creat`, `openat`, or `rename` operations). If an
attacker can trick a privileged user into mounting such an image, or if the
system is configured to automatically mount external media (like a USB drive)
upon insertion, the bug can be triggered. Therefore, it qualifies as both a
Malicious Filesystem Trigger and a Peripheral Trigger.
**Network / Remote / VM Triggers:** False
The bug is strictly contained within the local filesystem implementation
(`ocfs2`) and is not exposed to network traffic, remote protocols, or
virtualization-specific hypercalls/paravirtualized devices. Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/17 15:53 |
action |
syz-repro-to-c-repro |
0m
Results: map[SimplifiedCRepro:// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <setjmp.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/loop.h>
#ifndef __NR_memfd_create
#define __NR_memfd_create 319
#endif
static unsigned long long procid;
//% This code is derived from puff.{c,h}, found in the zlib development. The
//% original files come with the following copyright notice:
//% Copyright (C) 2002-2013 Mark Adler, all rights reserved
//% version 2.3, 21 Jan 2013
//% This software is provided 'as-is', without any express or implied
//% warranty. In no event will the author be held liable for any damages
//% arising from the use of this software.
//% Permission is granted to anyone to use this software for any purpose,
//% including commercial applications, and to alter it and redistribute it
//% freely, subject to the following restrictions:
//% 1. The origin of this software must not be misrepresented; you must not
//% claim that you wrote the original software. If you use this software
//% in a product, an acknowledgment in the product documentation would be
//% appreciated but is not required.
//% 2. Altered source versions must be plainly marked as such, and must not be
//% misrepresented as being the original software.
//% 3. This notice may not be removed or altered from any source distribution.
//% Mark Adler madler@alumni.caltech.edu
//% BEGIN CODE DERIVED FROM puff.{c,h}
#define MAXBITS 15
#define MAXLCODES 286
#define MAXDCODES 30
#define MAXCODES (MAXLCODES + MAXDCODES)
#define FIXLCODES 288
struct puff_state {
unsigned char* out;
unsigned long outlen;
unsigned long outcnt;
const unsigned char* in;
unsigned long inlen;
unsigned long incnt;
int bitbuf;
int bitcnt;
jmp_buf env;
};
static int puff_bits(struct puff_state* s, int need)
{
long val = s->bitbuf;
while (s->bitcnt < need) {
if (s->incnt == s->inlen)
longjmp(s->env, 1);
val |= (long)(s->in[s->incnt++]) << s->bitcnt;
s->bitcnt += 8;
}
s->bitbuf = (int)(val >> need);
s->bitcnt -= need;
return (int)(val & ((1L << need) - 1));
}
static int puff_stored(struct puff_state* s)
{
s->bitbuf = 0;
s->bitcnt = 0;
if (s->incnt + 4 > s->inlen)
return 2;
unsigned len = s->in[s->incnt++];
len |= s->in[s->incnt++] << 8;
if (s->in[s->incnt++] != (~len & 0xff) ||
s->in[s->incnt++] != ((~len >> 8) & 0xff))
return -2;
if (s->incnt + len > s->inlen)
return 2;
if (s->outcnt + len > s->outlen)
return 1;
for (; len--; s->outcnt++, s->incnt++) {
if (s->in[s->incnt])
s->out[s->outcnt] = s->in[s->incnt];
}
return 0;
}
struct puff_huffman {
short* count;
short* symbol;
};
static int puff_decode(struct puff_state* s, const struct puff_huffman* h)
{
int first = 0;
int index = 0;
int bitbuf = s->bitbuf;
int left = s->bitcnt;
int code = first = index = 0;
int len = 1;
short* next = h->count + 1;
while (1) {
while (left--) {
code |= bitbuf & 1;
bitbuf >>= 1;
int count = *next++;
if (code - count < first) {
s->bitbuf = bitbuf;
s->bitcnt = (s->bitcnt - len) & 7;
return h->symbol[index + (code - first)];
}
index += count;
first += count;
first <<= 1;
code <<= 1;
len++;
}
left = (MAXBITS + 1) - len;
if (left == 0)
break;
if (s->incnt == s->inlen)
longjmp(s->env, 1);
bitbuf = s->in[s->incnt++];
if (left > 8)
left = 8;
}
return -10;
}
static int puff_construct(struct puff_huffman* h, const short* length, int n)
{
int len;
for (len = 0; len <= MAXBITS; len++)
h->count[len] = 0;
int symbol;
for (symbol = 0; symbol < n; symbol++)
(h->count[length[symbol]])++;
if (h->count[0] == n)
return 0;
int left = 1;
for (len = 1; len <= MAXBITS; len++) {
left <<= 1;
left -= h->count[len];
if (left < 0)
return left;
}
short offs[MAXBITS + 1];
offs[1] = 0;
for (len = 1; len < MAXBITS; len++)
offs[len + 1] = offs[len] + h->count[len];
for (symbol = 0; symbol < n; symbol++)
if (length[symbol] != 0)
h->symbol[offs[length[symbol]]++] = symbol;
return left;
}
static int puff_codes(struct puff_state* s,
const struct puff_huffman* lencode,
const struct puff_huffman* distcode)
{
static const short lens[29] = {
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
static const short lext[29] = {
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
static const short dists[30] = {
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
8193, 12289, 16385, 24577};
static const short dext[30] = {
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
12, 12, 13, 13};
int symbol;
do {
symbol = puff_decode(s, lencode);
if (symbol < 0)
return symbol;
if (symbol < 256) {
if (s->outcnt == s->outlen)
return 1;
if (symbol)
s->out[s->outcnt] = symbol;
s->outcnt++;
} else if (symbol > 256) {
symbol -= 257;
if (symbol >= 29)
return -10;
int len = lens[symbol] + puff_bits(s, lext[symbol]);
symbol = puff_decode(s, distcode);
if (symbol < 0)
return symbol;
unsigned dist = dists[symbol] + puff_bits(s, dext[symbol]);
if (dist > s->outcnt)
return -11;
if (s->outcnt + len > s->outlen)
return 1;
while (len--) {
if (dist <= s->outcnt && s->out[s->outcnt - dist])
s->out[s->outcnt] = s->out[s->outcnt - dist];
s->outcnt++;
}
}
} while (symbol != 256);
return 0;
}
static int puff_fixed(struct puff_state* s)
{
static int virgin = 1;
static short lencnt[MAXBITS + 1], lensym[FIXLCODES];
static short distcnt[MAXBITS + 1], distsym[MAXDCODES];
static struct puff_huffman lencode, distcode;
if (virgin) {
lencode.count = lencnt;
lencode.symbol = lensym;
distcode.count = distcnt;
distcode.symbol = distsym;
short lengths[FIXLCODES];
int symbol;
for (symbol = 0; symbol < 144; symbol++)
lengths[symbol] = 8;
for (; symbol < 256; symbol++)
lengths[symbol] = 9;
for (; symbol < 280; symbol++)
lengths[symbol] = 7;
for (; symbol < FIXLCODES; symbol++)
lengths[symbol] = 8;
puff_construct(&lencode, lengths, FIXLCODES);
for (symbol = 0; symbol < MAXDCODES; symbol++)
lengths[symbol] = 5;
puff_construct(&distcode, lengths, MAXDCODES);
virgin = 0;
}
return puff_codes(s, &lencode, &distcode);
}
static int puff_dynamic(struct puff_state* s)
{
static const short order[19] =
{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
int nlen = puff_bits(s, 5) + 257;
int ndist = puff_bits(s, 5) + 1;
int ncode = puff_bits(s, 4) + 4;
if (nlen > MAXLCODES || ndist > MAXDCODES)
return -3;
short lengths[MAXCODES];
int index;
for (index = 0; index < ncode; index++)
lengths[order[index]] = puff_bits(s, 3);
for (; index < 19; index++)
lengths[order[index]] = 0;
short lencnt[MAXBITS + 1], lensym[MAXLCODES];
struct puff_huffman lencode = {lencnt, lensym};
int err = puff_construct(&lencode, lengths, 19);
if (err != 0)
return -4;
index = 0;
while (index < nlen + ndist) {
int symbol;
int len;
symbol = puff_decode(s, &lencode);
if (symbol < 0)
return symbol;
if (symbol < 16)
lengths[index++] = symbol;
else {
len = 0;
if (symbol == 16) {
if (index == 0)
return -5;
len = lengths[index - 1];
symbol = 3 + puff_bits(s, 2);
} else if (symbol == 17)
symbol = 3 + puff_bits(s, 3);
else
symbol = 11 + puff_bits(s, 7);
if (index + symbol > nlen + ndist)
return -6;
while (symbol--)
lengths[index++] = len;
}
}
if (lengths[256] == 0)
return -9;
err = puff_construct(&lencode, lengths, nlen);
if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1]))
return -7;
short distcnt[MAXBITS + 1], distsym[MAXDCODES];
struct puff_huffman distcode = {distcnt, distsym};
err = puff_construct(&distcode, lengths + nlen, ndist);
if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1]))
return -8;
return puff_codes(s, &lencode, &distcode);
}
static int puff(
unsigned char* dest,
unsigned long* destlen,
const unsigned char* source,
unsigned long sourcelen)
{
struct puff_state s = {
.out = dest,
.outlen = *destlen,
.outcnt = 0,
.in = source,
.inlen = sourcelen,
.incnt = 0,
.bitbuf = 0,
.bitcnt = 0,
};
int err;
if (setjmp(s.env) != 0)
err = 2;
else {
int last;
do {
last = puff_bits(&s, 1);
int type = puff_bits(&s, 2);
err = type == 0 ? puff_stored(&s) : (type == 1 ? puff_fixed(&s) : (type == 2 ? puff_dynamic(&s) : -1));
if (err != 0)
break;
} while (!last);
}
*destlen = s.outcnt;
return err;
}
//% END CODE DERIVED FROM puff.{c,h}
#define ZLIB_HEADER_WIDTH 2
static int puff_zlib_to_file(const unsigned char* source, unsigned long sourcelen, int dest_fd)
{
if (sourcelen < ZLIB_HEADER_WIDTH)
return 0;
source += ZLIB_HEADER_WIDTH;
sourcelen -= ZLIB_HEADER_WIDTH;
const unsigned long max_destlen = 132 << 20;
void* ret = mmap(0, max_destlen, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANON, -1, 0);
if (ret == MAP_FAILED)
return -1;
unsigned char* dest = (unsigned char*)ret;
unsigned long destlen = max_destlen;
int err = puff(dest, &destlen, source, sourcelen);
if (err) {
munmap(dest, max_destlen);
errno = -err;
return -1;
}
if (write(dest_fd, dest, destlen) != (ssize_t)destlen) {
munmap(dest, max_destlen);
return -1;
}
return munmap(dest, max_destlen);
}
static int setup_loop_device(unsigned char* data, unsigned long size, const char* loopname, int* loopfd_p)
{
int err = 0, loopfd = -1;
int memfd = syscall(__NR_memfd_create, "syzkaller", 0);
if (memfd == -1) {
err = errno;
goto error;
}
if (puff_zlib_to_file(data, size, memfd)) {
err = errno;
goto error_close_memfd;
}
loopfd = open(loopname, O_RDWR);
if (loopfd == -1) {
err = errno;
goto error_close_memfd;
}
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
if (errno != EBUSY) {
err = errno;
goto error_close_loop;
}
ioctl(loopfd, LOOP_CLR_FD, 0);
usleep(1000);
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
err = errno;
goto error_close_loop;
}
}
close(memfd);
*loopfd_p = loopfd;
return 0;
error_close_loop:
close(loopfd);
error_close_memfd:
close(memfd);
error:
errno = err;
return -1;
}
static void reset_loop_device(const char* loopname)
{
int loopfd = open(loopname, O_RDWR);
if (loopfd == -1) {
return;
}
if (ioctl(loopfd, LOOP_CLR_FD, 0)) {
}
close(loopfd);
}
static long syz_mount_image(
volatile long fsarg,
volatile long dir,
volatile long flags,
volatile long optsarg,
volatile long change_dir,
volatile unsigned long size,
volatile long image)
{
unsigned char* data = (unsigned char*)image;
int res = -1, err = 0, need_loop_device = !!size;
char* mount_opts = (char*)optsarg;
char* target = (char*)dir;
char* fs = (char*)fsarg;
char* source = NULL;
char loopname[64];
if (need_loop_device) {
int loopfd;
memset(loopname, 0, sizeof(loopname));
snprintf(loopname, sizeof(loopname), "/dev/loop%llu", procid);
if (setup_loop_device(data, size, loopname, &loopfd) == -1)
return -1;
close(loopfd);
source = loopname;
}
mkdir(target, 0777);
char opts[256];
memset(opts, 0, sizeof(opts));
if (strlen(mount_opts) > (sizeof(opts) - 32)) {
}
strncpy(opts, mount_opts, sizeof(opts) - 32);
if (strcmp(fs, "iso9660") == 0) {
flags |= MS_RDONLY;
} else if (strncmp(fs, "ext", 3) == 0) {
bool has_remount_ro = false;
char* remount_ro_start = strstr(opts, "errors=remount-ro");
if (remount_ro_start != NULL) {
char after = *(remount_ro_start + strlen("errors=remount-ro"));
char before = remount_ro_start == opts ? '\0' : *(remount_ro_start - 1);
has_remount_ro = ((before == '\0' || before == ',') && (after == '\0' || after == ','));
}
if (strstr(opts, "errors=panic") || !has_remount_ro)
strcat(opts, ",errors=continue");
} else if (strcmp(fs, "xfs") == 0) {
strcat(opts, ",nouuid");
} else if (strncmp(fs, "gfs2", 4) == 0 && (strstr(opts, "errors=panic") || strstr(opts, "debug"))) {
strcat(opts, ",errors=withdraw");
}
res = mount(source, target, fs, flags, opts);
if (res == -1) {
err = errno;
goto error_clear_loop;
}
res = open(target, O_RDONLY | O_DIRECTORY);
if (res == -1) {
err = errno;
goto error_clear_loop;
}
if (change_dir) {
res = chdir(target);
if (res == -1) {
err = errno;
}
}
error_clear_loop:
if (need_loop_device)
reset_loop_device(loopname);
errno = err;
return res;
}
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// syz_mount_image$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 = 0x8c0 (8 bytes)
// opts: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {61 63 6c 2c 68 65 61 72 74 62 65 61 74 3d 6e 6f 6e 65 2c 64 69 72 5f 72 65 73 76 5f 6c 65 76 65 6c 3d 30 30 30 30 33 2c 63 6f 68 65 72 65 6e 63 79 3d 66 75 6c 6c 2c 63 6f 68 65 72 65 6e 63 79 3d 66 75 6c 6c 2c 6c 6f 63 61 6c 66 6c 6f 63 6b 73 2c 63 6f 68 65 72 65 6e 63 79 3d 66 75 6c 6c 2c 6e 6f 61 63 6c 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00} (length 0x77)
// }
// }
// }
// chdir: int8 = 0x5 (1 bytes)
// size: len = 0x4451 (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x4451)
// }
// ]
// returns fd_dir
memcpy((void*)0x200000004440, "ocfs2\000", 6);
memcpy((void*)0x200000000040, "./file0\000", 8);
memcpy((void*)0x200000000080, "... [truncated large byte array] ...", 119);
memcpy((void*)0x200000008900, "... [truncated large byte array] ...", 17489);
syz_mount_image(/*fs=*/0x200000004440, /*dir=*/0x200000000040, /*flags=MS_NODIRATIME|MS_MANDLOCK|MS_DIRSYNC*/0x8c0, /*opts=*/0x200000000080, /*chdir=*/5, /*size=*/0x4451, /*img=*/0x200000008900);
// openat arguments: [
// fd: fd_dir (resource)
// file: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// flags: open_flags = 0x2040 (4 bytes)
// mode: open_mode = 0xd (2 bytes)
// ]
// returns fd
memcpy((void*)0x200000000000, "./file1\000", 8);
syscall(__NR_openat, /*fd=*/0xffffff9c, /*file=*/0x200000000000ul, /*flags=O_CREAT|FASYNC*/0x2040, /*mode=S_IXOTH|S_IROTH|S_IXGRP*/0xd);
// creat arguments: [
// file: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 32 00} (length 0x8)
// }
// mode: open_mode = 0x60 (8 bytes)
// ]
// returns fd
memcpy((void*)0x2000000001c0, "./file2\000", 8);
syscall(__NR_creat, /*file=*/0x2000000001c0ul, /*mode=S_IRGRP|S_IXUSR*/0x60ul);
// rename arguments: [
// old: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// new: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 32 00} (length 0x8)
// }
// ]
memcpy((void*)0x200000000000, "./file1\000", 8);
memcpy((void*)0x200000000080, "./file2\000", 8);
syscall(__NR_rename, /*old=*/0x200000000000ul, /*new=*/0x200000000080ul);
// ioctl$BTRFS_IOC_SNAP_CREATE_V2 arguments: [
// fd: fd (resource)
// cmd: const = 0x50009417 (4 bytes)
// arg: nil
// ]
syscall(__NR_ioctl, /*fd=*/(intptr_t)-1, /*cmd=*/0x50009417, /*arg=*/0ul);
return 0;
}
]
|
| 2/1 |
2026/05/17 15:53 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/d6947ec43f9e25088f868245c046ad31c67ad5fc]
|
| 3/1 |
2026/05/17 15:54 |
action |
kernel-builder |
24m
Results: map[KernelObj:/app/workdir/cache/build/2a93203aa17148da5a8af1bd1df49d1e21be05a4]
|
| 4/1 |
2026/05/17 16:19 |
action |
codesearch-prepare |
17m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/17 16:36 |
agent |
expert |
0m
Model:
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Instruction:
You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by greeping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Don't make assumptions; verify them with source code access. Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption or elevated privileges.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completly wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control tranfers, that's also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitabability depends on how exactly the uninit value
is used in the code, and what it affects.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidetial Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
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:
======================================================
WARNING: possible circular locking dependency detected
syzkaller #0 Not tainted
------------------------------------------------------
syz.3.33/6211 is trying to acquire lock:
ffff88803e171890 (&oi->ip_alloc_sem){+.+.}-{4:4}, at: ocfs2_try_remove_refcount_tree+0xb6/0x340 fs/ocfs2/refcounttree.c:932
but task is already holding lock:
ffff88803e171918 (&oi->ip_xattr_sem){++++}-{4:4}, at: ocfs2_try_remove_refcount_tree+0xa4/0x340 fs/ocfs2/refcounttree.c:931
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #3 (&oi->ip_xattr_sem){++++}-{4:4}:
down_read+0x97/0x200 kernel/locking/rwsem.c:1568
ocfs2_xattr_get+0xfa/0x270 fs/ocfs2/xattr.c:1368
__vfs_getxattr+0x3f4/0x430 fs/xattr.c:441
smk_fetch+0xb4/0x140 security/smack/smack_lsm.c:289
smack_d_instantiate+0x757/0x990 security/smack/smack_lsm.c:3653
security_d_instantiate+0x106/0x1d0 security/security.c:3704
d_instantiate+0x57/0xc0 fs/dcache.c:2009
ocfs2_mknod+0x1c98/0x2260 fs/ocfs2/namei.c:454
ocfs2_create+0x195/0x460 fs/ocfs2/namei.c:677
lookup_open fs/namei.c:4511 [inline]
open_last_lookups fs/namei.c:4611 [inline]
path_openat+0x13b4/0x38a0 fs/namei.c:4855
do_file_open+0x23e/0x4a0 fs/namei.c:4887
do_sys_openat2+0x113/0x200 fs/open.c:1364
do_sys_open fs/open.c:1370 [inline]
__do_sys_openat fs/open.c:1386 [inline]
__se_sys_openat fs/open.c:1381 [inline]
__x64_sys_openat+0x138/0x170 fs/open.c:1381
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
-> #2 (&journal->j_trans_barrier){.+.+}-{4:4}:
down_read+0x97/0x200 kernel/locking/rwsem.c:1568
ocfs2_start_trans+0x3ac/0x700 fs/ocfs2/journal.c:369
ocfs2_mknod+0x1181/0x2260 fs/ocfs2/namei.c:365
ocfs2_create+0x195/0x460 fs/ocfs2/namei.c:677
lookup_open fs/namei.c:4511 [inline]
open_last_lookups fs/namei.c:4611 [inline]
path_openat+0x13b4/0x38a0 fs/namei.c:4855
do_file_open+0x23e/0x4a0 fs/namei.c:4887
do_sys_openat2+0x113/0x200 fs/open.c:1364
do_sys_open fs/open.c:1370 [inline]
__do_sys_openat fs/open.c:1386 [inline]
__se_sys_openat fs/open.c:1381 [inline]
__x64_sys_openat+0x138/0x170 fs/open.c:1381
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
-> #1 (sb_internal#2){.+.+}-{0:0}:
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/super.h:19 [inline]
sb_start_intwrite include/linux/fs/super.h:177 [inline]
ocfs2_start_trans+0x2ac/0x700 fs/ocfs2/journal.c:367
ocfs2_orphan_for_truncate fs/ocfs2/file.c:397 [inline]
ocfs2_truncate_file+0x631/0x14e0 fs/ocfs2/file.c:501
ocfs2_setattr+0x15e5/0x1ca0 fs/ocfs2/file.c:1219
notify_change+0xc18/0xf60 fs/attr.c:556
do_truncate+0x1c2/0x250 fs/open.c:68
handle_truncate fs/namei.c:4307 [inline]
do_open fs/namei.c:4703 [inline]
path_openat+0x2fc4/0x38a0 fs/namei.c:4858
do_file_open+0x23e/0x4a0 fs/namei.c:4887
do_sys_openat2+0x113/0x200 fs/open.c:1364
do_sys_open fs/open.c:1370 [inline]
__do_sys_creat fs/open.c:1448 [inline]
__se_sys_creat fs/open.c:1442 [inline]
__x64_sys_creat+0x8f/0xc0 fs/open.c:1442
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
-> #0 (&oi->ip_alloc_sem){+.+.}-{4:4}:
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+0x15a5/0x2cf0 kernel/locking/lockdep.c:5237
lock_acquire+0x106/0x350 kernel/locking/lockdep.c:5868
down_write+0x3a/0x50 kernel/locking/rwsem.c:1625
ocfs2_try_remove_refcount_tree+0xb6/0x340 fs/ocfs2/refcounttree.c:932
ocfs2_truncate_file+0xdf3/0x14e0 fs/ocfs2/file.c:522
ocfs2_setattr+0x15e5/0x1ca0 fs/ocfs2/file.c:1219
notify_change+0xc18/0xf60 fs/attr.c:556
do_truncate+0x1c2/0x250 fs/open.c:68
handle_truncate fs/namei.c:4307 [inline]
do_open fs/namei.c:4703 [inline]
path_openat+0x2fc4/0x38a0 fs/namei.c:4858
do_file_open+0x23e/0x4a0 fs/namei.c:4887
do_sys_openat2+0x113/0x200 fs/open.c:1364
do_sys_open fs/open.c:1370 [inline]
__do_sys_creat fs/open.c:1448 [inline]
__se_sys_creat fs/open.c:1442 [inline]
__x64_sys_creat+0x8f/0xc0 fs/open.c:1442
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
other info that might help us debug this:
Chain exists of:
&oi->ip_alloc_sem --> &journal->j_trans_barrier --> &oi->ip_xattr_sem
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&oi->ip_xattr_sem);
lock(&journal->j_trans_barrier);
lock(&oi->ip_xattr_sem);
lock(&oi->ip_alloc_sem);
*** DEADLOCK ***
3 locks held by syz.3.33/6211:
#0: ffff88804090c480 (sb_writers#12){.+.+}-{0:0}, at: mnt_want_write+0x41/0x90 fs/namespace.c:493
#1: ffff88803e171c40 (&sb->s_type->i_mutex_key#24){+.+.}-{4:4}, at: inode_lock_killable include/linux/fs.h:1034 [inline]
#1: ffff88803e171c40 (&sb->s_type->i_mutex_key#24){+.+.}-{4:4}, at: do_truncate+0x18f/0x250 fs/open.c:63
#2: ffff88803e171918 (&oi->ip_xattr_sem){++++}-{4:4}, at: ocfs2_try_remove_refcount_tree+0xa4/0x340 fs/ocfs2/refcounttree.c:931
stack backtrace:
CPU: 1 UID: 0 PID: 6211 Comm: syz.3.33 Not tainted syzkaller #0 PREEMPT_{RT,(full)}
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 03/18/2026
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_circular_bug+0x2e1/0x300 kernel/locking/lockdep.c:2043
check_noncircular+0x12e/0x150 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+0x15a5/0x2cf0 kernel/locking/lockdep.c:5237
lock_acquire+0x106/0x350 kernel/locking/lockdep.c:5868
down_write+0x3a/0x50 kernel/locking/rwsem.c:1625
ocfs2_try_remove_refcount_tree+0xb6/0x340 fs/ocfs2/refcounttree.c:932
ocfs2_truncate_file+0xdf3/0x14e0 fs/ocfs2/file.c:522
ocfs2_setattr+0x15e5/0x1ca0 fs/ocfs2/file.c:1219
notify_change+0xc18/0xf60 fs/attr.c:556
do_truncate+0x1c2/0x250 fs/open.c:68
handle_truncate fs/namei.c:4307 [inline]
do_open fs/namei.c:4703 [inline]
path_openat+0x2fc4/0x38a0 fs/namei.c:4858
do_file_open+0x23e/0x4a0 fs/namei.c:4887
do_sys_openat2+0x113/0x200 fs/open.c:1364
do_sys_open fs/open.c:1370 [inline]
__do_sys_creat fs/open.c:1448 [inline]
__se_sys_creat fs/open.c:1442 [inline]
__x64_sys_creat+0x8f/0xc0 fs/open.c:1442
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f0df500c819
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f0df4645028 EFLAGS: 00000246 ORIG_RAX: 0000000000000055
RAX: ffffffffffffffda RBX: 00007f0df5286090 RCX: 00007f0df500c819
RDX: 0000000000000000 RSI: 0000000000000060 RDI: 00002000000001c0
RBP: 00007f0df50a2c91 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f0df5286128 R14: 00007f0df5286090 R15: 00007ffd2ee242c8
</TASK>
It is reproducible with the followint program.
Keep in mind that it may lack the precise threading, sandboxing, and some arguments of a working reproducer.
But it should give an idea of the involved syscalls.
// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <setjmp.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/loop.h>
#ifndef __NR_memfd_create
#define __NR_memfd_create 319
#endif
static unsigned long long procid;
//% This code is derived from puff.{c,h}, found in the zlib development. The
//% original files come with the following copyright notice:
//% Copyright (C) 2002-2013 Mark Adler, all rights reserved
//% version 2.3, 21 Jan 2013
//% This software is provided 'as-is', without any express or implied
//% warranty. In no event will the author be held liable for any damages
//% arising from the use of this software.
//% Permission is granted to anyone to use this software for any purpose,
//% including commercial applications, and to alter it and redistribute it
//% freely, subject to the following restrictions:
//% 1. The origin of this software must not be misrepresented; you must not
//% claim that you wrote the original software. If you use this software
//% in a product, an acknowledgment in the product documentation would be
//% appreciated but is not required.
//% 2. Altered source versions must be plainly marked as such, and must not be
//% misrepresented as being the original software.
//% 3. This notice may not be removed or altered from any source distribution.
//% Mark Adler madler@alumni.caltech.edu
//% BEGIN CODE DERIVED FROM puff.{c,h}
#define MAXBITS 15
#define MAXLCODES 286
#define MAXDCODES 30
#define MAXCODES (MAXLCODES + MAXDCODES)
#define FIXLCODES 288
struct puff_state {
unsigned char* out;
unsigned long outlen;
unsigned long outcnt;
const unsigned char* in;
unsigned long inlen;
unsigned long incnt;
int bitbuf;
int bitcnt;
jmp_buf env;
};
static int puff_bits(struct puff_state* s, int need)
{
long val = s->bitbuf;
while (s->bitcnt < need) {
if (s->incnt == s->inlen)
longjmp(s->env, 1);
val |= (long)(s->in[s->incnt++]) << s->bitcnt;
s->bitcnt += 8;
}
s->bitbuf = (int)(val >> need);
s->bitcnt -= need;
return (int)(val & ((1L << need) - 1));
}
static int puff_stored(struct puff_state* s)
{
s->bitbuf = 0;
s->bitcnt = 0;
if (s->incnt + 4 > s->inlen)
return 2;
unsigned len = s->in[s->incnt++];
len |= s->in[s->incnt++] << 8;
if (s->in[s->incnt++] != (~len & 0xff) ||
s->in[s->incnt++] != ((~len >> 8) & 0xff))
return -2;
if (s->incnt + len > s->inlen)
return 2;
if (s->outcnt + len > s->outlen)
return 1;
for (; len--; s->outcnt++, s->incnt++) {
if (s->in[s->incnt])
s->out[s->outcnt] = s->in[s->incnt];
}
return 0;
}
struct puff_huffman {
short* count;
short* symbol;
};
static int puff_decode(struct puff_state* s, const struct puff_huffman* h)
{
int first = 0;
int index = 0;
int bitbuf = s->bitbuf;
int left = s->bitcnt;
int code = first = index = 0;
int len = 1;
short* next = h->count + 1;
while (1) {
while (left--) {
code |= bitbuf & 1;
bitbuf >>= 1;
int count = *next++;
if (code - count < first) {
s->bitbuf = bitbuf;
s->bitcnt = (s->bitcnt - len) & 7;
return h->symbol[index + (code - first)];
}
index += count;
first += count;
first <<= 1;
code <<= 1;
len++;
}
left = (MAXBITS + 1) - len;
if (left == 0)
break;
if (s->incnt == s->inlen)
longjmp(s->env, 1);
bitbuf = s->in[s->incnt++];
if (left > 8)
left = 8;
}
return -10;
}
static int puff_construct(struct puff_huffman* h, const short* length, int n)
{
int len;
for (len = 0; len <= MAXBITS; len++)
h->count[len] = 0;
int symbol;
for (symbol = 0; symbol < n; symbol++)
(h->count[length[symbol]])++;
if (h->count[0] == n)
return 0;
int left = 1;
for (len = 1; len <= MAXBITS; len++) {
left <<= 1;
left -= h->count[len];
if (left < 0)
return left;
}
short offs[MAXBITS + 1];
offs[1] = 0;
for (len = 1; len < MAXBITS; len++)
offs[len + 1] = offs[len] + h->count[len];
for (symbol = 0; symbol < n; symbol++)
if (length[symbol] != 0)
h->symbol[offs[length[symbol]]++] = symbol;
return left;
}
static int puff_codes(struct puff_state* s,
const struct puff_huffman* lencode,
const struct puff_huffman* distcode)
{
static const short lens[29] = {
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
static const short lext[29] = {
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
static const short dists[30] = {
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
8193, 12289, 16385, 24577};
static const short dext[30] = {
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
12, 12, 13, 13};
int symbol;
do {
symbol = puff_decode(s, lencode);
if (symbol < 0)
return symbol;
if (symbol < 256) {
if (s->outcnt == s->outlen)
return 1;
if (symbol)
s->out[s->outcnt] = symbol;
s->outcnt++;
} else if (symbol > 256) {
symbol -= 257;
if (symbol >= 29)
return -10;
int len = lens[symbol] + puff_bits(s, lext[symbol]);
symbol = puff_decode(s, distcode);
if (symbol < 0)
return symbol;
unsigned dist = dists[symbol] + puff_bits(s, dext[symbol]);
if (dist > s->outcnt)
return -11;
if (s->outcnt + len > s->outlen)
return 1;
while (len--) {
if (dist <= s->outcnt && s->out[s->outcnt - dist])
s->out[s->outcnt] = s->out[s->outcnt - dist];
s->outcnt++;
}
}
} while (symbol != 256);
return 0;
}
static int puff_fixed(struct puff_state* s)
{
static int virgin = 1;
static short lencnt[MAXBITS + 1], lensym[FIXLCODES];
static short distcnt[MAXBITS + 1], distsym[MAXDCODES];
static struct puff_huffman lencode, distcode;
if (virgin) {
lencode.count = lencnt;
lencode.symbol = lensym;
distcode.count = distcnt;
distcode.symbol = distsym;
short lengths[FIXLCODES];
int symbol;
for (symbol = 0; symbol < 144; symbol++)
lengths[symbol] = 8;
for (; symbol < 256; symbol++)
lengths[symbol] = 9;
for (; symbol < 280; symbol++)
lengths[symbol] = 7;
for (; symbol < FIXLCODES; symbol++)
lengths[symbol] = 8;
puff_construct(&lencode, lengths, FIXLCODES);
for (symbol = 0; symbol < MAXDCODES; symbol++)
lengths[symbol] = 5;
puff_construct(&distcode, lengths, MAXDCODES);
virgin = 0;
}
return puff_codes(s, &lencode, &distcode);
}
static int puff_dynamic(struct puff_state* s)
{
static const short order[19] =
{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
int nlen = puff_bits(s, 5) + 257;
int ndist = puff_bits(s, 5) + 1;
int ncode = puff_bits(s, 4) + 4;
if (nlen > MAXLCODES || ndist > MAXDCODES)
return -3;
short lengths[MAXCODES];
int index;
for (index = 0; index < ncode; index++)
lengths[order[index]] = puff_bits(s, 3);
for (; index < 19; index++)
lengths[order[index]] = 0;
short lencnt[MAXBITS + 1], lensym[MAXLCODES];
struct puff_huffman lencode = {lencnt, lensym};
int err = puff_construct(&lencode, lengths, 19);
if (err != 0)
return -4;
index = 0;
while (index < nlen + ndist) {
int symbol;
int len;
symbol = puff_decode(s, &lencode);
if (symbol < 0)
return symbol;
if (symbol < 16)
lengths[index++] = symbol;
else {
len = 0;
if (symbol == 16) {
if (index == 0)
return -5;
len = lengths[index - 1];
symbol = 3 + puff_bits(s, 2);
} else if (symbol == 17)
symbol = 3 + puff_bits(s, 3);
else
symbol = 11 + puff_bits(s, 7);
if (index + symbol > nlen + ndist)
return -6;
while (symbol--)
lengths[index++] = len;
}
}
if (lengths[256] == 0)
return -9;
err = puff_construct(&lencode, lengths, nlen);
if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1]))
return -7;
short distcnt[MAXBITS + 1], distsym[MAXDCODES];
struct puff_huffman distcode = {distcnt, distsym};
err = puff_construct(&distcode, lengths + nlen, ndist);
if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1]))
return -8;
return puff_codes(s, &lencode, &distcode);
}
static int puff(
unsigned char* dest,
unsigned long* destlen,
const unsigned char* source,
unsigned long sourcelen)
{
struct puff_state s = {
.out = dest,
.outlen = *destlen,
.outcnt = 0,
.in = source,
.inlen = sourcelen,
.incnt = 0,
.bitbuf = 0,
.bitcnt = 0,
};
int err;
if (setjmp(s.env) != 0)
err = 2;
else {
int last;
do {
last = puff_bits(&s, 1);
int type = puff_bits(&s, 2);
err = type == 0 ? puff_stored(&s) : (type == 1 ? puff_fixed(&s) : (type == 2 ? puff_dynamic(&s) : -1));
if (err != 0)
break;
} while (!last);
}
*destlen = s.outcnt;
return err;
}
//% END CODE DERIVED FROM puff.{c,h}
#define ZLIB_HEADER_WIDTH 2
static int puff_zlib_to_file(const unsigned char* source, unsigned long sourcelen, int dest_fd)
{
if (sourcelen < ZLIB_HEADER_WIDTH)
return 0;
source += ZLIB_HEADER_WIDTH;
sourcelen -= ZLIB_HEADER_WIDTH;
const unsigned long max_destlen = 132 << 20;
void* ret = mmap(0, max_destlen, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANON, -1, 0);
if (ret == MAP_FAILED)
return -1;
unsigned char* dest = (unsigned char*)ret;
unsigned long destlen = max_destlen;
int err = puff(dest, &destlen, source, sourcelen);
if (err) {
munmap(dest, max_destlen);
errno = -err;
return -1;
}
if (write(dest_fd, dest, destlen) != (ssize_t)destlen) {
munmap(dest, max_destlen);
return -1;
}
return munmap(dest, max_destlen);
}
static int setup_loop_device(unsigned char* data, unsigned long size, const char* loopname, int* loopfd_p)
{
int err = 0, loopfd = -1;
int memfd = syscall(__NR_memfd_create, "syzkaller", 0);
if (memfd == -1) {
err = errno;
goto error;
}
if (puff_zlib_to_file(data, size, memfd)) {
err = errno;
goto error_close_memfd;
}
loopfd = open(loopname, O_RDWR);
if (loopfd == -1) {
err = errno;
goto error_close_memfd;
}
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
if (errno != EBUSY) {
err = errno;
goto error_close_loop;
}
ioctl(loopfd, LOOP_CLR_FD, 0);
usleep(1000);
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
err = errno;
goto error_close_loop;
}
}
close(memfd);
*loopfd_p = loopfd;
return 0;
error_close_loop:
close(loopfd);
error_close_memfd:
close(memfd);
error:
errno = err;
return -1;
}
static void reset_loop_device(const char* loopname)
{
int loopfd = open(loopname, O_RDWR);
if (loopfd == -1) {
return;
}
if (ioctl(loopfd, LOOP_CLR_FD, 0)) {
}
close(loopfd);
}
static long syz_mount_image(
volatile long fsarg,
volatile long dir,
volatile long flags,
volatile long optsarg,
volatile long change_dir,
volatile unsigned long size,
volatile long image)
{
unsigned char* data = (unsigned char*)image;
int res = -1, err = 0, need_loop_device = !!size;
char* mount_opts = (char*)optsarg;
char* target = (char*)dir;
char* fs = (char*)fsarg;
char* source = NULL;
char loopname[64];
if (need_loop_device) {
int loopfd;
memset(loopname, 0, sizeof(loopname));
snprintf(loopname, sizeof(loopname), "/dev/loop%llu", procid);
if (setup_loop_device(data, size, loopname, &loopfd) == -1)
return -1;
close(loopfd);
source = loopname;
}
mkdir(target, 0777);
char opts[256];
memset(opts, 0, sizeof(opts));
if (strlen(mount_opts) > (sizeof(opts) - 32)) {
}
strncpy(opts, mount_opts, sizeof(opts) - 32);
if (strcmp(fs, "iso9660") == 0) {
flags |= MS_RDONLY;
} else if (strncmp(fs, "ext", 3) == 0) {
bool has_remount_ro = false;
char* remount_ro_start = strstr(opts, "errors=remount-ro");
if (remount_ro_start != NULL) {
char after = *(remount_ro_start + strlen("errors=remount-ro"));
char before = remount_ro_start == opts ? '\0' : *(remount_ro_start - 1);
has_remount_ro = ((before == '\0' || before == ',') && (after == '\0' || after == ','));
}
if (strstr(opts, "errors=panic") || !has_remount_ro)
strcat(opts, ",errors=continue");
} else if (strcmp(fs, "xfs") == 0) {
strcat(opts, ",nouuid");
} else if (strncmp(fs, "gfs2", 4) == 0 && (strstr(opts, "errors=panic") || strstr(opts, "debug"))) {
strcat(opts, ",errors=withdraw");
}
res = mount(source, target, fs, flags, opts);
if (res == -1) {
err = errno;
goto error_clear_loop;
}
res = open(target, O_RDONLY | O_DIRECTORY);
if (res == -1) {
err = errno;
goto error_clear_loop;
}
if (change_dir) {
res = chdir(target);
if (res == -1) {
err = errno;
}
}
error_clear_loop:
if (need_loop_device)
reset_loop_device(loopname);
errno = err;
return res;
}
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// syz_mount_image$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 = 0x8c0 (8 bytes)
// opts: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {61 63 6c 2c 68 65 61 72 74 62 65 61 74 3d 6e 6f 6e 65 2c 64 69 72 5f 72 65 73 76 5f 6c 65 76 65 6c 3d 30 30 30 30 33 2c 63 6f 68 65 72 65 6e 63 79 3d 66 75 6c 6c 2c 63 6f 68 65 72 65 6e 63 79 3d 66 75 6c 6c 2c 6c 6f 63 61 6c 66 6c 6f 63 6b 73 2c 63 6f 68 65 72 65 6e 63 79 3d 66 75 6c 6c 2c 6e 6f 61 63 6c 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00} (length 0x77)
// }
// }
// }
// chdir: int8 = 0x5 (1 bytes)
// size: len = 0x4451 (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x4451)
// }
// ]
// returns fd_dir
memcpy((void*)0x200000004440, "ocfs2\000", 6);
memcpy((void*)0x200000000040, "./file0\000", 8);
memcpy((void*)0x200000000080, "... [truncated large byte array] ...", 119);
memcpy((void*)0x200000008900, "... [truncated large byte array] ...", 17489);
syz_mount_image(/*fs=*/0x200000004440, /*dir=*/0x200000000040, /*flags=MS_NODIRATIME|MS_MANDLOCK|MS_DIRSYNC*/0x8c0, /*opts=*/0x200000000080, /*chdir=*/5, /*size=*/0x4451, /*img=*/0x200000008900);
// openat arguments: [
// fd: fd_dir (resource)
// file: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// flags: open_flags = 0x2040 (4 bytes)
// mode: open_mode = 0xd (2 bytes)
// ]
// returns fd
memcpy((void*)0x200000000000, "./file1\000", 8);
syscall(__NR_openat, /*fd=*/0xffffff9c, /*file=*/0x200000000000ul, /*flags=O_CREAT|FASYNC*/0x2040, /*mode=S_IXOTH|S_IROTH|S_IXGRP*/0xd);
// creat arguments: [
// file: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 32 00} (length 0x8)
// }
// mode: open_mode = 0x60 (8 bytes)
// ]
// returns fd
memcpy((void*)0x2000000001c0, "./file2\000", 8);
syscall(__NR_creat, /*file=*/0x2000000001c0ul, /*mode=S_IRGRP|S_IXUSR*/0x60ul);
// rename arguments: [
// old: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// new: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 32 00} (length 0x8)
// }
// ]
memcpy((void*)0x200000000000, "./file1\000", 8);
memcpy((void*)0x200000000080, "./file2\000", 8);
syscall(__NR_rename, /*old=*/0x200000000000ul, /*new=*/0x200000000080ul);
// ioctl$BTRFS_IOC_SNAP_CREATE_V2 arguments: [
// fd: fd (resource)
// cmd: const = 0x50009417 (4 bytes)
// arg: nil
// ]
syscall(__NR_ioctl, /*fd=*/(intptr_t)-1, /*cmd=*/0x50009417, /*arg=*/0ul);
return 0;
}
Reply: Based on the analysis of the bug report and the kernel source code, here is the assessment of the security impact:
**Exploitable:** False
The bug is a circular locking dependency (ABBA deadlock) between `ip_alloc_sem`, `j_trans_barrier`, and `ip_xattr_sem` in the `ocfs2` filesystem. Deadlocks lead to hanging tasks and system unresponsiveness, but they do not cause memory corruption, out-of-bounds accesses, or control-flow hijacking. Therefore, it is not exploitable for privilege escalation or arbitrary code execution.
**Denial Of Service:** True
The deadlock will cause the kernel threads attempting to acquire these locks to hang indefinitely. This can block further operations on the filesystem and potentially lead to a system-wide hang or panic (if hung task detection is configured to panic), resulting in a Denial of Service.
**Accessible From Unprivileged Processes / User Namespaces:** False
Triggering this bug requires mounting a crafted `ocfs2` filesystem image. The `ocfs2` filesystem does not have the `FS_USERNS_MOUNT` flag set in its `file_system_type` definition. This means it cannot be mounted from within an unprivileged user namespace. Mounting it requires `CAP_SYS_ADMIN` in the initial (root) user namespace. Thus, unprivileged users cannot reach this bug directly.
**Malicious Filesystem / Peripheral Trigger:** True
The bug is triggered by interacting with a maliciously crafted `ocfs2` filesystem image (e.g., via `creat`, `openat`, or `rename` operations). If an attacker can trick a privileged user into mounting such an image, or if the system is configured to automatically mount external media (like a USB drive) upon insertion, the bug can be triggered. Therefore, it qualifies as both a Malicious Filesystem Trigger and a Peripheral Trigger.
**Network / Remote / VM Triggers:** False
The bug is strictly contained within the local filesystem implementation (`ocfs2`) and is not exposed to network traffic, remote protocols, or virtualization-specific hypercalls/paravirtualized devices.
|
| 6/2 |
2026/05/17 16:36 |
llm |
expert |
0m
Model:
Tokens:
input: 16142
output: 21
thoughts: 286
Thoughts: **Analyzing a Potential OCFS2 Circular Lock Dependency**
Okay, here's what I'm seeing: a syzkaller report flagged a circular locking dependency within the OCFS2 filesystem. The lockdep trace points to a chain involving `&oi->ip_alloc_sem`, `&journal->j_trans_barrier`, and `&oi->ip_xattr_sem`. The lock order is problematic: first, there's `lock(&oi->ip_xattr_sem)`, then `lock(&oi->ip_alloc_sem)`. On another CPU we find the other part of the circle, where `lock(&journal->j_trans_barrier)` is followed by a *second* attempt to acquire `lock(&oi->ip_xattr_sem)`. This is clearly a deadlock scenario.
The reproducer itself is concerning. It involves mounting a crafted or malicious OCFS2 image via `syz_mount_image`, followed by a few file operations. The fact that mounting the image triggers this is a key piece of information. This means that a corrupted or cleverly designed filesystem image can exploit this vulnerability.
Now, let's consider the exploitability. The immediate question is: what privileges are required to mount this image? I know mounting generally needs `CAP_SYS_ADMIN`. And a quick check is in order, I'll need to confirm if OCFS2 is mountable in a user namespace. If it *isn't*, and I suspect it isn't (due to `FS_USERNS_MOUNT` likely not being set for OCFS2), then exploiting this might require a privilege escalation. But a malicious image combined with a bug is a serious combination, regardless.
|
| 7/2 |
2026/05/17 16:37 |
tool |
grepper |
0m
Args: map[Expression:FS_USERNS_MOUNT]
Results: map[Output:drivers/android/binder/rust_binderfs.c=753=static struct file_system_type binder_fs_type = {
--
drivers/android/binder/rust_binderfs.c-757- .kill_sb = binderfs_kill_super,
drivers/android/binder/rust_binderfs.c:758: .fs_flags = FS_USERNS_MOUNT,
drivers/android/binder/rust_binderfs.c-759-};
--
drivers/android/binderfs.c=749=static struct file_system_type binder_fs_type = {
--
drivers/android/binderfs.c-753- .kill_sb = binderfs_kill_super,
drivers/android/binderfs.c:754: .fs_flags = FS_USERNS_MOUNT,
drivers/android/binderfs.c-755-};
--
fs/binfmt_misc.c=1021=static struct file_system_type bm_fs_type = {
--
fs/binfmt_misc.c-1024- .init_fs_context = bm_init_fs_context,
fs/binfmt_misc.c:1025: .fs_flags = FS_USERNS_MOUNT,
fs/binfmt_misc.c-1026- .kill_sb = kill_anon_super,
--
fs/devpts/inode.c=453=static struct file_system_type devpts_fs_type = {
--
fs/devpts/inode.c-457- .kill_sb = devpts_kill_sb,
fs/devpts/inode.c:458: .fs_flags = FS_USERNS_MOUNT,
fs/devpts/inode.c-459-};
--
fs/fuse/inode.c=2157=static struct file_system_type fuse_fs_type = {
--
fs/fuse/inode.c-2159- .name = "fuse",
fs/fuse/inode.c:2160: .fs_flags = FS_HAS_SUBTYPE | FS_USERNS_MOUNT | FS_ALLOW_IDMAP,
fs/fuse/inode.c-2161- .init_fs_context = fuse_init_fs_context,
--
fs/overlayfs/params.c=782=static const struct fs_context_operations ovl_context_ops = {
--
fs/overlayfs/params.c-791- * This is called during fsopen() and will record the user namespace of
fs/overlayfs/params.c:792: * the caller in fc->user_ns since we've raised FS_USERNS_MOUNT. We'll
fs/overlayfs/params.c-793- * need it when we actually create the superblock to verify that the
--
fs/overlayfs/super.c=1571=struct file_system_type ovl_fs_type = {
--
fs/overlayfs/super.c-1575- .parameters = ovl_parameter_spec,
fs/overlayfs/super.c:1576: .fs_flags = FS_USERNS_MOUNT,
fs/overlayfs/super.c-1577- .kill_sb = kill_anon_super,
--
fs/proc/root.c=357=static struct file_system_type proc_fs_type = {
--
fs/proc/root.c-361- .kill_sb = proc_kill_sb,
fs/proc/root.c:362: .fs_flags = FS_USERNS_MOUNT | FS_DISALLOW_NOTIFY_PERM,
fs/proc/root.c-363-};
--
fs/ramfs/inode.c=317=static struct file_system_type ramfs_fs_type = {
--
fs/ramfs/inode.c-321- .kill_sb = ramfs_kill_sb,
fs/ramfs/inode.c:322: .fs_flags = FS_USERNS_MOUNT,
fs/ramfs/inode.c-323-};
--
fs/super.c=694=bool mount_capable(struct fs_context *fc)
fs/super.c-695-{
fs/super.c:696: if (!(fc->fs_type->fs_flags & FS_USERNS_MOUNT))
fs/super.c-697- return capable(CAP_SYS_ADMIN);
--
fs/super.c=734=struct super_block *sget_fc(struct fs_context *fc,
--
fs/super.c-743- /*
fs/super.c:744: * Never allow s_user_ns != &init_user_ns when FS_USERNS_MOUNT is
fs/super.c-745- * not set, as the filesystem is likely unprepared to handle it.
--
fs/super.c-748- */
fs/super.c:749: if (user_ns != &init_user_ns && !(fc->fs_type->fs_flags & FS_USERNS_MOUNT)) {
fs/super.c-750- errorfc(fc, "VFS: Mounting from non-initial user namespace is not allowed");
--
fs/sysfs/mount.c=92=static struct file_system_type sysfs_fs_type = {
--
fs/sysfs/mount.c-95- .kill_sb = sysfs_kill_sb,
fs/sysfs/mount.c:96: .fs_flags = FS_USERNS_MOUNT,
fs/sysfs/mount.c-97-};
--
include/linux/fs.h=2279=struct file_system_type {
--
include/linux/fs.h-2284-#define FS_HAS_SUBTYPE 4
include/linux/fs.h:2285:#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
include/linux/fs.h-2286-#define FS_DISALLOW_NOTIFY_PERM 16 /* Disable fanotify permission events */
--
ipc/mqueue.c=1613=static struct file_system_type mqueue_fs_type = {
--
ipc/mqueue.c-1616- .kill_sb = kill_anon_super,
ipc/mqueue.c:1617: .fs_flags = FS_USERNS_MOUNT,
ipc/mqueue.c-1618-};
--
kernel/bpf/inode.c=1074=static struct file_system_type bpf_fs_type = {
--
kernel/bpf/inode.c-1079- .kill_sb = bpf_kill_super,
kernel/bpf/inode.c:1080: .fs_flags = FS_USERNS_MOUNT,
kernel/bpf/inode.c-1081-};
--
kernel/cgroup/cgroup.c=2332=struct file_system_type cgroup_fs_type = {
--
kernel/cgroup/cgroup.c-2336- .kill_sb = cgroup_kill_sb,
kernel/cgroup/cgroup.c:2337: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2338-};
--
kernel/cgroup/cgroup.c=2340=static struct file_system_type cgroup2_fs_type = {
--
kernel/cgroup/cgroup.c-2344- .kill_sb = cgroup_kill_sb,
kernel/cgroup/cgroup.c:2345: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2346-};
--
kernel/cgroup/cgroup.c=2413=static struct file_system_type cpuset_fs_type = {
--
kernel/cgroup/cgroup.c-2416- .parameters = cpuset_fs_parameters,
kernel/cgroup/cgroup.c:2417: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2418-};
--
mm/shmem.c=5363=static struct file_system_type shmem_fs_type = {
--
mm/shmem.c-5370- .kill_sb = kill_anon_super,
mm/shmem.c:5371: .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP | FS_MGTIME,
mm/shmem.c-5372-};
--
mm/shmem.c=5772=static struct file_system_type shmem_fs_type = {
--
mm/shmem.c-5776- .kill_sb = ramfs_kill_sb,
mm/shmem.c:5777: .fs_flags = FS_USERNS_MOUNT,
mm/shmem.c-5778-};
]
|
| 8/2 |
2026/05/17 16:37 |
llm |
expert |
0m
Model:
Tokens:
input: 19130
output: 66
thoughts: 863
Thoughts: **Analyzing the ocfs2 Deadlock Vulnerability**
Okay, so the output confirms what I suspected: `ocfs2` doesn't support `FS_USERNS_MOUNT`. This means we need `CAP_SYS_ADMIN` in the initial user namespace to mount it. That's good, it restricts the attack surface significantly. No unprivileged processes or user namespaces are directly affected. This is looking like it requires a malicious filesystem image.
The core issue is a circular locking dependency – a classic deadlock. This causes a Denial of Service, specifically a system hang. Deadlocks, thankfully, aren't typically exploitable for privilege escalation or memory corruption. They just hang things.
Let's dig a bit deeper into the trigger. The bug is within `ocfs2_try_remove_refcount_tree`, triggered by `ocfs2_truncate_file`. This gets called from `ocfs2_setattr` during `do_truncate`, which we'd see with an `open(O_TRUNC)` or a `creat` call, for example.
The lockdep warning points to a deadlock between `oi->ip_alloc_sem`, `journal->j_trans_barrier`, and `oi->ip_xattr_sem`. This is happening because the critical file attribute locking order is violated, creating this ABBA deadlock scenario: CPU0 takes `oi->ip_xattr_sem` then `oi->ip_alloc_sem`, while CPU1 takes `journal->j_trans_barrier` then tries to grab the already held `oi->ip_xattr_sem`.
Since we need to mount an ocfs2 filesystem, and unprivileged users can't mount it, it's pretty much root or physical access only. Auto-mounting `ocfs2` on typical desktops is unlikely, but it *is* a filesystem, so "Malicious Filesystem Trigger" is definitely True. And a USB drive could absolutely contain a malicious `ocfs2` filesystem, so "Peripheral Trigger" is also True.
Obviously, it's a Denial of Service ("Denial Of Service" is True) because it's a deadlock. The system will hang. "Exploitable" is False because deadlocks are not typically exploitable for anything more than a DoS. "Accessible From Unprivileged Processes" and "Accessible From User Namespaces" are both False because of the lack of `FS_USERNS_MOUNT` support.
Let's review the lockdep report just to confirm the ABBA deadlock: `&oi->ip_alloc_sem` -> `&journal->j_trans_barrier` -> `&oi->ip_xattr_sem`, in that order. The locking scenario shows it clearly: acquiring `&oi->ip_xattr_sem` then `&oi->ip_alloc_sem` on CPU0 while on CPU1 we attempt to take `&journal->j_trans_barrier` and then, the already held lock `&oi->ip_xattr_sem`, leading to the hang. This is textbook.
I quickly double-checked for alternative mounting methods. Nope, no `FS_USERNS_MOUNT`, so it requires `CAP_SYS_ADMIN` in the init namespace.
So the results are:
* **Exploitable: False**
* **DenialOfService: True**
* **Unprivileged: False**
* **UserNamespace: False**
* **VMGuestTrigger: False**
* **VMHostTrigger: False**
* **NetworkTrigger: False**
* **RemoteTrigger: False**
* **PeripheralTrigger: True**
* **FilesystemTrigger: True**
Let's just consider the auto-mounting. Desktop environments usually auto-mount vfat, exfat, ntfs, ext4. They'll try anything blkid recognizes if there's a kernel module. So "PeripheralTrigger" and "FilesystemTrigger" are definitely True. And no other ways to trigger this without mounting, given it's `ocfs2` specific.
Okay, I think we're done here, set-results.
|
| 9/2 |
2026/05/17 16:37 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 10/2 |
2026/05/17 16:37 |
llm |
expert |
0m
Model:
Tokens:
input: 21011
output: 448
thoughts: 288
Thoughts: **Final Reasoning on the OCFS2 Deadlock**
Alright, the findings are in, and now it's time to solidify my reasoning. The core issue, as I see it, is a circular locking dependency – a classic deadlock scenario – right in the `ocfs2` filesystem code. It specifically revolves around the interplay of `ip_alloc_sem`, `j_trans_barrier`, and `ip_xattr_sem`. This is one of those frustrating, yet ultimately understandable, issues.
I can trigger this deadlock by intentionally crafting an `ocfs2` filesystem image that's just *asking* to be locked up. Certain file operations, like creating files, opening them with truncation, or renaming them, are the keys to unlock it.
While this is clearly a Denial of Service, causing tasks to hang indefinitely, it's not the end of the world. Thankfully, this is not exploitable in the typical sense. It doesn't lead to memory corruption, nor can it be used to escalate privileges, which is a significant relief. It's confined to the realm of a DOS.
The caveat here is the privileges needed for mounting the `ocfs2` image. As `ocfs2` doesn't have the `FS_USERNS_MOUNT` flag, it requires `CAP_SYS_ADMIN` in the initial user namespace. This means an unprivileged user cannot exploit it, even with the malicious image. This also suggests this bug is not an effective attack vector for container escapes.
However, the fact that a privileged user can trigger it by mounting a maliciously crafted `ocfs2` image is still a concern. Similarly, auto-mounting of external media (USB drives, etc.) that could contain a malicious image is another avenue for exploitation. Therefore, I can classify it as a Malicious Filesystem Trigger and also a Peripheral Trigger. It's not related to network-based attacks or any VM-specific aspects.
|
| 11/1 |
2026/05/17 16:37 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the bug report and the kernel source code, here is the
assessment of the security impact:
**Exploitable:** False
The bug is a circular locking dependency (ABBA deadlock) between `ip_alloc_sem`,
`j_trans_barrier`, and `ip_xattr_sem` in the `ocfs2` filesystem. Deadlocks lead
to hanging tasks and system unresponsiveness, but they do not cause memory
corruption, out-of-bounds accesses, or control-flow hijacking. Therefore, it is
not exploitable for privilege escalation or arbitrary code execution.
**Denial Of Service:** True
The deadlock will cause the kernel threads attempting to acquire these locks to
hang indefinitely. This can block further operations on the filesystem and
potentially lead to a system-wide hang or panic (if hung task detection is
configured to panic), resulting in a Denial of Service.
**Accessible From Unprivileged Processes / User Namespaces:** False
Triggering this bug requires mounting a crafted `ocfs2` filesystem image. The
`ocfs2` filesystem does not have the `FS_USERNS_MOUNT` flag set in its
`file_system_type` definition. This means it cannot be mounted from within an
unprivileged user namespace. Mounting it requires `CAP_SYS_ADMIN` in the initial
(root) user namespace. Thus, unprivileged users cannot reach this bug directly.
**Malicious Filesystem / Peripheral Trigger:** True
The bug is triggered by interacting with a maliciously crafted `ocfs2`
filesystem image (e.g., via `creat`, `openat`, or `rename` operations). If an
attacker can trick a privileged user into mounting such an image, or if the
system is configured to automatically mount external media (like a USB drive)
upon insertion, the bug can be triggered. Therefore, it qualifies as both a
Malicious Filesystem Trigger and a Peripheral Trigger.
**Network / Remote / VM Triggers:** False
The bug is strictly contained within the local filesystem implementation
(`ocfs2`) and is not exposed to network traffic, remote protocols, or
virtualization-specific hypercalls/paravirtualized devices.]
|