forked from Minki/linux
3fd8712707
commitbbb03029a8
("strparser: Generalize strparser") added more function pointers to 'struct strp_callbacks'; however, kcm_attach() was not updated to initialize them. This could cause the ->lock() and/or ->unlock() function pointers to be set to garbage values, causing a crash in strp_work(). Fix the bug by moving the callback structs into static memory, so unspecified members are zeroed. Also constify them while we're at it. This bug was found by syzkaller, which encountered the following splat: IP: 0x55 PGD 3b1ca067 P4D 3b1ca067 PUD 3b12f067 PMD 0 Oops: 0010 [#1] SMP KASAN Dumping ftrace buffer: (ftrace buffer empty) Modules linked in: CPU: 2 PID: 1194 Comm: kworker/u8:1 Not tainted 4.13.0-rc4-next-20170811 #2 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Bochs 01/01/2011 Workqueue: kstrp strp_work task: ffff88006bb0e480 task.stack: ffff88006bb10000 RIP: 0010:0x55 RSP: 0018:ffff88006bb17540 EFLAGS: 00010246 RAX: dffffc0000000000 RBX: ffff88006ce4bd60 RCX: 0000000000000000 RDX: 1ffff1000d9c97bd RSI: 0000000000000000 RDI: ffff88006ce4bc48 RBP: ffff88006bb17558 R08: ffffffff81467ab2 R09: 0000000000000000 R10: ffff88006bb17438 R11: ffff88006bb17940 R12: ffff88006ce4bc48 R13: ffff88003c683018 R14: ffff88006bb17980 R15: ffff88003c683000 FS: 0000000000000000(0000) GS:ffff88006de00000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000055 CR3: 000000003c145000 CR4: 00000000000006e0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 Call Trace: process_one_work+0xbf3/0x1bc0 kernel/workqueue.c:2098 worker_thread+0x223/0x1860 kernel/workqueue.c:2233 kthread+0x35e/0x430 kernel/kthread.c:231 ret_from_fork+0x2a/0x40 arch/x86/entry/entry_64.S:431 Code: Bad RIP value. RIP: 0x55 RSP: ffff88006bb17540 CR2: 0000000000000055 ---[ end trace f0e4920047069cee ]--- Here is a C reproducer (requires CONFIG_BPF_SYSCALL=y and CONFIG_AF_KCM=y): #include <linux/bpf.h> #include <linux/kcm.h> #include <linux/types.h> #include <stdint.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/syscall.h> #include <unistd.h> static const struct bpf_insn bpf_insns[3] = { { .code = 0xb7 }, /* BPF_MOV64_IMM(0, 0) */ { .code = 0x95 }, /* BPF_EXIT_INSN() */ }; static const union bpf_attr bpf_attr = { .prog_type = 1, .insn_cnt = 2, .insns = (uintptr_t)&bpf_insns, .license = (uintptr_t)"", }; int main(void) { int bpf_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &bpf_attr, sizeof(bpf_attr)); int inet_fd = socket(AF_INET, SOCK_STREAM, 0); int kcm_fd = socket(AF_KCM, SOCK_DGRAM, 0); ioctl(kcm_fd, SIOCKCMATTACH, &(struct kcm_attach) { .fd = inet_fd, .bpf_fd = bpf_fd }); } Fixes:bbb03029a8
("strparser: Generalize strparser") Cc: Dmitry Vyukov <dvyukov@google.com> Cc: Tom Herbert <tom@quantonium.net> Signed-off-by: Eric Biggers <ebiggers@google.com> Signed-off-by: David S. Miller <davem@davemloft.net>
148 lines
3.8 KiB
C
148 lines
3.8 KiB
C
/*
|
|
* Stream Parser
|
|
*
|
|
* Copyright (c) 2016 Tom Herbert <tom@herbertland.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2
|
|
* as published by the Free Software Foundation.
|
|
*/
|
|
|
|
#ifndef __NET_STRPARSER_H_
|
|
#define __NET_STRPARSER_H_
|
|
|
|
#include <linux/skbuff.h>
|
|
#include <net/sock.h>
|
|
|
|
#define STRP_STATS_ADD(stat, count) ((stat) += (count))
|
|
#define STRP_STATS_INCR(stat) ((stat)++)
|
|
|
|
struct strp_stats {
|
|
unsigned long long msgs;
|
|
unsigned long long bytes;
|
|
unsigned int mem_fail;
|
|
unsigned int need_more_hdr;
|
|
unsigned int msg_too_big;
|
|
unsigned int msg_timeouts;
|
|
unsigned int bad_hdr_len;
|
|
};
|
|
|
|
struct strp_aggr_stats {
|
|
unsigned long long msgs;
|
|
unsigned long long bytes;
|
|
unsigned int mem_fail;
|
|
unsigned int need_more_hdr;
|
|
unsigned int msg_too_big;
|
|
unsigned int msg_timeouts;
|
|
unsigned int bad_hdr_len;
|
|
unsigned int aborts;
|
|
unsigned int interrupted;
|
|
unsigned int unrecov_intr;
|
|
};
|
|
|
|
struct strparser;
|
|
|
|
/* Callbacks are called with lock held for the attached socket */
|
|
struct strp_callbacks {
|
|
int (*parse_msg)(struct strparser *strp, struct sk_buff *skb);
|
|
void (*rcv_msg)(struct strparser *strp, struct sk_buff *skb);
|
|
int (*read_sock_done)(struct strparser *strp, int err);
|
|
void (*abort_parser)(struct strparser *strp, int err);
|
|
void (*lock)(struct strparser *strp);
|
|
void (*unlock)(struct strparser *strp);
|
|
};
|
|
|
|
struct strp_msg {
|
|
int full_len;
|
|
int offset;
|
|
};
|
|
|
|
static inline struct strp_msg *strp_msg(struct sk_buff *skb)
|
|
{
|
|
return (struct strp_msg *)((void *)skb->cb +
|
|
offsetof(struct qdisc_skb_cb, data));
|
|
}
|
|
|
|
/* Structure for an attached lower socket */
|
|
struct strparser {
|
|
struct sock *sk;
|
|
|
|
u32 stopped : 1;
|
|
u32 paused : 1;
|
|
u32 aborted : 1;
|
|
u32 interrupted : 1;
|
|
u32 unrecov_intr : 1;
|
|
|
|
struct sk_buff **skb_nextp;
|
|
struct timer_list msg_timer;
|
|
struct sk_buff *skb_head;
|
|
unsigned int need_bytes;
|
|
struct delayed_work delayed_work;
|
|
struct work_struct work;
|
|
struct strp_stats stats;
|
|
struct strp_callbacks cb;
|
|
};
|
|
|
|
/* Must be called with lock held for attached socket */
|
|
static inline void strp_pause(struct strparser *strp)
|
|
{
|
|
strp->paused = 1;
|
|
}
|
|
|
|
/* May be called without holding lock for attached socket */
|
|
void strp_unpause(struct strparser *strp);
|
|
|
|
static inline void save_strp_stats(struct strparser *strp,
|
|
struct strp_aggr_stats *agg_stats)
|
|
{
|
|
/* Save psock statistics in the mux when psock is being unattached. */
|
|
|
|
#define SAVE_PSOCK_STATS(_stat) (agg_stats->_stat += \
|
|
strp->stats._stat)
|
|
SAVE_PSOCK_STATS(msgs);
|
|
SAVE_PSOCK_STATS(bytes);
|
|
SAVE_PSOCK_STATS(mem_fail);
|
|
SAVE_PSOCK_STATS(need_more_hdr);
|
|
SAVE_PSOCK_STATS(msg_too_big);
|
|
SAVE_PSOCK_STATS(msg_timeouts);
|
|
SAVE_PSOCK_STATS(bad_hdr_len);
|
|
#undef SAVE_PSOCK_STATS
|
|
|
|
if (strp->aborted)
|
|
agg_stats->aborts++;
|
|
if (strp->interrupted)
|
|
agg_stats->interrupted++;
|
|
if (strp->unrecov_intr)
|
|
agg_stats->unrecov_intr++;
|
|
}
|
|
|
|
static inline void aggregate_strp_stats(struct strp_aggr_stats *stats,
|
|
struct strp_aggr_stats *agg_stats)
|
|
{
|
|
#define SAVE_PSOCK_STATS(_stat) (agg_stats->_stat += stats->_stat)
|
|
SAVE_PSOCK_STATS(msgs);
|
|
SAVE_PSOCK_STATS(bytes);
|
|
SAVE_PSOCK_STATS(mem_fail);
|
|
SAVE_PSOCK_STATS(need_more_hdr);
|
|
SAVE_PSOCK_STATS(msg_too_big);
|
|
SAVE_PSOCK_STATS(msg_timeouts);
|
|
SAVE_PSOCK_STATS(bad_hdr_len);
|
|
SAVE_PSOCK_STATS(aborts);
|
|
SAVE_PSOCK_STATS(interrupted);
|
|
SAVE_PSOCK_STATS(unrecov_intr);
|
|
#undef SAVE_PSOCK_STATS
|
|
|
|
}
|
|
|
|
void strp_done(struct strparser *strp);
|
|
void strp_stop(struct strparser *strp);
|
|
void strp_check_rcv(struct strparser *strp);
|
|
int strp_init(struct strparser *strp, struct sock *sk,
|
|
const struct strp_callbacks *cb);
|
|
void strp_data_ready(struct strparser *strp);
|
|
int strp_process(struct strparser *strp, struct sk_buff *orig_skb,
|
|
unsigned int orig_offset, size_t orig_len,
|
|
size_t max_msg_size, long timeo);
|
|
|
|
#endif /* __NET_STRPARSER_H_ */
|