[<prev] [next>] [day] [month] [year] [list]
Message-ID: <CACVn-YmCOUQVQd1FYZhcMSQAK5gsvzA7+k5oiocN23nKRMKBMA@mail.gmail.com>
Date: Thu, 25 Dec 2025 19:12:58 +0800
From: yuhang hang <yangyuhang0619@...il.com>
To: "paul@...l-moore.com" <paul@...l-moore.com>, "jmorris@...ei.org" <jmorris@...ei.org>
Cc: "serge@...lyn.com" <serge@...lyn.com>,
"linux-security-module@...r.kernel.org" <linux-security-module@...r.kernel.org>,
"linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>,
"syzkaller-bugs@...glegroups.com" <syzkaller-bugs@...glegroups.com>
Subject: [BUG REPORT] memory leak in prepare creds triggered by
Netlink/fremovexattr (v6.12.62 , found C repro for Invalid bugs)
Hello,
I am writing to report a memory leak issue I encountered in
`prepare_creds()` while fuzzing Linux kernel v6.12.62.
I have successfully isolated the issue and created a reliable C
reproducer (included below).
=== Analysis & Triage ===
I noticed several existing reports on the Syzbot dashboard regarding
leaks in `prepare_creds`. However, after careful analysis, I believe
this is a distinct issue:
1. Different Trigger Path: Unlike the known issues(e.g., variants
#1、#2 and #5) related to `io_uring`, `bpf`, or `nfsd`, this leak is
triggered specifically by a combination of Netlink sockets and
`fremovexattr()`.
2. Addressing 'Invalid' Reports: I found similar reports in the
'Invalid' list (e.g., variants #3 and #4) that were closed due to the
lack of a reproducer. My C reproducer might be the missing piece to
resolve those cases.
=== Environment Details ===
- Kernel Version: v6.12.62 (Stable)
- Git Tree: upstream stable
- Architecture: x86_64
- Kernel Config: Attached as 'config.txt'
=== Console Output ===
==================================================================
BUG: memory leak
unreferenced object 0xffff888023928000 (size 184):
comm "syz-executor", pid 10631, jiffies 4294970296
hex dump (first 32 bytes):
01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
backtrace (crc 1adddbfd):
kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline]
slab_post_alloc_hook mm/slub.c:4152 [inline]
slab_alloc_node mm/slub.c:4197 [inline]
kmem_cache_alloc_noprof+0x29a/0x320 mm/slub.c:4204
prepare_creds+0x2e/0x760 kernel/cred.c:212
copy_creds+0xa7/0xa50 kernel/cred.c:312
copy_process+0xf7d/0x8b20 kernel/fork.c:2262
kernel_clone+0xeb/0x900 kernel/fork.c:2810
__do_sys_clone+0xcf/0x120 kernel/fork.c:2953
do_syscall_x64 arch/x86/entry/common.c:52 [inline]
do_syscall_64+0xcb/0x220 arch/x86/entry/common.c:83
entry_SYSCALL_64_after_hwframe+0x77/0x7f
BUG: memory leak
unreferenced object 0xffff88802067d920 (size 16):
comm "syz-executor", pid 10631, jiffies 4294970296
hex dump (first 16 bytes):
00 00 00 00 00 00 00 00 00 3d 08 1b 80 88 ff ff .........=......
backtrace (crc 8e8e0e90):
kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline]
slab_post_alloc_hook mm/slub.c:4152 [inline]
slab_alloc_node mm/slub.c:4197 [inline]
__do_kmalloc_node mm/slub.c:4331 [inline]
__kmalloc_noprof+0x331/0x460 mm/slub.c:4344
kmalloc_noprof include/linux/slab.h:882 [inline]
kzalloc_noprof include/linux/slab.h:1014 [inline]
lsm_blob_alloc security/security.c:685 [inline]
lsm_blob_alloc security/security.c:678 [inline]
lsm_cred_alloc security/security.c:702 [inline]
security_prepare_creds+0x294/0x320 security/security.c:3240
prepare_creds+0x54e/0x760 kernel/cred.c:242
copy_creds+0xa7/0xa50 kernel/cred.c:312
copy_process+0xf7d/0x8b20 kernel/fork.c:2262
kernel_clone+0xeb/0x900 kernel/fork.c:2810
__do_sys_clone+0xcf/0x120 kernel/fork.c:2953
do_syscall_x64 arch/x86/entry/common.c:52 [inline]
do_syscall_64+0xcb/0x220 arch/x86/entry/common.c:83
entry_SYSCALL_64_after_hwframe+0x77/0x7f
BUG: memory leak
unreferenced object 0xffff88805c661800 (size 1408):
comm "syz.0.17", pid 10940, jiffies 4294970299
hex dump (first 32 bytes):
01 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 ................
c0 9d 65 44 80 88 ff ff 00 a0 b7 12 80 88 ff ff ..eD............
backtrace (crc 3568c0e3):
kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline]
slab_post_alloc_hook mm/slub.c:4152 [inline]
slab_alloc_node mm/slub.c:4197 [inline]
kmem_cache_alloc_lru_noprof+0x298/0x310 mm/slub.c:4216
sock_alloc_inode+0x27/0x1d0 net/socket.c:307
alloc_inode+0x61/0x240 fs/inode.c:265
sock_alloc+0x40/0x270 net/socket.c:633
__sock_create+0xc1/0x880 net/socket.c:1540
sock_create net/socket.c:1632 [inline]
__sys_socket_create net/socket.c:1669 [inline]
__sys_socket_create net/socket.c:1654 [inline]
__sys_socket+0x147/0x260 net/socket.c:1716
__do_sys_socket net/socket.c:1730 [inline]
__se_sys_socket net/socket.c:1728 [inline]
__x64_sys_socket+0x72/0xb0 net/socket.c:1728
do_syscall_x64 arch/x86/entry/common.c:52 [inline]
do_syscall_64+0xcb/0x220 arch/x86/entry/common.c:83
entry_SYSCALL_64_after_hwframe+0x77/0x7f
BUG: memory leak
unreferenced object 0xffff888071a35c40 (size 80):
comm "syz.0.17", pid 10940, jiffies 4294970299
hex dump (first 32 bytes):
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
backtrace (crc 494f173f):
kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline]
slab_post_alloc_hook mm/slub.c:4152 [inline]
slab_alloc_node mm/slub.c:4197 [inline]
kmem_cache_alloc_noprof+0x29a/0x320 mm/slub.c:4204
lsm_inode_alloc security/security.c:756 [inline]
security_inode_alloc+0x3e/0x2d0 security/security.c:1692
inode_init_always_gfp+0xc69/0xfb0 fs/inode.c:235
inode_init_always include/linux/fs.h:3096 [inline]
alloc_inode+0x8a/0x240 fs/inode.c:272
sock_alloc+0x40/0x270 net/socket.c:633
__sock_create+0xc1/0x880 net/socket.c:1540
sock_create net/socket.c:1632 [inline]
__sys_socket_create net/socket.c:1669 [inline]
__sys_socket_create net/socket.c:1654 [inline]
__sys_socket+0x147/0x260 net/socket.c:1716
__do_sys_socket net/socket.c:1730 [inline]
__se_sys_socket net/socket.c:1728 [inline]
__x64_sys_socket+0x72/0xb0 net/socket.c:1728
do_syscall_x64 arch/x86/entry/common.c:52 [inline]
do_syscall_64+0xcb/0x220 arch/x86/entry/common.c:83
entry_SYSCALL_64_after_hwframe+0x77/0x7f
BUG: memory leak
unreferenced object 0xffff888012b7a000 (size 2048):
comm "syz.0.17", pid 10940, jiffies 4294970299
hex dump (first 32 bytes):
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
10 00 07 40 00 00 00 00 00 00 00 00 00 00 00 00 ...@............
backtrace (crc 1d357f10):
kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline]
slab_post_alloc_hook mm/slub.c:4152 [inline]
slab_alloc_node mm/slub.c:4197 [inline]
__do_kmalloc_node mm/slub.c:4331 [inline]
__kmalloc_noprof+0x331/0x460 mm/slub.c:4344
kmalloc_noprof include/linux/slab.h:882 [inline]
sk_prot_alloc+0x155/0x290 net/core/sock.c:2175
sk_alloc+0x36/0xc00 net/core/sock.c:2231
__netlink_create+0x5d/0x2c0 net/netlink/af_netlink.c:628
netlink_create+0x3a8/0x630 net/netlink/af_netlink.c:686
__sock_create+0x37a/0x880 net/socket.c:1576
sock_create net/socket.c:1632 [inline]
__sys_socket_create net/socket.c:1669 [inline]
__sys_socket_create net/socket.c:1654 [inline]
__sys_socket+0x147/0x260 net/socket.c:1716
__do_sys_socket net/socket.c:1730 [inline]
__se_sys_socket net/socket.c:1728 [inline]
__x64_sys_socket+0x72/0xb0 net/socket.c:1728
do_syscall_x64 arch/x86/entry/common.c:52 [inline]
do_syscall_64+0xcb/0x220 arch/x86/entry/common.c:83
entry_SYSCALL_64_after_hwframe+0x77/0x7f
BUG: memory leak
unreferenced object 0xffff888051829be0 (size 16):
comm "syz.0.17", pid 10940, jiffies 4294970299
hex dump (first 16 bytes):
00 3d 08 1b 80 88 ff ff 00 00 00 00 00 00 00 00 .=..............
backtrace (crc 55d3b4f7):
kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline]
slab_post_alloc_hook mm/slub.c:4152 [inline]
slab_alloc_node mm/slub.c:4197 [inline]
__do_kmalloc_node mm/slub.c:4331 [inline]
__kmalloc_noprof+0x331/0x460 mm/slub.c:4344
kmalloc_noprof include/linux/slab.h:882 [inline]
kzalloc_noprof include/linux/slab.h:1014 [inline]
lsm_blob_alloc security/security.c:685 [inline]
lsm_blob_alloc security/security.c:678 [inline]
lsm_sock_alloc security/security.c:4808 [inline]
security_sk_alloc+0x2ce/0x320 security/security.c:4824
sk_prot_alloc+0x16f/0x290 net/core/sock.c:2178
sk_alloc+0x36/0xc00 net/core/sock.c:2231
__netlink_create+0x5d/0x2c0 net/netlink/af_netlink.c:628
netlink_create+0x3a8/0x630 net/netlink/af_netlink.c:686
__sock_create+0x37a/0x880 net/socket.c:1576
sock_create net/socket.c:1632 [inline]
__sys_socket_create net/socket.c:1669 [inline]
__sys_socket_create net/socket.c:1654 [inline]
__sys_socket+0x147/0x260 net/socket.c:1716
__do_sys_socket net/socket.c:1730 [inline]
__se_sys_socket net/socket.c:1728 [inline]
__x64_sys_socket+0x72/0xb0 net/socket.c:1728
do_syscall_x64 arch/x86/entry/common.c:52 [inline]
do_syscall_64+0xcb/0x220 arch/x86/entry/common.c:83
entry_SYSCALL_64_after_hwframe+0x77/0x7f
BUG: memory leak
unreferenced object 0xffff88802b4bd1a0 (size 312):
comm "syz.0.17", pid 10940, jiffies 4294970299
hex dump (first 32 bytes):
00 00 50 40 00 00 00 00 02 00 00 00 00 00 00 00 ..P@............
60 77 a2 9a ff ff ff ff 00 00 00 00 00 00 00 00 `w..............
backtrace (crc 2796e8f7):
kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline]
slab_post_alloc_hook mm/slub.c:4152 [inline]
slab_alloc_node mm/slub.c:4197 [inline]
kmem_cache_alloc_lru_noprof+0x298/0x310 mm/slub.c:4216
__d_alloc+0x31/0x990 fs/dcache.c:1636
d_alloc_pseudo+0x1d/0xc0 fs/dcache.c:1768
alloc_path_pseudo fs/file_table.c:335 [inline]
alloc_file_pseudo+0xbf/0x1e0 fs/file_table.c:351
sock_alloc_file+0x53/0x1d0 net/socket.c:468
sock_map_fd net/socket.c:493 [inline]
__sys_socket+0x1bc/0x260 net/socket.c:1725
__do_sys_socket net/socket.c:1730 [inline]
__se_sys_socket net/socket.c:1728 [inline]
__x64_sys_socket+0x72/0xb0 net/socket.c:1728
do_syscall_x64 arch/x86/entry/common.c:52 [inline]
do_syscall_64+0xcb/0x220 arch/x86/entry/common.c:83
entry_SYSCALL_64_after_hwframe+0x77/0x7f
connection error: failed to recv *flatrpc.ExecutorMessageRawT: EOF
==================================================================
=== C Reproducer ===
==================================================================
// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <dirent.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <linux/futex.h>
static void sleep_ms(uint64_t ms)
{
usleep(ms * 1000);
}
static uint64_t current_time_ms(void)
{
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts))
exit(1);
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
}
static void thread_start(void* (*fn)(void*), void* arg)
{
pthread_t th;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 128 << 10);
int i = 0;
for (; i < 100; i++) {
if (pthread_create(&th, &attr, fn, arg) == 0) {
pthread_attr_destroy(&attr);
return;
}
if (errno == EAGAIN) {
usleep(50);
continue;
}
break;
}
exit(1);
}
typedef struct {
int state;
} event_t;
static void event_init(event_t* ev)
{
ev->state = 0;
}
static void event_reset(event_t* ev)
{
ev->state = 0;
}
static void event_set(event_t* ev)
{
if (ev->state)
exit(1);
__atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000);
}
static void event_wait(event_t* ev)
{
while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0);
}
static int event_isset(event_t* ev)
{
return __atomic_load_n(&ev->state, __ATOMIC_ACQUIRE);
}
static int event_timedwait(event_t* ev, uint64_t timeout)
{
uint64_t start = current_time_ms();
uint64_t now = start;
for (;;) {
uint64_t remain = timeout - (now - start);
struct timespec ts;
ts.tv_sec = remain / 1000;
ts.tv_nsec = (remain % 1000) * 1000 * 1000;
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, &ts);
if (__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
return 1;
now = current_time_ms();
if (now - start > timeout)
return 0;
}
}
static bool write_file(const char* file, const char* what, ...)
{
char buf[1024];
va_list args;
va_start(args, what);
vsnprintf(buf, sizeof(buf), what, args);
va_end(args);
buf[sizeof(buf) - 1] = 0;
int len = strlen(buf);
int fd = open(file, O_WRONLY | O_CLOEXEC);
if (fd == -1)
return false;
if (write(fd, buf, len) != len) {
int err = errno;
close(fd);
errno = err;
return false;
}
close(fd);
return true;
}
static void kill_and_wait(int pid, int* status)
{
kill(-pid, SIGKILL);
kill(pid, SIGKILL);
for (int i = 0; i < 100; i++) {
if (waitpid(-1, status, WNOHANG | __WALL) == pid)
return;
usleep(1000);
}
DIR* dir = opendir("/sys/fs/fuse/connections");
if (dir) {
for (;;) {
struct dirent* ent = readdir(dir);
if (!ent)
break;
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
char abort[300];
snprintf(abort, sizeof(abort), "/sys/fs/fuse/connections/%s/abort",
ent->d_name);
int fd = open(abort, O_WRONLY);
if (fd == -1) {
continue;
}
if (write(fd, abort, 1) < 0) {
}
close(fd);
}
closedir(dir);
} else {
}
while (waitpid(-1, status, __WALL) != pid) {
}
}
static void setup_test()
{
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
setpgrp();
write_file("/proc/self/oom_score_adj", "1000");
}
#define KMEMLEAK_FILE "/sys/kernel/debug/kmemleak"
static const char* setup_leak()
{
if (!write_file(KMEMLEAK_FILE, "scan=off")) {
if (errno == EBUSY)
return "KMEMLEAK disabled: increase CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE"
" or unset CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF";
return "failed to write(kmemleak, \"scan=off\")";
}
if (!write_file(KMEMLEAK_FILE, "scan"))
return "failed to write(kmemleak, \"scan\")";
sleep(5);
if (!write_file(KMEMLEAK_FILE, "scan"))
return "failed to write(kmemleak, \"scan\")";
if (!write_file(KMEMLEAK_FILE, "clear"))
return "failed to write(kmemleak, \"clear\")";
return NULL;
}
static void check_leaks(void)
{
int fd = open(KMEMLEAK_FILE, O_RDWR);
if (fd == -1)
exit(1);
uint64_t start = current_time_ms();
if (write(fd, "scan", 4) != 4)
exit(1);
sleep(1);
while (current_time_ms() - start < 4 * 1000)
sleep(1);
if (write(fd, "scan", 4) != 4)
exit(1);
static char buf[128 << 10];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n < 0)
exit(1);
int nleaks = 0;
if (n != 0) {
sleep(1);
if (write(fd, "scan", 4) != 4)
exit(1);
if (lseek(fd, 0, SEEK_SET) < 0)
exit(1);
n = read(fd, buf, sizeof(buf) - 1);
if (n < 0)
exit(1);
buf[n] = 0;
char* pos = buf;
char* end = buf + n;
while (pos < end) {
char* next = strstr(pos + 1, "unreferenced object");
if (!next)
next = end;
char prev = *next;
*next = 0;
fprintf(stderr, "BUG: memory leak\n%s\n", pos);
*next = prev;
pos = next;
nleaks++;
}
}
if (write(fd, "clear", 5) != 5)
exit(1);
close(fd);
if (nleaks)
exit(1);
}
struct thread_t {
int created, call;
event_t ready, done;
};
static struct thread_t threads[16];
static void execute_call(int call);
static int running;
static void* thr(void* arg)
{
struct thread_t* th = (struct thread_t*)arg;
for (;;) {
event_wait(&th->ready);
event_reset(&th->ready);
execute_call(th->call);
__atomic_fetch_sub(&running, 1, __ATOMIC_RELAXED);
event_set(&th->done);
}
return 0;
}
static void execute_one(void)
{
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {
}
int i, call, thread;
for (call = 0; call < 2; call++) {
for (thread = 0; thread < (int)(sizeof(threads) / sizeof(threads[0]));
thread++) {
struct thread_t* th = &threads[thread];
if (!th->created) {
th->created = 1;
event_init(&th->ready);
event_init(&th->done);
event_set(&th->done);
thread_start(thr, th);
}
if (!event_isset(&th->done))
continue;
event_reset(&th->done);
th->call = call;
__atomic_fetch_add(&running, 1, __ATOMIC_RELAXED);
event_set(&th->ready);
event_timedwait(&th->done, 50);
break;
}
}
for (i = 0; i < 100 && __atomic_load_n(&running, __ATOMIC_RELAXED); i++)
sleep_ms(1);
}
static void execute_one(void);
#define WAIT_FLAGS __WALL
static void loop(void)
{
int iter = 0;
for (;; iter++) {
int pid = fork();
if (pid < 0)
exit(1);
if (pid == 0) {
setup_test();
execute_one();
exit(0);
}
int status = 0;
uint64_t start = current_time_ms();
for (;;) {
sleep_ms(10);
if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid)
break;
if (current_time_ms() - start < 5000)
continue;
kill_and_wait(pid, &status);
break;
}
check_leaks();
}
}
uint64_t r[1] = {0xffffffffffffffff};
void execute_call(int call)
{
intptr_t res = 0;
switch (call) {
case 0:
// socket$nl_generic arguments: [
// domain: const = 0x10 (8 bytes)
// type: const = 0x3 (8 bytes)
// proto: const = 0x10 (4 bytes)
// ]
// returns sock_nl_generic
res = syscall(__NR_socket, /*domain=*/0x10ul, /*type=*/3ul, /*proto=*/0x10);
if (res != -1)
r[0] = res;
break;
case 1:
// fremovexattr arguments: [
// fd: fd (resource)
// name: nil
// ]
syscall(__NR_fremovexattr, /*fd=*/r[0], /*name=*/0ul);
break;
}
}
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul,
/*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul,
/*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul,
/*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/ 7ul,
/*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul,
/*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul,
/*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul,
/*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if ((reason = setup_leak()))
printf("the reproducer may not work as expected: leak checking setup "
"failed: %s\n",
reason);
loop();
return 0;
}
==================================================================
I am a graduate student researching kernel fuzzing algorithms. As this
is my first time submitting a bug report to the kernel community,
please bear with me if there are any procedural oversights.
Please let me know if you need any further details or if you would
like me to test any proposed patches.
Best regards,
yuhangyang
View attachment "config.txt" of type "text/plain" (268366 bytes)
Powered by blists - more mailing lists