2024-02-20 07:48:11

by Lukas Bulwahn

[permalink] [raw]
Subject: memory leak in smack since de93 e515 db30 ("Smack: Improve mount process memory use")

Dear Casey,

For some experimentation, I have been running fuzzing campaigns and I
noticed a memory leak in sys_fsconfig.

As I have a C reproducer, I could manually bisect it being introduced with:

commit de93e515db306767549bb29a926f523ca2a601ab
Author: Casey Schaufler <[email protected]>
Date: Wed Apr 5 08:46:14 2023 -0700

Smack: Improve mount process memory use

The existing mount processing code in Smack makes many unnecessary
copies of Smack labels. Because Smack labels never go away once
imported it is safe to use pointers to them rather than copies.
Replace the use of copies of label names to pointers to the global
label list entries.

So, it was introduced with v6.4; it is still reproducible on v6.8-rc5.
I did not check linux-next, but I suspect it is still there, if you
did not do any major rework.

The C reproducer below is automatically generated by syzkaller; so, it
is a bit difficult to read and a bit lengthy, but I hope you can
extract the essence.

If I have more time next week, I might also give you a more readable C
program, and possibly already some analysis and a bug-fix.

As far as I see, a bug-fix would need to be included to the LTS v6.6.y.

If you need any further information, just let me know.

Lukas


Here are the details:

Crash report:

BUG: memory leak
unreferenced object 0xffff000004aa91b0 (size 16):
comm "syz-executor276", pid 964, jiffies 4294943128 (age 9.790s)
hex dump (first 16 bytes):
2e 2d 7d 2e 27 5d 23 fe 00 00 00 00 00 00 00 00 .-}.']#.........
backtrace:
[<00000000cf09e3ce>] kmemleak_alloc_recursive
include/linux/kmemleak.h:42 [inline]
[<00000000cf09e3ce>] slab_post_alloc_hook mm/slab.h:765 [inline]
[<00000000cf09e3ce>] slab_alloc_node mm/slub.c:3478 [inline]
[<00000000cf09e3ce>] __kmem_cache_alloc_node+0x1a8/0x234 mm/slub.c:3517
[<00000000643fc445>] __do_kmalloc_node mm/slab_common.c:1025 [inline]
[<00000000643fc445>] __kmalloc_node_track_caller+0x48/0x74
mm/slab_common.c:1046
[<00000000a19493b9>] memdup_user+0x44/0xf8 mm/util.c:197
[<00000000684568ab>] strndup_user+0x8c/0xcc mm/util.c:256
[<00000000b4cbc820>] __do_sys_fsconfig fs/fsopen.c:433 [inline]
[<00000000b4cbc820>] __se_sys_fsconfig fs/fsopen.c:349 [inline]
[<00000000b4cbc820>] __arm64_sys_fsconfig+0x728/0x7b0 fs/fsopen.c:349
[<000000003ab31b26>] __invoke_syscall
arch/arm64/kernel/syscall.c:37 [inline]
[<000000003ab31b26>] invoke_syscall.constprop.0+0x6c/0x134
arch/arm64/kernel/syscall.c:51
[<00000000dbee991c>] el0_svc_common arch/arm64/kernel/syscall.c:136 [inline]
[<00000000dbee991c>] do_el0_svc+0x78/0x158 arch/arm64/kernel/syscall.c:155
[<000000009a39103d>] el0_svc+0x4c/0x158 arch/arm64/kernel/entry-common.c:678
[<00000000c2657137>] el0t_64_sync_handler+0x100/0x12c
arch/arm64/kernel/entry-common.c:696
[<00000000b52cd424>] el0t_64_sync+0x194/0x198 arch/arm64/kernel/entry.S:599

BUG: memory leak
unreferenced object 0xffff000004aa9a40 (size 16):
comm "syz-executor276", pid 965, jiffies 4294943562 (age 5.450s)
hex dump (first 16 bytes):
2e 2d 7d 2e 27 5d 23 fe 00 00 00 00 00 00 00 00 .-}.']#.........
backtrace:
[<00000000cf09e3ce>] kmemleak_alloc_recursive
include/linux/kmemleak.h:42 [inline]
[<00000000cf09e3ce>] slab_post_alloc_hook mm/slab.h:765 [inline]
[<00000000cf09e3ce>] slab_alloc_node mm/slub.c:3478 [inline]
[<00000000cf09e3ce>] __kmem_cache_alloc_node+0x1a8/0x234 mm/slub.c:3517
[<00000000643fc445>] __do_kmalloc_node mm/slab_common.c:1025 [inline]
[<00000000643fc445>] __kmalloc_node_track_caller+0x48/0x74
mm/slab_common.c:1046
[<00000000a19493b9>] memdup_user+0x44/0xf8 mm/util.c:197
[<00000000684568ab>] strndup_user+0x8c/0xcc mm/util.c:256
[<00000000b4cbc820>] __do_sys_fsconfig fs/fsopen.c:433 [inline]
[<00000000b4cbc820>] __se_sys_fsconfig fs/fsopen.c:349 [inline]
[<00000000b4cbc820>] __arm64_sys_fsconfig+0x728/0x7b0 fs/fsopen.c:349
[<000000003ab31b26>] __invoke_syscall
arch/arm64/kernel/syscall.c:37 [inline]
[<000000003ab31b26>] invoke_syscall.constprop.0+0x6c/0x134
arch/arm64/kernel/syscall.c:51
[<00000000dbee991c>] el0_svc_common arch/arm64/kernel/syscall.c:136 [inline]
[<00000000dbee991c>] do_el0_svc+0x78/0x158 arch/arm64/kernel/syscall.c:155
[<000000009a39103d>] el0_svc+0x4c/0x158 arch/arm64/kernel/entry-common.c:678
[<00000000c2657137>] el0t_64_sync_handler+0x100/0x12c
arch/arm64/kernel/entry-common.c:696
[<00000000b52cd424>] el0t_64_sync+0x194/0x198 arch/arm64/kernel/entry.S:599

C reproducer:

// https://None.appspot.com/bug?id=322fa31c4d8d351912142d420387672b633b8f48
// autogenerated by syzkaller (https://github.com/google/syzkaller)

#define _GNU_SOURCE

#include <dirent.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.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>

#ifndef __NR_fsconfig
#define __NR_fsconfig 431
#endif
#ifndef __NR_fsopen
#define __NR_fsopen 430
#endif
#ifndef __NR_mmap
#define __NR_mmap 222
#endif

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 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 void setup_leak()
{
if (!write_file(KMEMLEAK_FILE, "scan"))
exit(1);
sleep(5);
if (!write_file(KMEMLEAK_FILE, "scan"))
exit(1);
if (!write_file(KMEMLEAK_FILE, "clear"))
exit(1);
}

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);
}

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 (;;) {
if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid)
break;
sleep_ms(1);
if (current_time_ms() - start < 5000)
continue;
kill_and_wait(pid, &status);
break;
}
check_leaks();
}
}

uint64_t r[1] = {0xffffffffffffffff};

void execute_one(void)
{
intptr_t res = 0;
memcpy((void*)0x20000000, "sysfs\000", 6);
res = syscall(__NR_fsopen, /*type=*/0x20000000ul, /*flags=*/0ul);
if (res != -1)
r[0] = res;
memcpy((void*)0x20000640, "smackfstransmute", 16);
memcpy((void*)0x20000680, ".-}.\']#\376\000", 9);
syscall(__NR_fsconfig, /*fd=*/r[0], /*cmd=*/1ul, /*key=*/0x20000640ul,
/*value=*/0x20000680ul, /*aux=*/0ul);
}
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffff000ul, /*len=*/0x1000ul, /*prot=*/0ul,
/*flags=*/0x32ul, /*fd=*/-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x20000000ul, /*len=*/0x1000000ul, /*prot=*/7ul,
/*flags=*/0x32ul, /*fd=*/-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x21000000ul, /*len=*/0x1000ul, /*prot=*/0ul,
/*flags=*/0x32ul, /*fd=*/-1, /*offset=*/0ul);
setup_leak();
loop();
return 0;
}




Best regards,

Lukas