net/ipv6: slab-out-of-bounds read in seg6_validate_srh

From: Andrey Konovalov
Date: Tue Apr 18 2017 - 10:55:01 EST


Hi,

I've got the following error report while fuzzing the kernel with syzkaller.

On commit 4f7d029b9bf009fbee76bb10c0c4351a1870d2f3 (4.11-rc7).

A reproducer and .config are attached.

==================================================================
BUG: KASAN: slab-out-of-bounds in seg6_validate_srh+0x203/0x220
net/ipv6/seg6.c:57 at addr ffff88006a759608
Read of size 1 by task syz-executor4/2627
CPU: 2 PID: 2627 Comm: syz-executor4 Not tainted 4.11.0-rc7+ #247
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Bochs 01/01/2011
Call Trace:
__dump_stack lib/dump_stack.c:16 [inline]
dump_stack+0x292/0x398 lib/dump_stack.c:52
sctp: [Deprecated]: syz-executor5 (pid 2636) Use of int in maxseg socket option.
Use struct sctp_assoc_value instead
kasan_object_err+0x1c/0x70 mm/kasan/report.c:164
print_address_description mm/kasan/report.c:202 [inline]
kasan_report_error mm/kasan/report.c:291 [inline]
kasan_report+0x252/0x510 mm/kasan/report.c:347
__asan_report_load1_noabort+0x14/0x20 mm/kasan/report.c:365
seg6_validate_srh+0x203/0x220 net/ipv6/seg6.c:57
do_ipv6_setsockopt.isra.7+0x2008/0x3bc0 net/ipv6/ipv6_sockglue.c:439
ipv6_setsockopt+0x9b/0x140 net/ipv6/ipv6_sockglue.c:919
udpv6_setsockopt+0x45/0x80 net/ipv6/udp.c:1349
sock_common_setsockopt+0x95/0xd0 net/core/sock.c:2750
SYSC_setsockopt net/socket.c:1798 [inline]
SyS_setsockopt+0x270/0x3a0 net/socket.c:1777
entry_SYSCALL_64_fastpath+0x1f/0xc2
RIP: 0033:0x4458d9
RSP: 002b:00007f6b9696ab58 EFLAGS: 00000292 ORIG_RAX: 0000000000000036
RAX: ffffffffffffffda RBX: 0000000000000018 RCX: 00000000004458d9
RDX: 0000000000000039 RSI: 0000000000000029 RDI: 0000000000000018
RBP: 00000000006e34e0 R08: 00000000000000a8 R09: 0000000000000000
R10: 000000002000efe7 R11: 0000000000000292 R12: 0000000000708000
R13: 0000000000000006 R14: 0000000000000001 R15: 000000000000001a
Object at ffff88006a759520, in cache kmalloc-256 size: 256
Allocated:
PID = 2627
save_stack_trace+0x16/0x20 arch/x86/kernel/stacktrace.c:59
save_stack+0x43/0xd0 mm/kasan/kasan.c:513
set_track mm/kasan/kasan.c:525 [inline]
kasan_kmalloc+0xad/0xe0 mm/kasan/kasan.c:616
__kmalloc+0xa0/0x2d0 mm/slub.c:3745
kmalloc include/linux/slab.h:495 [inline]
sock_kmalloc+0x11e/0x1b0 net/core/sock.c:1804
ipv6_renew_options+0x2b5/0xb50 net/ipv6/exthdrs.c:1053
do_ipv6_setsockopt.isra.7+0x1f5c/0x3bc0 net/ipv6/ipv6_sockglue.c:413
ipv6_setsockopt+0x9b/0x140 net/ipv6/ipv6_sockglue.c:919
udpv6_setsockopt+0x45/0x80 net/ipv6/udp.c:1349
sock_common_setsockopt+0x95/0xd0 net/core/sock.c:2750
SYSC_setsockopt net/socket.c:1798 [inline]
SyS_setsockopt+0x270/0x3a0 net/socket.c:1777
entry_SYSCALL_64_fastpath+0x1f/0xc2
Freed:
PID = 2241
save_stack_trace+0x16/0x20 arch/x86/kernel/stacktrace.c:59
save_stack+0x43/0xd0 mm/kasan/kasan.c:513
set_track mm/kasan/kasan.c:525 [inline]
kasan_slab_free+0x73/0xc0 mm/kasan/kasan.c:589
slab_free_hook mm/slub.c:1357 [inline]
slab_free_freelist_hook mm/slub.c:1379 [inline]
slab_free mm/slub.c:2961 [inline]
kfree+0xe8/0x2b0 mm/slub.c:3882
seq_release fs/seq_file.c:379 [inline]
single_release+0x80/0xb0 fs/seq_file.c:615
__fput+0x332/0x7f0 fs/file_table.c:209
____fput+0x15/0x20 fs/file_table.c:245
task_work_run+0x19b/0x270 kernel/task_work.c:116
tracehook_notify_resume include/linux/tracehook.h:191 [inline]
exit_to_usermode_loop+0x1c2/0x200 arch/x86/entry/common.c:161
prepare_exit_to_usermode arch/x86/entry/common.c:191 [inline]
syscall_return_slowpath+0x3d3/0x420 arch/x86/entry/common.c:260
entry_SYSCALL_64_fastpath+0xc0/0xc2
Memory state around the buggy address:
ffff88006a759500: fc fc fc fc 00 00 00 00 00 00 00 00 00 00 00 00
ffff88006a759580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffff88006a759600: 00 fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
^
ffff88006a759680: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff88006a759700: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc 00
==================================================================

Attachment: .config
Description: Binary data

// autogenerated by syzkaller (http://github.com/google/syzkaller)

#ifndef __NR_setsockopt
#define __NR_setsockopt 54
#endif
#ifndef __NR_mmap
#define __NR_mmap 9
#endif
#ifndef __NR_socket
#define __NR_socket 41
#endif

#define _GNU_SOURCE

#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <linux/capability.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <linux/kvm.h>
#include <linux/sched.h>
#include <net/if_arp.h>

#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <pthread.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

const int kFailStatus = 67;
const int kErrorStatus = 68;
const int kRetryStatus = 69;

__attribute__((noreturn)) void doexit(int status)
{
volatile unsigned i;
syscall(__NR_exit_group, status);
for (i = 0;; i++) {
}
}

__attribute__((noreturn)) void fail(const char* msg, ...)
{
int e = errno;
fflush(stdout);
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);
}

__attribute__((noreturn)) void exitf(const char* msg, ...)
{
int e = errno;
fflush(stdout);
va_list args;
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
fprintf(stderr, " (errno %d)\n", e);
doexit(kRetryStatus);
}

static int flag_debug;

void debug(const char* msg, ...)
{
if (!flag_debug)
return;
va_list args;
va_start(args, msg);
vfprintf(stdout, msg, args);
va_end(args);
fflush(stdout);
}

__thread int skip_segv;
__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)) {
debug("SIGSEGV on %p, skipping\n", addr);
_longjmp(segv_env, 1);
}
debug("SIGSEGV on %p, exiting\n", addr);
doexit(sig);
for (;;) {
}
}

static void install_segv_handler()
{
struct sigaction sa;
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); \
}

#define BITMASK_LEN(type, bf_len) (type)((1ull << (bf_len)) - 1)

#define BITMASK_LEN_OFF(type, bf_off, bf_len) \
(type)(BITMASK_LEN(type, (bf_len)) << (bf_off))

#define STORE_BY_BITMASK(type, addr, val, bf_off, bf_len) \
if ((bf_off) == 0 && (bf_len) == 0) { \
*(type*)(addr) = (type)(val); \
} else { \
type new_val = *(type*)(addr); \
new_val &= ~BITMASK_LEN_OFF(type, (bf_off), (bf_len)); \
new_val |= ((type)(val)&BITMASK_LEN(type, (bf_len))) << (bf_off); \
*(type*)(addr) = new_val; \
}

static uintptr_t execute_syscall(int nr, uintptr_t a0, uintptr_t a1,
uintptr_t a2, uintptr_t a3,
uintptr_t a4, uintptr_t a5,
uintptr_t a6, uintptr_t a7,
uintptr_t a8)
{
switch (nr) {
default:
return syscall(nr, a0, a1, a2, a3, a4, a5);
}
}

static void setup_main_process()
{
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);
install_segv_handler();

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

unshare(CLONE_NEWNS);
unshare(CLONE_NEWIPC);
unshare(CLONE_IO);
}

static int do_sandbox_none(int executor_pid, bool enable_tun)
{
int pid = fork();
if (pid)
return pid;

sandbox_common();

loop();
doexit(1);
}

long r[5];
void loop()
{
memset(r, -1, sizeof(r));
r[0] = execute_syscall(__NR_mmap, 0x2000e000ul, 0x1000ul, 0x3ul,
0x32ul, 0xfffffffffffffffful, 0x0ul, 0, 0, 0);
r[1] = execute_syscall(__NR_mmap, 0x2000f000ul, 0x1000ul, 0x1ul,
0x32ul, 0xfffffffffffffffful, 0x0ul, 0, 0, 0);
r[2] = execute_syscall(__NR_socket, 0xaul, 0x2ul, 0x0ul, 0, 0, 0, 0,
0, 0);
NONFAILING(memcpy(
(void*)0x2000ef92,
"\x7c\x14\x04\x00\x00\x00\x07\x97\xee\x0a\xf4\x64\xc8\xde\xb6\x13"
"\xe9\x4a\xa8\x5d\xeb\x86\xc2\x3a\x1c\x35\x0d\x36\xc3\x5a\xc1\x4c"
"\xbb\x63\x87\xd8\xce\x17\xe5\x37\xf9\x1d\xe8\xb1\xf8\xc0\x51\xec"
"\x65\xf8\x37\x70\x65\x81\x24\x2d\x21\x6c\x8c\x20\xf4\xb7\x1c\xd5"
"\x80\xbe\x50\x6c\x3b\xf6\x5b\xf6\x76\x4a\x22\xb4\x04\x81\x0b\x58"
"\x22\x2d\x07\xe0\xcc\x1a\x84\x5f\x30\x67\x92\xf8\xbb\x59\x0c\x82"
"\xc2\x9b\x17\x1c\x4f\x50\xf7\xbd\x91\xf6\xb5\x87\x52\x22\x27\x02"
"\xbc\x7e\xff\xc9\x4a\x9b\x9a\x43\xa9\x91\x91\x11\x61\x39\xe8\x3b"
"\xcf\x8b\x6e\x07\x00\x00\x00\x34\x10\xad\x07\xa7\x81\x8f\x2d\x38"
"\x5b\xdf\x94\xf1\x51\x32\x14\x91\xcf\x80\xbd\x6f\x5b\x06\xd4\x16"
"\x57\x75\x01\x99\xa4\x84\x30\x69",
168));
r[4] = execute_syscall(__NR_setsockopt, r[2], 0x29ul, 0x39ul,
0x2000ef92ul, 0xa8ul, 0, 0, 0, 0);
}
int main()
{
setup_main_process();
int pid = do_sandbox_none(0, false);
int status = 0;
while (waitpid(pid, &status, __WALL) != pid) {
}
return 0;
}