kcm: memory leak in kcm_sendmsg
From: Dmitry Vyukov
Date: Tue Jan 09 2018 - 12:15:09 EST
Hello,
syzkaller has discovered the following memory leak:
unreferenced object 0xffff8800655d5e20 (size 512):
comm "a.out", pid 10342, jiffies 4295928494 (age 24.051s)
hex dump (first 32 bytes):
80 6b 5d 65 00 88 ff ff 69 63 65 73 2f 76 69 72 .k]e....ices/vir
74 75 61 6c 2f 6e 65 74 2f 74 75 6e 6c 30 2f 71 tual/net/tunl0/q
backtrace:
[<0000000017222de2>] kmemleak_alloc_recursive
include/linux/kmemleak.h:55 [inline]
[<0000000017222de2>] slab_post_alloc_hook mm/slab.h:440 [inline]
[<0000000017222de2>] slab_alloc_node mm/slub.c:2725 [inline]
[<0000000017222de2>] __kmalloc_node_track_caller+0x19f/0x360 mm/slub.c:4320
[<00000000468595b2>] __kmalloc_reserve.isra.39+0x3a/0xe0
net/core/skbuff.c:137
[<000000005d645735>] __alloc_skb+0x144/0x7c0 net/core/skbuff.c:205
[<0000000076b4c539>] alloc_skb include/linux/skbuff.h:983 [inline]
[<0000000076b4c539>] kcm_sendmsg+0x66a/0x2480 net/kcm/kcmsock.c:968
[<0000000035be3c2b>] sock_sendmsg_nosec net/socket.c:636 [inline]
[<0000000035be3c2b>] sock_sendmsg+0xd2/0x120 net/socket.c:646
[<00000000abbae6ad>] SYSC_sendto+0x3de/0x640 net/socket.c:1727
[<00000000b55ba03b>] SyS_sendto+0x40/0x50 net/socket.c:1695
[<000000005d14bb62>] entry_SYSCALL_64_fastpath+0x23/0x9a
[<0000000000cf1810>] 0xffffffffffffffff
unreferenced object 0xffff880053801e40 (size 232):
comm "a.out", pid 10342, jiffies 4295928494 (age 24.051s)
hex dump (first 32 bytes):
c0 20 80 53 00 88 ff ff 00 00 00 00 00 00 00 00 . .S............
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
backtrace:
[<00000000519e860b>] kmemleak_alloc_recursive
include/linux/kmemleak.h:55 [inline]
[<00000000519e860b>] slab_post_alloc_hook mm/slab.h:440 [inline]
[<00000000519e860b>] slab_alloc_node mm/slub.c:2725 [inline]
[<00000000519e860b>] kmem_cache_alloc_node+0x12d/0x2a0 mm/slub.c:2761
[<000000001a066279>] __alloc_skb+0x103/0x7c0 net/core/skbuff.c:193
[<0000000076b4c539>] alloc_skb include/linux/skbuff.h:983 [inline]
[<0000000076b4c539>] kcm_sendmsg+0x66a/0x2480 net/kcm/kcmsock.c:968
[<0000000035be3c2b>] sock_sendmsg_nosec net/socket.c:636 [inline]
[<0000000035be3c2b>] sock_sendmsg+0xd2/0x120 net/socket.c:646
[<00000000abbae6ad>] SYSC_sendto+0x3de/0x640 net/socket.c:1727
[<00000000b55ba03b>] SyS_sendto+0x40/0x50 net/socket.c:1695
[<000000005d14bb62>] entry_SYSCALL_64_fastpath+0x23/0x9a
[<0000000000cf1810>] 0xffffffffffffffff
Reproducer is attached. On 4.15-rc7.
// autogenerated by syzkaller (http://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <dirent.h>
#include <endian.h>
#include <errno.h>
#include <linux/futex.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
__attribute__((noreturn)) static void doexit(int status)
{
volatile unsigned i;
syscall(__NR_exit_group, status);
for (i = 0;; i++) {
}
}
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
const int kFailStatus = 67;
const int kRetryStatus = 69;
static void fail(const char* msg, ...)
{
int e = errno;
va_list args;
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
fprintf(stderr, " (errno %d)\n", e);
doexit((e == ENOMEM || e == EAGAIN) ? kRetryStatus : kFailStatus);
}
static void exitf(const char* msg, ...)
{
int e = errno;
va_list args;
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
fprintf(stderr, " (errno %d)\n", e);
doexit(kRetryStatus);
}
static __thread int skip_segv;
static __thread jmp_buf segv_env;
static void segv_handler(int sig, siginfo_t* info, void* uctx)
{
uintptr_t addr = (uintptr_t)info->si_addr;
const uintptr_t prog_start = 1 << 20;
const uintptr_t prog_end = 100 << 20;
if (__atomic_load_n(&skip_segv, __ATOMIC_RELAXED) &&
(addr < prog_start || addr > prog_end)) {
_longjmp(segv_env, 1);
}
doexit(sig);
}
static void install_segv_handler()
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_IGN;
syscall(SYS_rt_sigaction, 0x20, &sa, NULL, 8);
syscall(SYS_rt_sigaction, 0x21, &sa, NULL, 8);
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = segv_handler;
sa.sa_flags = SA_NODEFER | SA_SIGINFO;
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGBUS, &sa, NULL);
}
#define NONFAILING(...) \
{ \
__atomic_fetch_add(&skip_segv, 1, __ATOMIC_SEQ_CST); \
if (_setjmp(segv_env) == 0) { \
__VA_ARGS__; \
} \
__atomic_fetch_sub(&skip_segv, 1, __ATOMIC_SEQ_CST); \
}
static uint64_t current_time_ms()
{
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts))
fail("clock_gettime failed");
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
}
static void use_temporary_dir()
{
char tmpdir_template[] = "./syzkaller.XXXXXX";
char* tmpdir = mkdtemp(tmpdir_template);
if (!tmpdir)
fail("failed to mkdtemp");
if (chmod(tmpdir, 0777))
fail("failed to chmod");
if (chdir(tmpdir))
fail("failed to chdir");
}
static void loop();
static void sandbox_common()
{
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
setpgrp();
setsid();
struct rlimit rlim;
rlim.rlim_cur = rlim.rlim_max = 128 << 20;
setrlimit(RLIMIT_AS, &rlim);
rlim.rlim_cur = rlim.rlim_max = 8 << 20;
setrlimit(RLIMIT_MEMLOCK, &rlim);
rlim.rlim_cur = rlim.rlim_max = 1 << 20;
setrlimit(RLIMIT_FSIZE, &rlim);
rlim.rlim_cur = rlim.rlim_max = 1 << 20;
setrlimit(RLIMIT_STACK, &rlim);
rlim.rlim_cur = rlim.rlim_max = 0;
setrlimit(RLIMIT_CORE, &rlim);
#define CLONE_NEWCGROUP 0x02000000
unshare(CLONE_NEWNS);
unshare(CLONE_NEWIPC);
unshare(CLONE_NEWCGROUP);
unshare(CLONE_NEWNET);
unshare(CLONE_NEWUTS);
unshare(CLONE_SYSVSEM);
}
static int do_sandbox_none(int executor_pid, bool enable_tun)
{
unshare(CLONE_NEWPID);
int pid = fork();
if (pid < 0)
fail("sandbox fork failed");
if (pid)
return pid;
sandbox_common();
loop();
doexit(1);
}
static void remove_dir(const char* dir)
{
DIR* dp;
struct dirent* ep;
int iter = 0;
retry:
dp = opendir(dir);
if (dp == NULL) {
if (errno == EMFILE) {
exitf("opendir(%s) failed due to NOFILE, exiting", dir);
}
exitf("opendir(%s) failed", dir);
}
while ((ep = readdir(dp))) {
if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0)
continue;
char filename[FILENAME_MAX];
snprintf(filename, sizeof(filename), "%s/%s", dir, ep->d_name);
struct stat st;
if (lstat(filename, &st))
exitf("lstat(%s) failed", filename);
if (S_ISDIR(st.st_mode)) {
remove_dir(filename);
continue;
}
int i;
for (i = 0;; i++) {
if (unlink(filename) == 0)
break;
if (errno == EROFS) {
break;
}
if (errno != EBUSY || i > 100)
exitf("unlink(%s) failed", filename);
if (umount2(filename, MNT_DETACH))
exitf("umount(%s) failed", filename);
}
}
closedir(dp);
int i;
for (i = 0;; i++) {
if (rmdir(dir) == 0)
break;
if (i < 100) {
if (errno == EROFS) {
break;
}
if (errno == EBUSY) {
if (umount2(dir, MNT_DETACH))
exitf("umount(%s) failed", dir);
continue;
}
if (errno == ENOTEMPTY) {
if (iter < 100) {
iter++;
goto retry;
}
}
}
exitf("rmdir(%s) failed", dir);
}
}
static void test();
void loop()
{
int iter;
for (iter = 0;; iter++) {
char cwdbuf[256];
sprintf(cwdbuf, "./%d", iter);
if (mkdir(cwdbuf, 0777))
fail("failed to mkdir");
int pid = fork();
if (pid < 0)
fail("loop fork failed");
if (pid == 0) {
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
setpgrp();
if (chdir(cwdbuf))
fail("failed to chdir");
test();
doexit(0);
}
int status = 0;
uint64_t start = current_time_ms();
for (;;) {
int res = waitpid(-1, &status, __WALL | WNOHANG);
if (res == pid)
break;
usleep(1000);
if (current_time_ms() - start > 5 * 1000) {
kill(-pid, SIGKILL);
kill(pid, SIGKILL);
while (waitpid(-1, &status, __WALL) != pid) {
}
break;
}
}
remove_dir(cwdbuf);
}
}
struct thread_t {
int created, running, call;
pthread_t th;
};
static struct thread_t threads[16];
static void execute_call(int call);
static int running;
static int collide;
static void* thr(void* arg)
{
struct thread_t* th = (struct thread_t*)arg;
for (;;) {
while (!__atomic_load_n(&th->running, __ATOMIC_ACQUIRE))
syscall(SYS_futex, &th->running, FUTEX_WAIT, 0, 0);
execute_call(th->call);
__atomic_fetch_sub(&running, 1, __ATOMIC_RELAXED);
__atomic_store_n(&th->running, 0, __ATOMIC_RELEASE);
syscall(SYS_futex, &th->running, FUTEX_WAKE);
}
return 0;
}
static void execute(int num_calls)
{
int call, thread;
running = 0;
for (call = 0; call < num_calls; call++) {
for (thread = 0; thread < sizeof(threads) / sizeof(threads[0]); thread++) {
struct thread_t* th = &threads[thread];
if (!th->created) {
th->created = 1;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 128 << 10);
pthread_create(&th->th, &attr, thr, th);
}
if (!__atomic_load_n(&th->running, __ATOMIC_ACQUIRE)) {
th->call = call;
__atomic_fetch_add(&running, 1, __ATOMIC_RELAXED);
__atomic_store_n(&th->running, 1, __ATOMIC_RELEASE);
syscall(SYS_futex, &th->running, FUTEX_WAKE);
if (collide && call % 2)
break;
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 20 * 1000 * 1000;
syscall(SYS_futex, &th->running, FUTEX_WAIT, 1, &ts);
if (running)
usleep((call == num_calls - 1) ? 10000 : 1000);
break;
}
}
}
}
long r[3];
uint64_t procid;
void execute_call(int call)
{
switch (call) {
case 0:
syscall(__NR_mmap, 0x20000000, 0xfff000, 3, 0x32, -1, 0);
break;
case 1:
r[0] = syscall(__NR_socket, 0x29, 0x802, 0);
break;
case 2:
NONFAILING(*(uint64_t*)0x20556915 = 0x20b9cfe4);
NONFAILING(*(uint32_t*)0x2055691d = 0x1c);
NONFAILING(*(uint64_t*)0x20556925 = 0x20812000);
NONFAILING(*(uint64_t*)0x2055692d = 1);
NONFAILING(*(uint64_t*)0x20556935 = 0x20868000);
NONFAILING(*(uint64_t*)0x2055693d = 0);
NONFAILING(*(uint32_t*)0x20556945 = 0);
NONFAILING(*(uint32_t*)0x2055694d = 0);
NONFAILING(*(uint16_t*)0x20b9cfe4 = 0xa);
NONFAILING(*(uint16_t*)0x20b9cfe6 = htobe16(0x4e20 + procid * 4));
NONFAILING(*(uint32_t*)0x20b9cfe8 = 0);
NONFAILING(*(uint8_t*)0x20b9cfec = 0xfe);
NONFAILING(*(uint8_t*)0x20b9cfed = 0x80);
NONFAILING(*(uint8_t*)0x20b9cfee = 0);
NONFAILING(*(uint8_t*)0x20b9cfef = 0);
NONFAILING(*(uint8_t*)0x20b9cff0 = 0);
NONFAILING(*(uint8_t*)0x20b9cff1 = 0);
NONFAILING(*(uint8_t*)0x20b9cff2 = 0);
NONFAILING(*(uint8_t*)0x20b9cff3 = 0);
NONFAILING(*(uint8_t*)0x20b9cff4 = 0);
NONFAILING(*(uint8_t*)0x20b9cff5 = 0);
NONFAILING(*(uint8_t*)0x20b9cff6 = 0);
NONFAILING(*(uint8_t*)0x20b9cff7 = 0);
NONFAILING(*(uint8_t*)0x20b9cff8 = 0);
NONFAILING(*(uint8_t*)0x20b9cff9 = 0);
NONFAILING(*(uint8_t*)0x20b9cffa = 0 + procid * 1);
NONFAILING(*(uint8_t*)0x20b9cffb = 0xaa);
NONFAILING(*(uint32_t*)0x20b9cffc = 0);
NONFAILING(*(uint64_t*)0x20812000 = 0x20cd7fd2);
NONFAILING(*(uint64_t*)0x20812008 = 1);
NONFAILING(memcpy((void*)0x20cd7fd2, "G", 1));
syscall(__NR_sendmmsg, r[0], 0x20556915, 1, 0x8010);
break;
case 3:
NONFAILING(*(uint32_t*)0x205a6000 = r[0]);
if (syscall(__NR_ioctl, r[0], 0x89e2, 0x205a6000) != -1)
NONFAILING(r[1] = *(uint32_t*)0x205a6000);
break;
case 4:
NONFAILING(*(uint32_t*)0x209d0ffc = 0xe8);
if (syscall(__NR_getsockopt, r[1], 0, 0x10, 0x20d7f000, 0x209d0ffc) != -1)
NONFAILING(r[2] = *(uint32_t*)0x20d7f030);
break;
case 5:
NONFAILING(*(uint8_t*)0x20df7fe8 = 0xfe);
NONFAILING(*(uint8_t*)0x20df7fe9 = 0x80);
NONFAILING(*(uint8_t*)0x20df7fea = 0);
NONFAILING(*(uint8_t*)0x20df7feb = 0);
NONFAILING(*(uint8_t*)0x20df7fec = 0);
NONFAILING(*(uint8_t*)0x20df7fed = 0);
NONFAILING(*(uint8_t*)0x20df7fee = 0);
NONFAILING(*(uint8_t*)0x20df7fef = 0);
NONFAILING(*(uint8_t*)0x20df7ff0 = 0);
NONFAILING(*(uint8_t*)0x20df7ff1 = 0);
NONFAILING(*(uint8_t*)0x20df7ff2 = 0);
NONFAILING(*(uint8_t*)0x20df7ff3 = 0);
NONFAILING(*(uint8_t*)0x20df7ff4 = 0);
NONFAILING(*(uint8_t*)0x20df7ff5 = 0);
NONFAILING(*(uint8_t*)0x20df7ff6 = 0 + procid * 1);
NONFAILING(*(uint8_t*)0x20df7ff7 = 0xbb);
NONFAILING(*(uint32_t*)0x20df7ff8 = 8);
NONFAILING(*(uint32_t*)0x20df7ffc = r[2]);
syscall(__NR_ioctl, r[1], 0x8916, 0x20df7fe8);
break;
case 6:
NONFAILING(*(uint64_t*)0x20dc8000 = 0x205d1000);
NONFAILING(*(uint32_t*)0x20dc8008 = 0xa);
NONFAILING(*(uint64_t*)0x20dc8010 = 0x20277000);
NONFAILING(*(uint64_t*)0x20dc8018 = 1);
NONFAILING(*(uint64_t*)0x20dc8020 = 0x20000000);
NONFAILING(*(uint64_t*)0x20dc8028 = 0);
NONFAILING(*(uint32_t*)0x20dc8030 = 0);
NONFAILING(*(uint16_t*)0x205d1000 = 0);
NONFAILING(memcpy((void*)0x205d1002, "./file0", 8));
NONFAILING(*(uint64_t*)0x20277000 = 0x20cc8000);
NONFAILING(*(uint64_t*)0x20277008 = 1);
NONFAILING(memcpy((void*)0x20cc8000, "\xab", 1));
syscall(__NR_sendmsg, r[1], 0x20dc8000, 0);
break;
case 7:
syscall(__NR_sendto, r[0], 0x209ccff0, 0x7fffffff, 0, 0x20000000, 0);
break;
}
}
void test()
{
memset(r, -1, sizeof(r));
execute(8);
collide = 1;
execute(8);
}
int main()
{
char* cwd = get_current_dir_name();
for (procid = 0; procid < 8; procid++) {
if (fork() == 0) {
install_segv_handler();
for (;;) {
if (chdir(cwd))
fail("failed to chdir");
use_temporary_dir();
int pid = do_sandbox_none(procid, false);
int status = 0;
while (waitpid(pid, &status, __WALL) != pid) {
}
}
}
}
sleep(1000000);
return 0;
}