| 0/0 |
2026/05/25 08:15 |
flow |
assessment-security |
43m
Results: map[DenialOfService:false Explanation:Based on the analysis of the bug report and the kernel source code, here is the
assessment of the security impact:
**Bug Analysis:**
The bug is a `WARNING` triggered in `cleanup_transaction` (in
`fs/btrfs/transaction.c`) due to a transaction abort with error `-28` (`ENOSPC`
- No space left on device).
In Btrfs, running out of space *during* a transaction is considered a bug in the
space reservation logic (space should have been reserved before the transaction
started). To help debug such issues, Btrfs uses a `WARN` macro to print a stack
trace when a transaction is aborted due to `ENOSPC`.
**Security Impact:**
* **Exploitable:** No. The condition is properly handled by the Btrfs error
handling paths. The transaction is aborted, and the filesystem is turned
read-only. There is no memory corruption, out-of-bounds access, or
use-after-free involved.
* **Denial Of Service:** No. The `WARNING` does not halt the kernel in
production builds (unless `panic_on_warn` is explicitly set). The transaction
abort only affects the specific Btrfs filesystem that ran out of space, making
it read-only. It does not crash the system, cause deadlocks, or affect other
filesystems.
* **Accessible From Unprivileged Processes / User Namespaces:** No. The crash is
triggered via the `BTRFS_IOC_BALANCE` ioctl. In `fs/btrfs/ioctl.c`,
`btrfs_ioctl_balance()` explicitly checks for `capable(CAP_SYS_ADMIN)`, which
requires root privileges in the initial user namespace. Furthermore, Btrfs does
not have the `FS_USERNS_MOUNT` flag, meaning unprivileged users cannot mount a
Btrfs image even within a user namespace.
* **Filesystem / Peripheral Trigger:** Yes. A malicious Btrfs image on a USB
drive could potentially trigger this if the desktop environment auto-mounts it
and the filesystem resumes a paused balance operation upon mount. However, as
established, the impact is benign (a kernel warning and the USB drive becoming
read-only).
In conclusion, this is a functional bug in Btrfs space reservation during
balance operations, but it does not pose a security boundary violation or a
system-wide denial-of-service threat. Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/25 08:15 |
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;
static long syz_open_procfs(volatile long a0, volatile long a1)
{
char buf[128];
memset(buf, 0, sizeof(buf));
if (a0 == 0) {
snprintf(buf, sizeof(buf), "/proc/self/%s", (char*)a1);
} else if (a0 == -1) {
snprintf(buf, sizeof(buf), "/proc/thread-self/%s", (char*)a1);
} else {
snprintf(buf, sizeof(buf), "/proc/self/task/%d/%s", (int)a0, (char*)a1);
}
int fd = open(buf, O_RDWR);
if (fd == -1)
fd = open(buf, O_RDONLY);
return fd;
}
//% 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[3] = {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff};
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
intptr_t res = 0;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// syz_mount_image$btrfs arguments: [
// fs: ptr[in, buffer] {
// buffer: {62 74 72 66 73 00} (length 0x6)
// }
// dir: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 30 00} (length 0x8)
// }
// flags: mount_flags = 0x0 (8 bytes)
// opts: ptr[in, fs_options[btrfs_options]] {
// fs_options[btrfs_options] {
// elems: array[fs_opt_elem[btrfs_options]] {
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// clear_cache: buffer: {63 6c 65 61 72 5f 63 61 63 68 65} (length 0xb)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// user_subvol_rm: buffer: {75 73 65 72 5f 73 75 62 76 6f 6c 5f 72 6d 5f 61 6c 6c 6f 77 65 64} (length 0x16)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// compress_force_algo: fs_opt["compress-force", stringnoz[btrfs_compress_algos]] {
// name: buffer: {63 6f 6d 70 72 65 73 73 2d 66 6f 72 63 65} (length 0xe)
// eq: const = 0x3d (1 bytes)
// val: buffer: {7a 6c 69 62} (length 0x4)
// }
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// noautodefrag: buffer: {6e 6f 61 75 74 6f 64 65 66 72 61 67} (length 0xc)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// autodefrag: buffer: {61 75 74 6f 64 65 66 72 61 67} (length 0xa)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// autodefrag: buffer: {61 75 74 6f 64 65 66 72 61 67} (length 0xa)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// metadata_ratio: fs_opt["metadata_ratio", fmt[hex, int32]] {
// name: buffer: {6d 65 74 61 64 61 74 61 5f 72 61 74 69 6f} (length 0xe)
// eq: const = 0x3d (1 bytes)
// val: int32 = 0x101 (18 bytes)
// }
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// space_cache: buffer: {73 70 61 63 65 5f 63 61 63 68 65} (length 0xb)
// }
// comma: const = 0x2c (1 bytes)
// }
// }
// common: array[fs_opt_elem[fs_options_common]] {
// }
// null: const = 0x0 (1 bytes)
// }
// }
// chdir: int8 = 0x0 (1 bytes)
// size: len = 0x559e (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x559e)
// }
// ]
// returns fd_dir
memcpy((void*)0x200000000000, "btrfs\000", 6);
memcpy((void*)0x2000000015c0, "./file0\000", 8);
memcpy((void*)0x200000000440, "clear_cache", 11);
*(uint8_t*)0x20000000044b = 0x2c;
memcpy((void*)0x20000000044c, "user_subvol_rm_allowed", 22);
*(uint8_t*)0x200000000462 = 0x2c;
memcpy((void*)0x200000000463, "compress-force", 14);
*(uint8_t*)0x200000000471 = 0x3d;
memcpy((void*)0x200000000472, "zlib", 4);
*(uint8_t*)0x200000000476 = 0x2c;
memcpy((void*)0x200000000477, "noautodefrag", 12);
*(uint8_t*)0x200000000483 = 0x2c;
memcpy((void*)0x200000000484, "autodefrag", 10);
*(uint8_t*)0x20000000048e = 0x2c;
memcpy((void*)0x20000000048f, "autodefrag", 10);
*(uint8_t*)0x200000000499 = 0x2c;
memcpy((void*)0x20000000049a, "metadata_ratio", 14);
*(uint8_t*)0x2000000004a8 = 0x3d;
sprintf((char*)0x2000000004a9, "0x%016llx", (long long)0x101);
*(uint8_t*)0x2000000004bb = 0x2c;
memcpy((void*)0x2000000004bc, "space_cache", 11);
*(uint8_t*)0x2000000004c7 = 0x2c;
*(uint8_t*)0x2000000004c8 = 0;
memcpy((void*)0x2000000103c0, "... [truncated large byte array] ...", 21918);
syz_mount_image(/*fs=*/0x200000000000, /*dir=*/0x2000000015c0, /*flags=*/0, /*opts=*/0x200000000440, /*chdir=*/0, /*size=*/0x559e, /*img=*/0x2000000103c0);
// chdir arguments: [
// dir: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 30 00} (length 0x8)
// }
// ]
memcpy((void*)0x200000000140, "./file0\000", 8);
syscall(__NR_chdir, /*dir=*/0x200000000140ul);
// open arguments: [
// file: ptr[in, buffer] {
// buffer: {2e 2f 62 75 73 00} (length 0x6)
// }
// flags: open_flags = 0x14927e (8 bytes)
// mode: open_mode = 0x41 (8 bytes)
// ]
// returns fd
memcpy((void*)0x200000000180, "./bus\000", 6);
res = syscall(__NR_open, /*file=*/0x200000000180ul, /*flags=O_TRUNC|O_SYNC|O_NOATIME|O_LARGEFILE|O_CREAT|O_RDWR|0x3c*/0x14927eul, /*mode=S_IXOTH|S_IXUSR*/0x41ul);
if (res != -1)
r[0] = res;
// ioctl$FITRIM arguments: [
// fd: fd (resource)
// cmd: const = 0xc4009420 (4 bytes)
// arg: ptr[in, fstrim_range] {
// fstrim_range {
// start: int64 = 0x5 (8 bytes)
// len: int64 = 0x7e00 (8 bytes)
// minlen: int64 = 0x0 (8 bytes)
// }
// }
// ]
*(uint64_t*)0x200000000180 = 5;
*(uint64_t*)0x200000000188 = 0x7e00;
*(uint64_t*)0x200000000190 = 0;
syscall(__NR_ioctl, /*fd=*/r[0], /*cmd=*/0xc4009420, /*arg=*/0x200000000180ul);
// creat arguments: [
// file: ptr[in, buffer] {
// buffer: {2e 2f 62 75 73 00} (length 0x6)
// }
// mode: open_mode = 0x0 (8 bytes)
// ]
// returns fd
memcpy((void*)0x200000000040, "./bus\000", 6);
res = syscall(__NR_creat, /*file=*/0x200000000040ul, /*mode=*/0ul);
if (res != -1)
r[1] = res;
// 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 31 00} (length 0x8)
// }
// flags: mount_flags = 0x1000040 (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 {
// barrier: buffer: {62 61 72 72 69 65 72} (length 0x7)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// nodioread_nolock: buffer: {6e 6f 64 69 6f 72 65 61 64 5f 6e 6f 6c 6f 63 6b} (length 0x10)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// jqfmt_vfsv0: buffer: {6a 71 66 6d 74 3d 76 66 73 76 30} (length 0xb)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// barrier: buffer: {62 61 72 72 69 65 72} (length 0x7)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// auto_da_alloc: buffer: {61 75 74 6f 5f 64 61 5f 61 6c 6c 6f 63} (length 0xd)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// nodioread_nolock: buffer: {6e 6f 64 69 6f 72 65 61 64 5f 6e 6f 6c 6f 63 6b} (length 0x10)
// }
// 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 = 0x59c (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x59c)
// }
// ]
// returns fd_dir
memcpy((void*)0x2000000000c0, "ext4\000", 5);
memcpy((void*)0x200000000000, "./file1\000", 8);
memcpy((void*)0x200000000180, "barrier", 7);
*(uint8_t*)0x200000000187 = 0x2c;
memcpy((void*)0x200000000188, "nodioread_nolock", 16);
*(uint8_t*)0x200000000198 = 0x2c;
memcpy((void*)0x200000000199, "jqfmt=vfsv0", 11);
*(uint8_t*)0x2000000001a4 = 0x2c;
memcpy((void*)0x2000000001a5, "barrier", 7);
*(uint8_t*)0x2000000001ac = 0x2c;
memcpy((void*)0x2000000001ad, "auto_da_alloc", 13);
*(uint8_t*)0x2000000001ba = 0x2c;
memcpy((void*)0x2000000001bb, "nodioread_nolock", 16);
*(uint8_t*)0x2000000001cb = 0x2c;
*(uint8_t*)0x2000000001cc = 0;
memcpy((void*)0x200000001840, "... [truncated large byte array] ...", 1436);
syz_mount_image(/*fs=*/0x2000000000c0, /*dir=*/0x200000000000, /*flags=MS_STRICTATIME|MS_MANDLOCK*/0x1000040, /*opts=*/0x200000000180, /*chdir=*/1, /*size=*/0x59c, /*img=*/0x200000001840);
// openat arguments: [
// fd: fd_dir (resource)
// file: nil
// flags: open_flags = 0x0 (4 bytes)
// mode: open_mode = 0x0 (2 bytes)
// ]
// returns fd
syscall(__NR_openat, /*fd=*/0xffffff9c, /*file=*/0ul, /*flags=*/0, /*mode=*/0);
// syz_open_procfs arguments: [
// pid: pid (resource)
// file: ptr[in, buffer] {
// buffer: {6d 6f 75 6e 74 69 6e 66 6f 00} (length 0xa)
// }
// ]
// returns fd
memcpy((void*)0x200000000040, "mountinfo\000", 10);
res = -1;
res = syz_open_procfs(/*pid=*/-1, /*file=*/0x200000000040);
if (res != -1)
r[2] = res;
// lseek arguments: [
// fd: fd (resource)
// offset: intptr = 0xfffd (8 bytes)
// whence: seek_whence = 0x0 (8 bytes)
// ]
syscall(__NR_lseek, /*fd=*/r[2], /*offset=*/0xfffdul, /*whence=*/0ul);
// ioctl$FS_IOC_REMOVE_ENCRYPTION_KEY arguments: [
// fd: fd_dir (resource)
// cmd: const = 0xc0185879 (4 bytes)
// arg: ptr[inout, fscrypt_remove_key_arg] {
// fscrypt_remove_key_arg {
// key_spec: union fscrypt_key_specifier {
// desc: fscrypt_key_specifier__by_descriptor {
// type: const = 0x1 (4 bytes)
// reserved: const = 0x0 (4 bytes)
// descriptor: union fscrypt_key_descriptor {
// desc3: buffer: {e8 da b9 92 34 bb 31 2e} (length 0x8)
// }
// reserved2: buffer: {00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00} (length 0x18)
// }
// }
// removal_status_flags: int32 = 0x0 (4 bytes)
// reserved: buffer: {00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00} (length 0x14)
// }
// }
// ]
*(uint32_t*)0x200000000080 = 1;
*(uint32_t*)0x200000000084 = 0;
memcpy((void*)0x200000000088, "\350\332\271\2224\2731.", 8);
memset((void*)0x200000000090, 0, 24);
memset((void*)0x2000000000ac, 0, 20);
syscall(__NR_ioctl, /*fd=*/r[1], /*cmd=*/0xc0185879, /*arg=*/0x200000000080ul);
return 0;
}
]
|
| 2/1 |
2026/05/25 08:15 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/64ce7073360d8ffada95f0fe7d47ad11e5540bda]
|
| 3/1 |
2026/05/25 08:17 |
action |
kernel-builder |
27m
Results: map[KernelObj:/app/workdir/cache/build/5dca7e888c96a99991c6b3e1c19d1009d4b8980d]
|
| 4/1 |
2026/05/25 08:44 |
action |
codesearch-prepare |
12m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/25 08:56 |
agent |
expert |
1m
Model:
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Instruction:
You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by greeping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption or elevated privileges.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completly wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control tranfers, that's also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitabability depends on how exactly the uninit value
is used in the code, and what it affects.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidetial Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
BTRFS info (device loop0): relocating block group 6881280 flags data|metadata
BTRFS info (device loop0): relocating block group 5242880 flags data|metadata
BTRFS warning (device loop0): Skipping commit of aborted transaction.
------------[ cut here ]------------
BTRFS: Transaction aborted (error -28)
WARNING: fs/btrfs/transaction.c:2045 at cleanup_transaction+0x7e6/0x9c0 fs/btrfs/transaction.c:2045, CPU#0: syz.0.55/6681
Modules linked in:
CPU: 0 UID: 0 PID: 6681 Comm: syz.0.55 Not tainted syzkaller #0 PREEMPT_{RT,(full)}
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2026
RIP: 0010:cleanup_transaction+0x7e9/0x9c0 fs/btrfs/transaction.c:2045
Code: 0a e8 9b 11 e4 fd e9 82 00 00 00 e8 21 19 c9 fd 84 c0 74 2a e8 88 11 e4 fd eb 72 e8 81 11 e4 fd 48 8d 3d 0a 90 92 0b 44 89 fe <67> 48 0f b9 3a 4c 8b 6c 24 18 4c 8b 64 24 20 e9 a7 f9 ff ff e8 5e
RSP: 0018:ffffc9000682f3a0 EFLAGS: 00010293
RAX: ffffffff83e0428f RBX: ffff88802d2d0000 RCX: ffff888028ee9e40
RDX: 0000000000000000 RSI: 00000000ffffffe4 RDI: ffffffff8f72d2a0
RBP: ffffc9000682f4d0 R08: ffff888028ee9e40 R09: 0000000000000003
R10: 0000000000000100 R11: 00000000fffffffb R12: 1ffff110088f7736
R13: ffff8880447bb9b0 R14: 0000000000000000 R15: 00000000ffffffe4
FS: 00007f25d41fe6c0(0000) GS:ffff888126342000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00005630992c55f8 CR3: 000000003b062000 CR4: 00000000003526f0
Call Trace:
<TASK>
btrfs_commit_transaction+0x25aa/0x31a0 fs/btrfs/transaction.c:2630
prepare_to_relocate+0x3a1/0x490 fs/btrfs/relocation.c:3479
relocate_block_group+0x149/0xe90 fs/btrfs/relocation.c:3504
do_nonremap_reloc+0xa7/0x5b0 fs/btrfs/relocation.c:5248
btrfs_relocate_block_group+0x5fe/0x980 fs/btrfs/relocation.c:5407
btrfs_relocate_chunk+0x10f/0x820 fs/btrfs/volumes.c:3590
__btrfs_balance+0x1aec/0x26e0 fs/btrfs/volumes.c:4486
btrfs_balance+0xafa/0x11f0 fs/btrfs/volumes.c:4873
btrfs_ioctl_balance+0x3d6/0x610 fs/btrfs/ioctl.c:3446
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:597 [inline]
__se_sys_ioctl+0xff/0x170 fs/ioctl.c:583
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f25d4b9c629
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:00007f25d41fe028 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007f25d4e15fa0 RCX: 00007f25d4b9c629
RDX: 0000200000000180 RSI: 00000000c4009420 RDI: 0000000000000004
RBP: 00007f25d4c32b39 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f25d4e16038 R14: 00007f25d4e15fa0 R15: 00007ffd31bf0b28
</TASK>
----------------
Code disassembly (best guess):
0: 0a e8 or %al,%ch
2: 9b fwait
3: 11 e4 adc %esp,%esp
5: fd std
6: e9 82 00 00 00 jmp 0x8d
b: e8 21 19 c9 fd call 0xfdc91931
10: 84 c0 test %al,%al
12: 74 2a je 0x3e
14: e8 88 11 e4 fd call 0xfde411a1
19: eb 72 jmp 0x8d
1b: e8 81 11 e4 fd call 0xfde411a1
20: 48 8d 3d 0a 90 92 0b lea 0xb92900a(%rip),%rdi # 0xb929031
27: 44 89 fe mov %r15d,%esi
* 2a: 67 48 0f b9 3a ud1 (%edx),%rdi <-- trapping instruction
2f: 4c 8b 6c 24 18 mov 0x18(%rsp),%r13
34: 4c 8b 64 24 20 mov 0x20(%rsp),%r12
39: e9 a7 f9 ff ff jmp 0xfffff9e5
3e: e8 .byte 0xe8
3f: 5e pop %rsi
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;
static long syz_open_procfs(volatile long a0, volatile long a1)
{
char buf[128];
memset(buf, 0, sizeof(buf));
if (a0 == 0) {
snprintf(buf, sizeof(buf), "/proc/self/%s", (char*)a1);
} else if (a0 == -1) {
snprintf(buf, sizeof(buf), "/proc/thread-self/%s", (char*)a1);
} else {
snprintf(buf, sizeof(buf), "/proc/self/task/%d/%s", (int)a0, (char*)a1);
}
int fd = open(buf, O_RDWR);
if (fd == -1)
fd = open(buf, O_RDONLY);
return fd;
}
//% 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[3] = {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff};
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
intptr_t res = 0;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// syz_mount_image$btrfs arguments: [
// fs: ptr[in, buffer] {
// buffer: {62 74 72 66 73 00} (length 0x6)
// }
// dir: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 30 00} (length 0x8)
// }
// flags: mount_flags = 0x0 (8 bytes)
// opts: ptr[in, fs_options[btrfs_options]] {
// fs_options[btrfs_options] {
// elems: array[fs_opt_elem[btrfs_options]] {
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// clear_cache: buffer: {63 6c 65 61 72 5f 63 61 63 68 65} (length 0xb)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// user_subvol_rm: buffer: {75 73 65 72 5f 73 75 62 76 6f 6c 5f 72 6d 5f 61 6c 6c 6f 77 65 64} (length 0x16)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// compress_force_algo: fs_opt["compress-force", stringnoz[btrfs_compress_algos]] {
// name: buffer: {63 6f 6d 70 72 65 73 73 2d 66 6f 72 63 65} (length 0xe)
// eq: const = 0x3d (1 bytes)
// val: buffer: {7a 6c 69 62} (length 0x4)
// }
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// noautodefrag: buffer: {6e 6f 61 75 74 6f 64 65 66 72 61 67} (length 0xc)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// autodefrag: buffer: {61 75 74 6f 64 65 66 72 61 67} (length 0xa)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// autodefrag: buffer: {61 75 74 6f 64 65 66 72 61 67} (length 0xa)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// metadata_ratio: fs_opt["metadata_ratio", fmt[hex, int32]] {
// name: buffer: {6d 65 74 61 64 61 74 61 5f 72 61 74 69 6f} (length 0xe)
// eq: const = 0x3d (1 bytes)
// val: int32 = 0x101 (18 bytes)
// }
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[btrfs_options] {
// elem: union btrfs_options {
// space_cache: buffer: {73 70 61 63 65 5f 63 61 63 68 65} (length 0xb)
// }
// comma: const = 0x2c (1 bytes)
// }
// }
// common: array[fs_opt_elem[fs_options_common]] {
// }
// null: const = 0x0 (1 bytes)
// }
// }
// chdir: int8 = 0x0 (1 bytes)
// size: len = 0x559e (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x559e)
// }
// ]
// returns fd_dir
memcpy((void*)0x200000000000, "btrfs\000", 6);
memcpy((void*)0x2000000015c0, "./file0\000", 8);
memcpy((void*)0x200000000440, "clear_cache", 11);
*(uint8_t*)0x20000000044b = 0x2c;
memcpy((void*)0x20000000044c, "user_subvol_rm_allowed", 22);
*(uint8_t*)0x200000000462 = 0x2c;
memcpy((void*)0x200000000463, "compress-force", 14);
*(uint8_t*)0x200000000471 = 0x3d;
memcpy((void*)0x200000000472, "zlib", 4);
*(uint8_t*)0x200000000476 = 0x2c;
memcpy((void*)0x200000000477, "noautodefrag", 12);
*(uint8_t*)0x200000000483 = 0x2c;
memcpy((void*)0x200000000484, "autodefrag", 10);
*(uint8_t*)0x20000000048e = 0x2c;
memcpy((void*)0x20000000048f, "autodefrag", 10);
*(uint8_t*)0x200000000499 = 0x2c;
memcpy((void*)0x20000000049a, "metadata_ratio", 14);
*(uint8_t*)0x2000000004a8 = 0x3d;
sprintf((char*)0x2000000004a9, "0x%016llx", (long long)0x101);
*(uint8_t*)0x2000000004bb = 0x2c;
memcpy((void*)0x2000000004bc, "space_cache", 11);
*(uint8_t*)0x2000000004c7 = 0x2c;
*(uint8_t*)0x2000000004c8 = 0;
memcpy((void*)0x2000000103c0, "... [truncated large byte array] ...", 21918);
syz_mount_image(/*fs=*/0x200000000000, /*dir=*/0x2000000015c0, /*flags=*/0, /*opts=*/0x200000000440, /*chdir=*/0, /*size=*/0x559e, /*img=*/0x2000000103c0);
// chdir arguments: [
// dir: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 30 00} (length 0x8)
// }
// ]
memcpy((void*)0x200000000140, "./file0\000", 8);
syscall(__NR_chdir, /*dir=*/0x200000000140ul);
// open arguments: [
// file: ptr[in, buffer] {
// buffer: {2e 2f 62 75 73 00} (length 0x6)
// }
// flags: open_flags = 0x14927e (8 bytes)
// mode: open_mode = 0x41 (8 bytes)
// ]
// returns fd
memcpy((void*)0x200000000180, "./bus\000", 6);
res = syscall(__NR_open, /*file=*/0x200000000180ul, /*flags=O_TRUNC|O_SYNC|O_NOATIME|O_LARGEFILE|O_CREAT|O_RDWR|0x3c*/0x14927eul, /*mode=S_IXOTH|S_IXUSR*/0x41ul);
if (res != -1)
r[0] = res;
// ioctl$FITRIM arguments: [
// fd: fd (resource)
// cmd: const = 0xc4009420 (4 bytes)
// arg: ptr[in, fstrim_range] {
// fstrim_range {
// start: int64 = 0x5 (8 bytes)
// len: int64 = 0x7e00 (8 bytes)
// minlen: int64 = 0x0 (8 bytes)
// }
// }
// ]
*(uint64_t*)0x200000000180 = 5;
*(uint64_t*)0x200000000188 = 0x7e00;
*(uint64_t*)0x200000000190 = 0;
syscall(__NR_ioctl, /*fd=*/r[0], /*cmd=*/0xc4009420, /*arg=*/0x200000000180ul);
// creat arguments: [
// file: ptr[in, buffer] {
// buffer: {2e 2f 62 75 73 00} (length 0x6)
// }
// mode: open_mode = 0x0 (8 bytes)
// ]
// returns fd
memcpy((void*)0x200000000040, "./bus\000", 6);
res = syscall(__NR_creat, /*file=*/0x200000000040ul, /*mode=*/0ul);
if (res != -1)
r[1] = res;
// 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 31 00} (length 0x8)
// }
// flags: mount_flags = 0x1000040 (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 {
// barrier: buffer: {62 61 72 72 69 65 72} (length 0x7)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// nodioread_nolock: buffer: {6e 6f 64 69 6f 72 65 61 64 5f 6e 6f 6c 6f 63 6b} (length 0x10)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// jqfmt_vfsv0: buffer: {6a 71 66 6d 74 3d 76 66 73 76 30} (length 0xb)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// barrier: buffer: {62 61 72 72 69 65 72} (length 0x7)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// auto_da_alloc: buffer: {61 75 74 6f 5f 64 61 5f 61 6c 6c 6f 63} (length 0xd)
// }
// comma: const = 0x2c (1 bytes)
// }
// fs_opt_elem[ext4_options] {
// elem: union ext4_options {
// nodioread_nolock: buffer: {6e 6f 64 69 6f 72 65 61 64 5f 6e 6f 6c 6f 63 6b} (length 0x10)
// }
// 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 = 0x59c (8 bytes)
// img: ptr[in, buffer] {
// buffer: (compressed buffer with length 0x59c)
// }
// ]
// returns fd_dir
memcpy((void*)0x2000000000c0, "ext4\000", 5);
memcpy((void*)0x200000000000, "./file1\000", 8);
memcpy((void*)0x200000000180, "barrier", 7);
*(uint8_t*)0x200000000187 = 0x2c;
memcpy((void*)0x200000000188, "nodioread_nolock", 16);
*(uint8_t*)0x200000000198 = 0x2c;
memcpy((void*)0x200000000199, "jqfmt=vfsv0", 11);
*(uint8_t*)0x2000000001a4 = 0x2c;
memcpy((void*)0x2000000001a5, "barrier", 7);
*(uint8_t*)0x2000000001ac = 0x2c;
memcpy((void*)0x2000000001ad, "auto_da_alloc", 13);
*(uint8_t*)0x2000000001ba = 0x2c;
memcpy((void*)0x2000000001bb, "nodioread_nolock", 16);
*(uint8_t*)0x2000000001cb = 0x2c;
*(uint8_t*)0x2000000001cc = 0;
memcpy((void*)0x200000001840, "... [truncated large byte array] ...", 1436);
syz_mount_image(/*fs=*/0x2000000000c0, /*dir=*/0x200000000000, /*flags=MS_STRICTATIME|MS_MANDLOCK*/0x1000040, /*opts=*/0x200000000180, /*chdir=*/1, /*size=*/0x59c, /*img=*/0x200000001840);
// openat arguments: [
// fd: fd_dir (resource)
// file: nil
// flags: open_flags = 0x0 (4 bytes)
// mode: open_mode = 0x0 (2 bytes)
// ]
// returns fd
syscall(__NR_openat, /*fd=*/0xffffff9c, /*file=*/0ul, /*flags=*/0, /*mode=*/0);
// syz_open_procfs arguments: [
// pid: pid (resource)
// file: ptr[in, buffer] {
// buffer: {6d 6f 75 6e 74 69 6e 66 6f 00} (length 0xa)
// }
// ]
// returns fd
memcpy((void*)0x200000000040, "mountinfo\000", 10);
res = -1;
res = syz_open_procfs(/*pid=*/-1, /*file=*/0x200000000040);
if (res != -1)
r[2] = res;
// lseek arguments: [
// fd: fd (resource)
// offset: intptr = 0xfffd (8 bytes)
// whence: seek_whence = 0x0 (8 bytes)
// ]
syscall(__NR_lseek, /*fd=*/r[2], /*offset=*/0xfffdul, /*whence=*/0ul);
// ioctl$FS_IOC_REMOVE_ENCRYPTION_KEY arguments: [
// fd: fd_dir (resource)
// cmd: const = 0xc0185879 (4 bytes)
// arg: ptr[inout, fscrypt_remove_key_arg] {
// fscrypt_remove_key_arg {
// key_spec: union fscrypt_key_specifier {
// desc: fscrypt_key_specifier__by_descriptor {
// type: const = 0x1 (4 bytes)
// reserved: const = 0x0 (4 bytes)
// descriptor: union fscrypt_key_descriptor {
// desc3: buffer: {e8 da b9 92 34 bb 31 2e} (length 0x8)
// }
// reserved2: buffer: {00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00} (length 0x18)
// }
// }
// removal_status_flags: int32 = 0x0 (4 bytes)
// reserved: buffer: {00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00} (length 0x14)
// }
// }
// ]
*(uint32_t*)0x200000000080 = 1;
*(uint32_t*)0x200000000084 = 0;
memcpy((void*)0x200000000088, "\350\332\271\2224\2731.", 8);
memset((void*)0x200000000090, 0, 24);
memset((void*)0x2000000000ac, 0, 20);
syscall(__NR_ioctl, /*fd=*/r[1], /*cmd=*/0xc0185879, /*arg=*/0x200000000080ul);
return 0;
}
Reply: Based on the analysis of the bug report and the kernel source code, here is the assessment of the security impact:
**Bug Analysis:**
The bug is a `WARNING` triggered in `cleanup_transaction` (in `fs/btrfs/transaction.c`) due to a transaction abort with error `-28` (`ENOSPC` - No space left on device).
In Btrfs, running out of space *during* a transaction is considered a bug in the space reservation logic (space should have been reserved before the transaction started). To help debug such issues, Btrfs uses a `WARN` macro to print a stack trace when a transaction is aborted due to `ENOSPC`.
**Security Impact:**
* **Exploitable:** No. The condition is properly handled by the Btrfs error handling paths. The transaction is aborted, and the filesystem is turned read-only. There is no memory corruption, out-of-bounds access, or use-after-free involved.
* **Denial Of Service:** No. The `WARNING` does not halt the kernel in production builds (unless `panic_on_warn` is explicitly set). The transaction abort only affects the specific Btrfs filesystem that ran out of space, making it read-only. It does not crash the system, cause deadlocks, or affect other filesystems.
* **Accessible From Unprivileged Processes / User Namespaces:** No. The crash is triggered via the `BTRFS_IOC_BALANCE` ioctl. In `fs/btrfs/ioctl.c`, `btrfs_ioctl_balance()` explicitly checks for `capable(CAP_SYS_ADMIN)`, which requires root privileges in the initial user namespace. Furthermore, Btrfs does not have the `FS_USERNS_MOUNT` flag, meaning unprivileged users cannot mount a Btrfs image even within a user namespace.
* **Filesystem / Peripheral Trigger:** Yes. A malicious Btrfs image on a USB drive could potentially trigger this if the desktop environment auto-mounts it and the filesystem resumes a paused balance operation upon mount. However, as established, the impact is benign (a kernel warning and the USB drive becoming read-only).
In conclusion, this is a functional bug in Btrfs space reservation during balance operations, but it does not pose a security boundary violation or a system-wide denial-of-service threat.
|
| 6/2 |
2026/05/25 08:56 |
llm |
expert |
0m
Model:
Tokens:
input: 19306
output: 17
thoughts: 792
Thoughts: **Analyzing a Btrfs ENOSPC Crash from syzkaller**
Okay, so I've got this syzkaller bug report for the Linux kernel, and the key thing is a `WARNING` in `fs/btrfs/transaction.c` at line 2045, specifically within `cleanup_transaction`. The error itself is a `BTRFS: Transaction aborted (error -28)`, which translates to ENOSPC β "No space left on device." That's the core issue.
Now, let's trace through this call stack, starting from the point of failure: it looks like `btrfs_commit_transaction` is the entry point to the trace and the crash occurs while executing `prepare_to_relocate`, during a `btrfs_balance` operation. That's immediately interesting, because balancing implies attempting to reorganize data to optimize disk space, which makes the ENOSPC error more understandable, if not entirely expected at the outset. I am seeing the program executes `do_nonremap_reloc`, `btrfs_relocate_block_group`, `btrfs_relocate_chunk`, `__btrfs_balance` and finally the `btrfs_balance` ioctl function.
The syzkaller program mounts a btrfs image and then, wait, it *also* mounts an ext4 image? That seems unusual for a Btrfs balance scenario, but syzkaller can be quirky. It then fires off some ioctls. The crash is happening within `btrfs_ioctl_balance`, so whatever the ext4 volume is doing is probably not directly causal.
Hang on... I need to make sure I understand the ioctl being called. I see a reference to `ioctl$FITRIM`, but that's a red herring. Let's make sure that's `0xc0185879` - I'm thinking, `FITRIM` is not what we're after, we need to confirm `BTRFS_IOC_BALANCE`, which is a different code. Yes, I'm pretty sure `0xc4009420` is `BTRFS_IOC_BALANCE`. Let's confirm that. Right, `BTRFS_IOC_BALANCE` is defined using the standard `_IOWR` macro, with `BTRFS_IOCTL_MAGIC` (0x94) and `32` (0x20) as the arguments, confirming my thoughts. The `ioctl` number is based on the size of the structure. I'm recalling now, the `struct btrfs_ioctl_balance_args` is indeed 1024 bytes large, or 0x400. That means it is the balance ioctl, which makes perfect sense with the error message and call trace. So, we're definitely looking at the `BTRFS_IOC_BALANCE` ioctl.
Now, back to the crash point. I should probably have a look at `fs/btrfs/transaction.c:2045` now, specifically in the `cleanup_transaction` function and understand what might trigger the no-space condition here during a balancing operation.
|
| 7/2 |
2026/05/25 08:57 |
tool |
grepper |
0m
Args: map[Expression:cleanup_transaction]
Results: map[Output:drivers/android/binder.c=1686=static void binder_send_failed_reply(struct binder_transaction *t,
--
drivers/android/binder.c-1744-/**
drivers/android/binder.c:1745: * binder_cleanup_transaction() - cleans up undelivered transaction
drivers/android/binder.c-1746- * @t: transaction that needs to be cleaned up
--
drivers/android/binder.c-1749- */
drivers/android/binder.c:1750:static void binder_cleanup_transaction(struct binder_transaction *t,
drivers/android/binder.c-1751- const char *reason,
--
drivers/android/binder.c=4729=static int binder_thread_read(struct binder_proc *proc,
--
drivers/android/binder.c-5074- buffer->transaction = NULL;
drivers/android/binder.c:5075: binder_cleanup_transaction(t, "fd fixups failed",
drivers/android/binder.c-5076- BR_FAILED_REPLY);
--
drivers/android/binder.c-5109-
drivers/android/binder.c:5110: binder_cleanup_transaction(t, "put_user failed",
drivers/android/binder.c-5111- BR_FAILED_REPLY);
--
drivers/android/binder.c-5119-
drivers/android/binder.c:5120: binder_cleanup_transaction(t, "copy_to_user failed",
drivers/android/binder.c-5121- BR_FAILED_REPLY);
--
drivers/android/binder.c=5177=static void binder_release_work(struct binder_proc *proc,
--
drivers/android/binder.c-5196-
drivers/android/binder.c:5197: binder_cleanup_transaction(t, "process died.",
drivers/android/binder.c-5198- BR_DEAD_REPLY);
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c=35=static void qlcnic_sriov_vf_cancel_fw_work(struct qlcnic_adapter *);
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c:36:static void qlcnic_sriov_cleanup_transaction(struct qlcnic_bc_trans *);
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-37-static int qlcnic_sriov_issue_cmd(struct qlcnic_adapter *,
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c=239=void qlcnic_sriov_cleanup_list(struct qlcnic_trans_list *t_list)
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-254- qlcnic_free_mbx_args(&cmd);
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c:255: qlcnic_sriov_cleanup_transaction(trans);
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-256- }
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c=753=static int qlcnic_sriov_prepare_bc_hdr(struct qlcnic_bc_trans *trans,
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-816-
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c:817:static void qlcnic_sriov_cleanup_transaction(struct qlcnic_bc_trans *trans)
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-818-{
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c=1051=static void qlcnic_sriov_process_bc_cmd(struct work_struct *work)
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1081- req = qlcnic_sriov_clear_trans(vf, trans, QLC_BC_RESPONSE);
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c:1082: qlcnic_sriov_cleanup_transaction(trans);
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1083- if (req)
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c=1148=static void qlcnic_sriov_handle_pending_trans(struct qlcnic_sriov *sriov,
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1186- if (qlcnic_sriov_add_act_list(sriov, vf, trans))
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c:1187: qlcnic_sriov_cleanup_transaction(trans);
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1188-
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c=1192=static void qlcnic_sriov_handle_bc_cmd(struct qlcnic_sriov *sriov,
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1226- if (err) {
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c:1227: qlcnic_sriov_cleanup_transaction(trans);
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1228- return;
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1234- qlcnic_free_mbx_args(&cmd);
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c:1235: qlcnic_sriov_cleanup_transaction(trans);
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1236- return;
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1255- qlcnic_free_mbx_args(&cmd);
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c:1256: qlcnic_sriov_cleanup_transaction(trans);
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1257- }
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c=1374=static int __qlcnic_sriov_issue_cmd(struct qlcnic_adapter *adapter,
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1391- if (rsp)
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c:1392: goto cleanup_transaction;
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1393-
--
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1445-
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c:1446:cleanup_transaction:
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c:1447: qlcnic_sriov_cleanup_transaction(trans);
drivers/net/ethernet/qlogic/qlcnic/qlcnic_sriov_common.c-1448-
--
fs/btrfs/disk-io.c-61-
fs/btrfs/disk-io.c:62:static int btrfs_cleanup_transaction(struct btrfs_fs_info *fs_info);
fs/btrfs/disk-io.c-63-static void btrfs_error_commit_super(struct btrfs_fs_info *fs_info);
--
fs/btrfs/disk-io.c=1501=static int transaction_kthread(void *arg)
--
fs/btrfs/disk-io.c-1553- if (BTRFS_FS_ERROR(fs_info))
fs/btrfs/disk-io.c:1554: btrfs_cleanup_transaction(fs_info);
fs/btrfs/disk-io.c-1555- if (!kthread_should_stop() &&
--
fs/btrfs/disk-io.c=3283=int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_devices)
--
fs/btrfs/disk-io.c-3684- kthread_stop(fs_info->transaction_kthread);
fs/btrfs/disk-io.c:3685: btrfs_cleanup_transaction(fs_info);
fs/btrfs/disk-io.c-3686- btrfs_free_fs_roots(fs_info);
--
fs/btrfs/disk-io.c=4523=static void btrfs_error_commit_super(struct btrfs_fs_info *fs_info)
--
fs/btrfs/disk-io.c-4525- /* cleanup FS via transaction */
fs/btrfs/disk-io.c:4526: btrfs_cleanup_transaction(fs_info);
fs/btrfs/disk-io.c-4527-
--
fs/btrfs/disk-io.c=4812=void btrfs_cleanup_one_transaction(struct btrfs_transaction *cur_trans)
--
fs/btrfs/disk-io.c-4841-
fs/btrfs/disk-io.c:4842:static int btrfs_cleanup_transaction(struct btrfs_fs_info *fs_info)
fs/btrfs/disk-io.c-4843-{
--
fs/btrfs/extent-tree.c=2172=int btrfs_run_delayed_refs(struct btrfs_trans_handle *trans, u64 min_bytes)
--
fs/btrfs/extent-tree.c-2177-
fs/btrfs/extent-tree.c:2178: /* We'll clean this up in btrfs_cleanup_transaction */
fs/btrfs/extent-tree.c-2179- if (TRANS_ABORTED(trans))
--
fs/btrfs/transaction.c=2024=int btrfs_commit_current_transaction(struct btrfs_root *root)
--
fs/btrfs/transaction.c-2037-
fs/btrfs/transaction.c:2038:static void cleanup_transaction(struct btrfs_trans_handle *trans, int err)
fs/btrfs/transaction.c-2039-{
--
fs/btrfs/transaction.c=2206=int btrfs_commit_transaction(struct btrfs_trans_handle *trans)
--
fs/btrfs/transaction.c-2369- btrfs_lockdep_release(fs_info, btrfs_trans_num_writers);
fs/btrfs/transaction.c:2370: goto cleanup_transaction;
fs/btrfs/transaction.c-2371- }
--
fs/btrfs/transaction.c-2621- btrfs_scrub_continue(fs_info);
fs/btrfs/transaction.c:2622:cleanup_transaction:
fs/btrfs/transaction.c-2623- btrfs_trans_release_metadata(trans);
--
fs/btrfs/transaction.c-2629- current->journal_info = NULL;
fs/btrfs/transaction.c:2630: cleanup_transaction(trans, ret);
fs/btrfs/transaction.c-2631-
--
fs/btrfs/transaction.c-2636- btrfs_lockdep_release(fs_info, btrfs_trans_num_writers);
fs/btrfs/transaction.c:2637: goto cleanup_transaction;
fs/btrfs/transaction.c-2638-
--
fs/btrfs/volumes.c=3563=int btrfs_relocate_chunk(struct btrfs_fs_info *fs_info, u64 chunk_offset, bool verbose)
--
fs/btrfs/volumes.c-3594- * If we had a transaction abort, stop all running scrubs.
fs/btrfs/volumes.c:3595: * See transaction.c:cleanup_transaction() why we do it here.
fs/btrfs/volumes.c-3596- */
]
|
| 8/2 |
2026/05/25 08:57 |
llm |
expert |
0m
Model:
Tokens:
input: 24370
output: 41
thoughts: 51
Thoughts: Here's my analysis of the situation:
**Investigating a Btrfs Transaction Cleanup Warning**
Okay, so I've got a warning related to transaction cleanup. The breadcrumb trail leads me to `cleanup_transaction` in the Btrfs filesystem code. Specifically, the warning originates from `fs/btrfs/transaction.c`, around line 2045. My next step is clear. I need to dive into that file and scrutinize the code surrounding that specific line. I'll need to understand the context of this function, what triggers the warning condition, and what potential issues it's flagging. Hopefully, I can understand the nature of this issue and come up with a fix.
|
| 9/2 |
2026/05/25 08:57 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/transaction.c IncludeLines:true Name:cleanup_transaction]
Results: map[SourceCode:2038: static void cleanup_transaction(struct btrfs_trans_handle *trans, int err)
2039: {
2040: struct btrfs_fs_info *fs_info = trans->fs_info;
2041: struct btrfs_transaction *cur_trans = trans->transaction;
2042:
2043: WARN_ON(refcount_read(&trans->use_count) > 1);
2044:
2045: btrfs_abort_transaction(trans, err);
2046:
2047: spin_lock(&fs_info->trans_lock);
2048:
2049: /*
2050: * If the transaction is removed from the list, it means this
2051: * transaction has been committed successfully, so it is impossible
2052: * to call the cleanup function.
2053: */
2054: BUG_ON(list_empty(&cur_trans->list));
2055:
2056: if (cur_trans == fs_info->running_transaction) {
2057: cur_trans->state = TRANS_STATE_COMMIT_DOING;
2058: spin_unlock(&fs_info->trans_lock);
2059:
2060: /*
2061: * The thread has already released the lockdep map as reader
2062: * already in btrfs_commit_transaction().
2063: */
2064: btrfs_might_wait_for_event(fs_info, btrfs_trans_num_writers);
2065: wait_event(cur_trans->writer_wait,
2066: atomic_read(&cur_trans->num_writers) == 1);
2067:
2068: spin_lock(&fs_info->trans_lock);
2069: }
2070:
2071: /*
2072: * Now that we know no one else is still using the transaction we can
2073: * remove the transaction from the list of transactions. This avoids
2074: * the transaction kthread from cleaning up the transaction while some
2075: * other task is still using it, which could result in a use-after-free
2076: * on things like log trees, as it forces the transaction kthread to
2077: * wait for this transaction to be cleaned up by us.
2078: */
2079: list_del_init(&cur_trans->list);
2080:
2081: spin_unlock(&fs_info->trans_lock);
2082:
2083: btrfs_cleanup_one_transaction(trans->transaction);
2084:
2085: spin_lock(&fs_info->trans_lock);
2086: if (cur_trans == fs_info->running_transaction)
2087: fs_info->running_transaction = NULL;
2088: spin_unlock(&fs_info->trans_lock);
2089:
2090: if (trans->type & __TRANS_FREEZABLE)
2091: sb_end_intwrite(fs_info->sb);
2092: btrfs_put_transaction(cur_trans);
2093: btrfs_put_transaction(cur_trans);
2094:
2095: trace_btrfs_transaction_commit(fs_info);
2096:
2097: if (current->journal_info == trans)
2098: current->journal_info = NULL;
2099:
2100: /*
2101: * If relocation is running, we can't cancel scrub because that will
2102: * result in a deadlock. Before relocating a block group, relocation
2103: * pauses scrub, then starts and commits a transaction before unpausing
2104: * scrub. If the transaction commit is being done by the relocation
2105: * task or triggered by another task and the relocation task is waiting
2106: * for the commit, and we end up here due to an error in the commit
2107: * path, then calling btrfs_scrub_cancel() will deadlock, as we are
2108: * asking for scrub to stop while having it asked to be paused higher
2109: * above in relocation code.
2110: */
2111: if (!test_bit(BTRFS_FS_RELOC_RUNNING, &fs_info->flags))
2112: btrfs_scrub_cancel(fs_info);
2113:
2114: kmem_cache_free(btrfs_trans_handle_cachep, trans);
2115: }
SourceFile:fs/btrfs/transaction.c]
|
| 10/2 |
2026/05/25 08:57 |
llm |
expert |
0m
Model:
Tokens:
input: 25909
output: 45
thoughts: 62
Thoughts: **Investigating a Btrfs Transaction Abort**
Okay, so the kernel's throwing a warning β specifically, it's popping up at line 2045: `btrfs_abort_transaction(trans, err);`. Right, that's a key line. My first instinct is to dig into what's actually *happening* at that point. Let's see... `btrfs_abort_transaction`... wait a second, that's a macro, isn't it? Which means it's likely expanding into something with a more descriptive call, potentially with a warning or error log. Okay, I need to expand that macro to see what's really going on under the hood. Time to trace the definition of `btrfs_abort_transaction`. Let's see what that macro does.
|
| 11/2 |
2026/05/25 08:57 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:fs/btrfs/transaction.c IncludeLines:true Name:btrfs_abort_transaction]
Results: map[SourceCode: SourceFile:]
|
| 12/2 |
2026/05/25 08:57 |
llm |
expert |
0m
Model:
Tokens:
input: 26215
output: 23
thoughts: 15
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing the Code Snippet**
Okay, so I'm looking at this chunk of code and it's immediately apparent: *this is a macro*. No need to dive too deep initially. Let's not waste time manually parsing and potentially missing something. My go-to for these situations, especially when dealing with unfamiliar macros, is usually `grepper`. I'll fire that up and see if I can't quickly trace the expansion and what calls this macro is making.
|
| 13/2 |
2026/05/25 08:57 |
tool |
grepper |
0m
Args: map[Expression:#define btrfs_abort_transaction]
Results: map[Output:fs/btrfs/transaction.h=231=static inline bool btrfs_abort_should_print_stack(int error)
--
fs/btrfs/transaction.h-245- */
fs/btrfs/transaction.h:246:#define btrfs_abort_transaction(trans, error) \
fs/btrfs/transaction.h-247-do { \
]
|
| 14/2 |
2026/05/25 08:57 |
llm |
expert |
0m
Model:
Tokens:
input: 26484
output: 45
thoughts: 28
Thoughts: Here's my take on those thoughts, tailored for an expert audience:
**Diving into `btrfs_abort_transaction`**
Okay, time to get my hands dirty. I'm thinking I need to refresh my memory on the specifics of `btrfs_abort_transaction`. Let's pull up the definition. I need to understand exactly how this function works internally. I know it's a critical component in the btrfs transaction management system, but I need to quickly run through the details again to ensure a solid grasp on its capabilities and potential implications. Specifically, I'm interested in how it interacts with the commit process, how it handles rollback, and any potential race conditions I should be mindful of, given my current focus. I expect to find some insights into the underlying mechanisms that govern transaction atomicity and how it ensures data integrity during failure scenarios.
|
| 15/2 |
2026/05/25 08:57 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:fs/btrfs/transaction.h IncludeLines:true Name:btrfs_abort_transaction]
Results: map[SourceCode: SourceFile:]
|
| 16/2 |
2026/05/25 08:57 |
llm |
expert |
0m
Model:
Tokens:
input: 26761
output: 23
thoughts: 22
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into Transaction Handling in Btrfs**
Okay, let's pull up `fs/btrfs/transaction.h` and zero in around line 246. I'm thinking we need to refresh our memory on the guts of how Btrfs manages transactions. Specifically, I'm interested in the interplay between⦠I suspect it's probably the context of how the transaction ID is managed relative to committing various blocks. I want to look at how the transaction is managed with the data being committed, and the metadata too. Line 246 should have the key structures or macros related to tracking the transaction state and possibly the ordering of operations. Hopefully, it shows some of the invariants that are maintained and how the journal interacts with the core filesystem components. I'm aiming to get a handle on how concurrent operations are synchronized and how the rollback mechanism works in practice by looking at how the state information is stored and propagated. It's time to dig in and see what the code's got.
|
| 17/2 |
2026/05/25 08:57 |
tool |
grepper |
0m
Args: map[Expression:#define btrfs_abort_transaction]
Results: map[Output:fs/btrfs/transaction.h=231=static inline bool btrfs_abort_should_print_stack(int error)
--
fs/btrfs/transaction.h-245- */
fs/btrfs/transaction.h:246:#define btrfs_abort_transaction(trans, error) \
fs/btrfs/transaction.h-247-do { \
]
|
| 18/2 |
2026/05/25 08:57 |
llm |
expert |
0m
Model:
Tokens:
input: 27148
output: 24
thoughts: 25
Thoughts: Okay, let's dive into this. Here's my take on those lines from `fs/btrfs/transaction.h`:
**Analyzing Btrfs Transaction Management - Diving into the Code**
Alright, I'm heading over to `fs/btrfs/transaction.h` and focusing on lines 240-260. My primary goal here is to get a solid grasp of how Btrfs is managing its transactions at a low level. I need to refresh my understanding of the fundamental building blocks of its atomicity guarantees. I'm assuming that these lines probably deal with the core data structures used to track the operations within a given transaction. Perhaps we are looking at something like a per-transaction context, a list of modified blocks, or a mechanism for ensuring that changes are either fully committed or rolled back in case of an error. I'm expecting to see definitions for the data structures involved in tracking file system changes, maybe something related to the commit log, or maybe the mechanism to ensure consistent snapshots. I'm going to pay close attention to the memory layout and pointer relationships. The way they handle concurrency and locking around these structures is crucial too. I'll be keeping an eye out for any specific optimization techniques related to transaction isolation, as those are usually critical for performance and scalability. I'm ready to start reading, but these are my initial expectations.
|
| 19/2 |
2026/05/25 08:57 |
tool |
read-file |
0m
Args: map[File:fs/btrfs/transaction.h]
Results: map[Contents:/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2007 Oracle. All rights reserved.
*/
#ifndef BTRFS_TRANSACTION_H
#define BTRFS_TRANSACTION_H
#include <linux/atomic.h>
#include <linux/refcount.h>
#include <linux/list.h>
#include <linux/time64.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include "btrfs_inode.h"
#include "delayed-ref.h"
struct dentry;
struct inode;
struct btrfs_pending_snapshot;
struct btrfs_fs_info;
struct btrfs_root_item;
struct btrfs_root;
struct btrfs_path;
/*
* Signal that a direct IO write is in progress, to avoid deadlock for sync
* direct IO writes when fsync is called during the direct IO write path.
*/
#define BTRFS_TRANS_DIO_WRITE_STUB ((void *) 1)
/* Radix-tree tag for roots that are part of the transaction. */
#define BTRFS_ROOT_TRANS_TAG 0
enum btrfs_trans_state {
TRANS_STATE_RUNNING,
TRANS_STATE_COMMIT_PREP,
TRANS_STATE_COMMIT_START,
TRANS_STATE_COMMIT_DOING,
TRANS_STATE_UNBLOCKED,
TRANS_STATE_SUPER_COMMITTED,
TRANS_STATE_COMPLETED,
TRANS_STATE_MAX,
};
#define BTRFS_TRANS_HAVE_FREE_BGS 0
#define BTRFS_TRANS_DIRTY_BG_RUN 1
#define BTRFS_TRANS_CACHE_ENOSPC 2
struct btrfs_transaction {
u64 transid;
/*
* total external writers(USERSPACE/START/ATTACH) in this
* transaction, it must be zero before the transaction is
* being committed
*/
atomic_t num_extwriters;
/*
* total writers in this transaction, it must be zero before the
* transaction can end
*/
atomic_t num_writers;
refcount_t use_count;
unsigned long flags;
/* Be protected by fs_info->trans_lock when we want to change it. */
enum btrfs_trans_state state;
int aborted;
struct list_head list;
struct extent_io_tree dirty_pages;
time64_t start_time;
wait_queue_head_t writer_wait;
wait_queue_head_t commit_wait;
struct list_head pending_snapshots;
struct list_head dev_update_list;
struct list_head switch_commits;
struct list_head dirty_bgs;
/*
* There is no explicit lock which protects io_bgs, rather its
* consistency is implied by the fact that all the sites which modify
* it do so under some form of transaction critical section, namely:
*
* - btrfs_start_dirty_block_groups - This function can only ever be
* run by one of the transaction committers. Refer to
* BTRFS_TRANS_DIRTY_BG_RUN usage in btrfs_commit_transaction
*
* - btrfs_write_dirty_blockgroups - this is called by
* commit_cowonly_roots from transaction critical section
* (TRANS_STATE_COMMIT_DOING)
*
* - btrfs_cleanup_dirty_bgs - called on transaction abort
*/
struct list_head io_bgs;
struct list_head dropped_roots;
struct extent_io_tree pinned_extents;
/*
* we need to make sure block group deletion doesn't race with
* free space cache writeout. This mutex keeps them from stomping
* on each other
*/
struct mutex cache_write_mutex;
spinlock_t dirty_bgs_lock;
/* Protected by spin lock fs_info->unused_bgs_lock. */
struct list_head deleted_bgs;
spinlock_t dropped_roots_lock;
struct btrfs_delayed_ref_root delayed_refs;
struct btrfs_fs_info *fs_info;
/*
* Number of ordered extents the transaction must wait for before
* committing. These are ordered extents started by a fast fsync.
*/
atomic_t pending_ordered;
wait_queue_head_t pending_wait;
};
enum {
ENUM_BIT(__TRANS_FREEZABLE),
ENUM_BIT(__TRANS_START),
ENUM_BIT(__TRANS_ATTACH),
ENUM_BIT(__TRANS_JOIN),
ENUM_BIT(__TRANS_JOIN_NOLOCK),
ENUM_BIT(__TRANS_DUMMY),
ENUM_BIT(__TRANS_JOIN_NOSTART),
};
#define TRANS_START (__TRANS_START | __TRANS_FREEZABLE)
#define TRANS_ATTACH (__TRANS_ATTACH)
#define TRANS_JOIN (__TRANS_JOIN | __TRANS_FREEZABLE)
#define TRANS_JOIN_NOLOCK (__TRANS_JOIN_NOLOCK)
#define TRANS_JOIN_NOSTART (__TRANS_JOIN_NOSTART)
#define TRANS_EXTWRITERS (__TRANS_START | __TRANS_ATTACH)
struct btrfs_trans_handle {
u64 transid;
u64 bytes_reserved;
u64 delayed_refs_bytes_reserved;
u64 chunk_bytes_reserved;
unsigned long delayed_ref_updates;
unsigned long delayed_ref_csum_deletions;
struct btrfs_transaction *transaction;
struct btrfs_block_rsv *block_rsv;
struct btrfs_block_rsv *orig_rsv;
/* Set by a task that wants to create a snapshot. */
struct btrfs_pending_snapshot *pending_snapshot;
refcount_t use_count;
unsigned int type;
/*
* Error code of transaction abort, set outside of locks and must use
* the READ_ONCE/WRITE_ONCE access
*/
short aborted;
bool adding_csums;
bool allocating_chunk;
bool removing_chunk;
bool reloc_reserved;
bool in_fsync;
struct btrfs_fs_info *fs_info;
struct list_head new_bgs;
struct btrfs_block_rsv delayed_rsv;
};
/*
* The abort status can be changed between calls and is not protected by locks.
* This accepts btrfs_transaction and btrfs_trans_handle as types. Once it's
* set to a non-zero value it does not change, so the macro should be in checks
* but is not necessary for further reads of the value.
*/
#define TRANS_ABORTED(trans) (unlikely(READ_ONCE((trans)->aborted)))
struct btrfs_pending_snapshot {
struct dentry *dentry;
struct btrfs_inode *dir;
struct btrfs_root *root;
struct btrfs_root_item *root_item;
struct btrfs_root *snap;
struct btrfs_qgroup_inherit *inherit;
struct btrfs_path *path;
/* block reservation for the operation */
struct btrfs_block_rsv block_rsv;
/* extra metadata reservation for relocation */
int error;
/* Preallocated anonymous block device number */
dev_t anon_dev;
bool readonly;
struct list_head list;
};
static inline void btrfs_set_inode_last_trans(struct btrfs_trans_handle *trans,
struct btrfs_inode *inode)
{
spin_lock(&inode->lock);
inode->last_trans = trans->transaction->transid;
inode->last_sub_trans = btrfs_get_root_log_transid(inode->root);
inode->last_log_commit = inode->last_sub_trans - 1;
spin_unlock(&inode->lock);
}
/*
* Make qgroup codes to skip given qgroupid, means the old/new_roots for
* qgroup won't contain the qgroupid in it.
*/
static inline void btrfs_set_skip_qgroup(struct btrfs_trans_handle *trans,
u64 qgroupid)
{
struct btrfs_delayed_ref_root *delayed_refs;
delayed_refs = &trans->transaction->delayed_refs;
WARN_ON(delayed_refs->qgroup_to_skip);
delayed_refs->qgroup_to_skip = qgroupid;
}
static inline void btrfs_clear_skip_qgroup(struct btrfs_trans_handle *trans)
{
struct btrfs_delayed_ref_root *delayed_refs;
delayed_refs = &trans->transaction->delayed_refs;
WARN_ON(!delayed_refs->qgroup_to_skip);
delayed_refs->qgroup_to_skip = 0;
}
/*
* We want the transaction abort to print stack trace only for errors where the
* cause could be a bug, eg. due to ENOSPC, and not for common errors that are
* caused by external factors.
*/
static inline bool btrfs_abort_should_print_stack(int error)
{
switch (error) {
case -EIO:
case -EROFS:
case -ENOMEM:
return false;
}
return true;
}
/*
* Call btrfs_abort_transaction as early as possible when an error condition is
* detected, that way the exact stack trace is reported for some errors.
*/
#define btrfs_abort_transaction(trans, error) \
do { \
bool __first = false; \
/* Report first abort since mount */ \
if (!test_and_set_bit(BTRFS_FS_STATE_TRANS_ABORTED, \
&((trans)->fs_info->fs_state))) { \
__first = true; \
if (WARN(btrfs_abort_should_print_stack(error), \
KERN_ERR \
"BTRFS: Transaction aborted (error %d)\n", \
(error))) { \
/* Stack trace printed. */ \
} else { \
btrfs_err((trans)->fs_info, \
"Transaction aborted (error %d)", \
(error)); \
} \
} \
__btrfs_abort_transaction((trans), __func__, \
__LINE__, (error), __first); \
} while (0)
int btrfs_end_transaction(struct btrfs_trans_handle *trans);
struct btrfs_trans_handle *btrfs_start_transaction(struct btrfs_root *root,
unsigned int num_items);
struct btrfs_trans_handle *btrfs_start_transaction_fallback_global_rsv(
struct btrfs_root *root,
unsigned int num_items);
struct btrfs_trans_handle *btrfs_join_transaction(struct btrfs_root *root);
struct btrfs_trans_handle *btrfs_join_transaction_spacecache(struct btrfs_root *root);
struct btrfs_trans_handle *btrfs_join_transaction_nostart(struct btrfs_root *root);
struct btrfs_trans_handle *btrfs_attach_transaction(struct btrfs_root *root);
struct btrfs_trans_handle *btrfs_attach_transaction_barrier(
struct btrfs_root *root);
int btrfs_wait_for_commit(struct btrfs_fs_info *fs_info, u64 transid);
void btrfs_add_dead_root(struct btrfs_root *root);
void btrfs_maybe_wake_unfinished_drop(struct btrfs_fs_info *fs_info);
int btrfs_clean_one_deleted_snapshot(struct btrfs_fs_info *fs_info);
int btrfs_commit_transaction(struct btrfs_trans_handle *trans);
void btrfs_commit_transaction_async(struct btrfs_trans_handle *trans);
int btrfs_commit_current_transaction(struct btrfs_root *root);
int btrfs_end_transaction_throttle(struct btrfs_trans_handle *trans);
bool btrfs_should_end_transaction(struct btrfs_trans_handle *trans);
void btrfs_throttle(struct btrfs_fs_info *fs_info);
int btrfs_record_root_in_trans(struct btrfs_trans_handle *trans,
struct btrfs_root *root);
int btrfs_write_marked_extents(struct btrfs_fs_info *fs_info,
struct extent_io_tree *dirty_pages, int mark);
int btrfs_wait_tree_log_extents(struct btrfs_root *root, int mark);
int btrfs_transaction_blocked(struct btrfs_fs_info *info);
void btrfs_put_transaction(struct btrfs_transaction *transaction);
void btrfs_add_dropped_root(struct btrfs_trans_handle *trans,
struct btrfs_root *root);
void btrfs_trans_release_chunk_metadata(struct btrfs_trans_handle *trans);
void __cold __btrfs_abort_transaction(struct btrfs_trans_handle *trans,
const char *function,
unsigned int line, int error, bool first_hit);
int __init btrfs_transaction_init(void);
void __cold btrfs_transaction_exit(void);
#endif
]
|
| 20/2 |
2026/05/25 08:57 |
llm |
expert |
0m
Model:
Tokens:
input: 30906
output: 21
thoughts: 780
Thoughts: **Analyzing a Btrfs Transaction Abort Due to ENOSPC**
Okay, so I'm looking at this `btrfs_abort_transaction` function and it's clear it's specifically designed to handle transaction aborts, and I see that if `test_and_set_bit` indicates the transaction hasn't been aborted yet, it triggers a `WARN` if `btrfs_abort_should_print_stack` returns true. In this case, the `btrfs_abort_should_print_stack` check is based on the error code. I'm seeing that the error code is ENOSPC (-28), and the function *does* print a stack trace for that! The bug report references a specific line in `transaction.c` β a transaction abort caused by `ENOSPC`.
My initial thought is, isn't `ENOSPC` just a normal condition, a full disk? But then I remember that `btrfs_abort_should_print_stack` specifically calls out ENOSPC as a potential bug! The comment says it all: it's a bug *because* btrfs should have reserved space for the transaction, and running out of space *during* means the reservation failed. This suggests a potential flaw in how btrfs manages its space allocation, particularly during transactions.
So, let's look at the exploitability. It's a `WARNING`, not a `BUG`, so it doesn't immediately indicate memory corruption. The concern is Denial of Service. Aborting a transaction can make the filesystem read-only or, worse, shut it down. But the filesystem is being mounted by the user in a user namespace, or maybe via a loop device. Then I recall the syzkaller reproducer - it mounts the filesystem. If an unprivileged user can mount a crafted BTRFS image and trigger this `WARN`, does it crash the system? Nope, it just prints a warning and aborts the transaction for that specific filesystem.
Now, does a `WARN` actually crash the system? In production builds, `panic_on_warn` is almost always zero, so the `WARN` just prints a stack trace and keeps going. So, it *shouldn't* cause a system crash. The bug report does say "WARNING", confirming this.
So, is it a Denial of Service? A user triggering the `WARN` might spam the kernel log, but that's a minor DoS. The key question now becomes, can an unprivileged user even mount a BTRFS image? Normally, `mount` requires `CAP_SYS_ADMIN` in the initial namespace, but can it be mounted in a user namespace? I'll need to check if btrfs supports `FS_USERNS_MOUNT`.
|
| 21/2 |
2026/05/25 08:57 |
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=2130=static struct file_system_type fuse_fs_type = {
--
fs/fuse/inode.c-2132- .name = "fuse",
fs/fuse/inode.c:2133: .fs_flags = FS_HAS_SUBTYPE | FS_USERNS_MOUNT | FS_ALLOW_IDMAP,
fs/fuse/inode.c-2134- .init_fs_context = fuse_init_fs_context,
--
fs/overlayfs/params.c=760=static const struct fs_context_operations ovl_context_ops = {
--
fs/overlayfs/params.c-769- * This is called during fsopen() and will record the user namespace of
fs/overlayfs/params.c:770: * the caller in fc->user_ns since we've raised FS_USERNS_MOUNT. We'll
fs/overlayfs/params.c-771- * need it when we actually create the superblock to verify that the
--
fs/overlayfs/super.c=1575=struct file_system_type ovl_fs_type = {
--
fs/overlayfs/super.c-1579- .parameters = ovl_parameter_spec,
fs/overlayfs/super.c:1580: .fs_flags = FS_USERNS_MOUNT,
fs/overlayfs/super.c-1581- .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=90=static struct file_system_type sysfs_fs_type = {
--
fs/sysfs/mount.c-93- .kill_sb = sysfs_kill_sb,
fs/sysfs/mount.c:94: .fs_flags = FS_USERNS_MOUNT,
fs/sysfs/mount.c-95-};
--
include/linux/fs.h=2271=struct file_system_type {
--
include/linux/fs.h-2276-#define FS_HAS_SUBTYPE 4
include/linux/fs.h:2277:#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
include/linux/fs.h-2278-#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=2392=struct file_system_type cgroup_fs_type = {
--
kernel/cgroup/cgroup.c-2396- .kill_sb = cgroup_kill_sb,
kernel/cgroup/cgroup.c:2397: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2398-};
--
kernel/cgroup/cgroup.c=2400=static struct file_system_type cgroup2_fs_type = {
--
kernel/cgroup/cgroup.c-2404- .kill_sb = cgroup_kill_sb,
kernel/cgroup/cgroup.c:2405: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2406-};
--
kernel/cgroup/cgroup.c=2473=static struct file_system_type cpuset_fs_type = {
--
kernel/cgroup/cgroup.c-2476- .parameters = cpuset_fs_parameters,
kernel/cgroup/cgroup.c:2477: .fs_flags = FS_USERNS_MOUNT,
kernel/cgroup/cgroup.c-2478-};
--
mm/shmem.c=5351=static struct file_system_type shmem_fs_type = {
--
mm/shmem.c-5358- .kill_sb = kill_anon_super,
mm/shmem.c:5359: .fs_flags = FS_USERNS_MOUNT | FS_ALLOW_IDMAP | FS_MGTIME,
mm/shmem.c-5360-};
--
mm/shmem.c=5761=static struct file_system_type shmem_fs_type = {
--
mm/shmem.c-5765- .kill_sb = ramfs_kill_sb,
mm/shmem.c:5766: .fs_flags = FS_USERNS_MOUNT,
mm/shmem.c-5767-};
]
|
| 22/2 |
2026/05/25 08:57 |
llm |
expert |
0m
Model:
Tokens:
input: 34616
output: 45
thoughts: 258
Thoughts: **My Btrfs Conundrum**
Okay, so Btrfs is being a bit difficult. I know it doesn't support `FS_USERNS_MOUNT`, meaning that, by default, an unprivileged user can't just mount a Btrfs image, even inside a user namespace. That's a key piece of information. But hold on... the Syzkaller reproducer for this bug *is* mounting Btrfs. Huh. I better check the code...
Ah, right, `syz_mount_image`... and that's using the standard mount. Okay, that makes sense. Syzkaller's test environment usually runs with root privileges or with capabilities. So, if a normal user *can't* mount Btrfs, then they can't trivially trigger this vulnerability by, say, mounting a maliciously crafted image. That's good to know, it narrows the attack vector considerably.
But... hang on. What about a pre-existing mount? What if the system administrator has *already* mounted a Btrfs filesystem? Could an unprivileged user potentially trigger the vulnerable `BTRFS_IOC_BALANCE` ioctl in that scenario? That's the real question. It's a key test to check. Time to dive into the permissions for `BTRFS_IOC_BALANCE`. Let's see what the system allows.
|
| 23/2 |
2026/05/25 08:57 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/ioctl.c IncludeLines:true Name:btrfs_ioctl_balance]
Results: map[SourceCode:3372: static long btrfs_ioctl_balance(struct file *file, void __user *arg)
3373: {
3374: struct btrfs_root *root = BTRFS_I(file_inode(file))->root;
3375: struct btrfs_fs_info *fs_info = root->fs_info;
3376: struct btrfs_ioctl_balance_args *bargs;
3377: struct btrfs_balance_control *bctl;
3378: bool need_unlock = true;
3379: int ret;
3380:
3381: if (!capable(CAP_SYS_ADMIN))
3382: return -EPERM;
3383:
3384: ret = mnt_want_write_file(file);
3385: if (ret)
3386: return ret;
3387:
3388: bargs = memdup_user(arg, sizeof(*bargs));
3389: if (IS_ERR(bargs)) {
3390: ret = PTR_ERR(bargs);
3391: bargs = NULL;
3392: goto out;
3393: }
3394:
3395: ret = btrfs_try_lock_balance(fs_info, &need_unlock);
3396: if (ret)
3397: goto out;
3398:
3399: lockdep_assert_held(&fs_info->balance_mutex);
3400:
3401: if (bargs->flags & BTRFS_BALANCE_RESUME) {
3402: if (!fs_info->balance_ctl) {
3403: ret = -ENOTCONN;
3404: goto out_unlock;
3405: }
3406:
3407: bctl = fs_info->balance_ctl;
3408: spin_lock(&fs_info->balance_lock);
3409: bctl->flags |= BTRFS_BALANCE_RESUME;
3410: spin_unlock(&fs_info->balance_lock);
3411: btrfs_exclop_balance(fs_info, BTRFS_EXCLOP_BALANCE);
3412:
3413: goto do_balance;
3414: }
3415:
3416: if (bargs->flags & ~(BTRFS_BALANCE_ARGS_MASK | BTRFS_BALANCE_TYPE_MASK)) {
3417: ret = -EINVAL;
3418: goto out_unlock;
3419: }
3420:
3421: if (fs_info->balance_ctl) {
3422: ret = -EINPROGRESS;
3423: goto out_unlock;
3424: }
3425:
3426: bctl = kzalloc(sizeof(*bctl), GFP_KERNEL);
3427: if (!bctl) {
3428: ret = -ENOMEM;
3429: goto out_unlock;
3430: }
3431:
3432: memcpy(&bctl->data, &bargs->data, sizeof(bctl->data));
3433: memcpy(&bctl->meta, &bargs->meta, sizeof(bctl->meta));
3434: memcpy(&bctl->sys, &bargs->sys, sizeof(bctl->sys));
3435:
3436: bctl->flags = bargs->flags;
3437: do_balance:
3438: /*
3439: * Ownership of bctl and exclusive operation goes to btrfs_balance.
3440: * bctl is freed in reset_balance_state, or, if restriper was paused
3441: * all the way until unmount, in free_fs_info. The flag should be
3442: * cleared after reset_balance_state.
3443: */
3444: need_unlock = false;
3445:
3446: ret = btrfs_balance(fs_info, bctl, bargs);
3447: bctl = NULL;
3448:
3449: if (ret == 0 || ret == -ECANCELED) {
3450: if (copy_to_user(arg, bargs, sizeof(*bargs)))
3451: ret = -EFAULT;
3452: }
3453:
3454: kfree(bctl);
3455: out_unlock:
3456: mutex_unlock(&fs_info->balance_mutex);
3457: if (need_unlock)
3458: btrfs_exclop_finish(fs_info);
3459: out:
3460: mnt_drop_write_file(file);
3461: kfree(bargs);
3462: return ret;
3463: }
SourceFile:fs/btrfs/ioctl.c]
|
| 24/2 |
2026/05/25 08:57 |
llm |
expert |
0m
Model:
Tokens:
input: 36602
output: 44
thoughts: 497
Thoughts: **Analyzing the Btrfs Balance IOCTL Vulnerability**
Okay, let's break this down. My initial thought is that `btrfs_ioctl_balance` requires `CAP_SYS_ADMIN`, as the code clearly states with `if (!capable(CAP_SYS_ADMIN)) return -EPERM;`. That's the first hurdle. But, hold on a second... `capable` checks against the *initial* namespace, not the user namespace, right? I need to double-check that. Yes, I'm right, `capable` checks `init_user_ns`.
So, that means a regular, unprivileged user *shouldn't* be able to trigger `BTRFS_IOC_BALANCE`, even if they're in a user namespace. That's good from a security perspective. But, what if there's a different path? What if the vulnerability lies elsewhere, perhaps triggered via `FITRIM` or another related IOCTL?
Looking at the syzkaller reproducer, it shows a call to `ioctl` with `0xc4009420`, which I confirm, is `BTRFS_IOC_BALANCE`. The reproducer also shows a call to `ioctl` with `0xc0185879`, which is `FS_IOC_REMOVE_ENCRYPTION_KEY`. Hmm, that's interesting. However, the crash trace is conclusive: it's showing `btrfs_ioctl_balance` right in the stack: `btrfs_ioctl_balance+0x3d6/0x610 fs/btrfs/ioctl.c:3446`.
So, the crash *is* directly caused by `btrfs_ioctl_balance`. Given that it requires `CAP_SYS_ADMIN` in the initial namespace, it's virtually impossible for an unprivileged process or user namespace to trigger the balance operation directly. This aligns with my understanding of the permission model.
Just to be absolutely sure, I'll go back and verify. Yep, the `if (!capable(CAP_SYS_ADMIN))` check is right there at the beginning of `btrfs_ioctl_balance`. It's not a case of a missed check further down the function.
Now, the important question: is there any other, indirect way to trigger `btrfs_balance`? I'll need to check the references to `btrfs_balance` to make certain.
|
| 25/2 |
2026/05/25 08:58 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:fs/btrfs/volumes.c IncludeSnippetLines:0 Name:btrfs_balance]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:balance_kthread SourceFile:fs/btrfs/volumes.c SourceLine:4936] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:btrfs_ioctl_balance SourceFile:fs/btrfs/ioctl.c SourceLine:3446]] TruncatedOutput:false]
|
| 26/2 |
2026/05/25 08:58 |
llm |
expert |
0m
Model:
Tokens:
input: 37796
output: 48
thoughts: 59
Thoughts: **Tracing the Balance Flow**
Okay, so I'm looking at how the balancing process kicks off in Btrfs. It seems the main entry points for this are `balance_kthread` and `btrfs_ioctl_balance`. `balance_kthread`, the kernel thread responsible for the balancing operations, is initialized by `btrfs_resume_balance_async`. This is the critical piece to follow. I need to understand when and how this asynchronous resume function is called. Let's dig into the code and investigate the call sites for `btrfs_resume_balance_async` to get a clearer picture of the control flow. Knowing those upstream calls will illuminate the balance lifecycle.
|
| 27/2 |
2026/05/25 08:58 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:fs/btrfs/volumes.c IncludeSnippetLines:0 Name:btrfs_resume_balance_async]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:btrfs_start_pre_rw_mount SourceFile:fs/btrfs/disk-io.c SourceLine:3141]] TruncatedOutput:false]
|
| 28/2 |
2026/05/25 08:58 |
llm |
expert |
0m
Model:
Tokens:
input: 38130
output: 66
thoughts: 2945
Thoughts: **Analysis of the Btrfs Transaction Abort Bug**
Okay, let's break this down. We're looking at a scenario triggered by `btrfs_start_pre_rw_mount`, potentially happening during mount if a balance was paused. However, we know that btrfs, by design, isn't mountable by unprivileged users, even inside user namespaces. So, could a malicious USB drive be the culprit through auto-mounting? Yes, a desktop environment will mount the USB as root, and a paused balance on the btrfs image will resume, triggering this behavior. Or, an auto-mount script that runs `btrfs balance` could cause this.
The issue is a transaction abort with an error code, accompanied by a `WARNING` message. But, does this constitute a Denial of Service? Typically, `WARNING` messages don't crash the system unless `panic_on_warn` is enabled. Here, the transaction aborts, resulting in a read-only or erroring filesystem, just like what happens if a filesystem is full or corrupted. A user plugs in the malicious drive, it auto-mounts, the transaction aborts, and the drive becomes read-only. This doesn't affect the rest of the system. Thus, it's not a system-wide Denial of Service.
Even if the root filesystem were affected, which it isn't here, it still wouldn't be a DoS because the attacker doesn't control the root filesystem. It's the attacker's own USB drive which aborts.
The warning itself just indicates the printing of a stack trace. No memory corruption, no exploitable behavior.
Here are the dimensions:
* **Exploitable:** No. Transaction abort due to ENOSPC (-28), causing a WARN.
* **Denial of Service:** No. The transaction aborts on the *specific* btrfs filesystem, rendering it read-only.
* **Unprivileged:** No. `btrfs_ioctl_balance` requires `CAP_SYS_ADMIN`.
* **User Namespace:** No. `capable(CAP_SYS_ADMIN)` applies to the initial namespace, and btrfs can't be mounted in a user namespace.
* **VM Guest Trigger:** No.
* **VM Host Trigger:** No
* **Network Trigger:** No
* **Remote Trigger:** No
* **Peripheral Trigger:** Yes. A malicious USB drive can trigger it, but this doesn't cause a DoS or exploit, just a warning.
* **Filesystem Trigger:** Yes. Mounting a crafted image does trigger it.
Is this a bug at all? Btrfs developers view this as a bug when a transaction aborts due to ENOSPC. Space reservation should have been done correctly. But, the impact is only a warning and an abort.
I confirmed `btrfs_ioctl_balance` requires `CAP_SYS_ADMIN`, and `btrfs_resume_balance_async` is only called from mount. Mounting a btrfs filesystem is impossible by an unprivileged user. Other triggers, like a call to `btrfs_balance`, is not possible in this case.
I reviewed that the transaction abort doesn't cause a panic. The condition is handled; the transaction aborts. There is also no other consequence of the warning, and it is handled safely.
The syzkaller reproducer focuses on `BTRFS_IOC_BALANCE` via a mounted btrfs image. The ext4 part is likely irrelevant. This can be called on any file in a btrfs filesystem.
So, it's just a btrfs transaction abort due to ENOSPC during balance. This is a known btrfs issue where balances might run out of space. This is a *functional* issue, not a security one.
Let's double-check the stack trace. The relevant lines of code clearly show standard error handling. No memory corruption, and the error code is not exploitable. The `capable(CAP_SYS_ADMIN)` check is the only check.
|
| 29/2 |
2026/05/25 08:58 |
tool |
set-results |
0m
Args: map[DenialOfService:false Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 30/2 |
2026/05/25 08:58 |
llm |
expert |
0m
Model:
Tokens:
input: 42080
output: 506
thoughts: 276
Thoughts: **Final Thoughts on the Analysis**
Okay, the analysis is complete, time to formulate the definitive response. Let me summarize my findings.
The core issue boils down to a `WARNING` in the `cleanup_transaction` function. This is triggered by a transaction abort, specifically due to an `ENOSPC` error (-28) while a `btrfs balance` operation is running. I immediately recognize this as the typical behavior of Btrfs when it encounters space exhaustion during a transaction. The use of `WARN` to print a stack trace is standard in these circumstances because space exhaustion *during* a transaction usually indicates a flaw in space reservation.
However, after a thorough review, the system correctly handles the condition. The transaction is properly aborted, and the file system goes into read-only mode. Importantly, there's *no* indication of memory corruption, system crashes, or any exploitable condition that would allow for privilege escalation or remote code execution. Thatβs a significant positive.
Furthermore, the `btrfs_ioctl_balance` ioctl, which triggers the balance operation, requires `CAP_SYS_ADMIN` in the initial user namespace. This means that unprivileged users *cannot* trigger this scenario, nor can it be initiated from within a user namespace. Additionally, the lack of `FS_USERNS_MOUNT` support prevents unprivileged users from mounting a crafted Btrfs image.
The only remaining potential vector is an auto-mounted crafted image (perhaps from a USB drive) that resumes a paused balance. However, even in this case, the impact is limited to a warning message and the file system becoming read-only. This doesn't present a system-wide Denial of Service vulnerability.
|
| 31/1 |
2026/05/25 08:58 |
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:
**Bug Analysis:**
The bug is a `WARNING` triggered in `cleanup_transaction` (in
`fs/btrfs/transaction.c`) due to a transaction abort with error `-28` (`ENOSPC`
- No space left on device).
In Btrfs, running out of space *during* a transaction is considered a bug in the
space reservation logic (space should have been reserved before the transaction
started). To help debug such issues, Btrfs uses a `WARN` macro to print a stack
trace when a transaction is aborted due to `ENOSPC`.
**Security Impact:**
* **Exploitable:** No. The condition is properly handled by the Btrfs error
handling paths. The transaction is aborted, and the filesystem is turned
read-only. There is no memory corruption, out-of-bounds access, or
use-after-free involved.
* **Denial Of Service:** No. The `WARNING` does not halt the kernel in
production builds (unless `panic_on_warn` is explicitly set). The transaction
abort only affects the specific Btrfs filesystem that ran out of space, making
it read-only. It does not crash the system, cause deadlocks, or affect other
filesystems.
* **Accessible From Unprivileged Processes / User Namespaces:** No. The crash is
triggered via the `BTRFS_IOC_BALANCE` ioctl. In `fs/btrfs/ioctl.c`,
`btrfs_ioctl_balance()` explicitly checks for `capable(CAP_SYS_ADMIN)`, which
requires root privileges in the initial user namespace. Furthermore, Btrfs does
not have the `FS_USERNS_MOUNT` flag, meaning unprivileged users cannot mount a
Btrfs image even within a user namespace.
* **Filesystem / Peripheral Trigger:** Yes. A malicious Btrfs image on a USB
drive could potentially trigger this if the desktop environment auto-mounts it
and the filesystem resumes a paused balance operation upon mount. However, as
established, the impact is benign (a kernel warning and the USB drive becoming
read-only).
In conclusion, this is a functional bug in Btrfs space reservation during
balance operations, but it does not pose a security boundary violation or a
system-wide denial-of-service threat.]
|